您好,登錄后才能下訂單哦!
本篇內容主要講解“SpringDataJpa怎么實體對象增強設計”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“SpringDataJpa怎么實體對象增強設計”吧!
在日常的 Java-web 開發過程中時常需要做一些單表的數據操作,常用操作有:單表的新增、單表根據ID查詢,單表根據ID刪除,單表根據ID修改。對于這四種單表的基本操作時常需要編寫很多重復代碼如何避免編寫重復編寫這類代碼成了一個問題。面對這樣的一個問題我們常規的解決方案有代碼生成器,代碼生成器可以通過數據庫建表語句直接得到Controoler、service、dao三者從而避免重復編寫。除此之外筆者思考了另一種處理方式,不通過代碼生成器通過一個注解來完成上述操作。
首先需要考慮的是四種單表操作的API設計,一般情況下筆者會定義這樣的幾個API,下面是關于新增的API筆者會設計出如下接口。
以用戶做新增舉例
POST http://host:port/user Content-Type: application/json // 添加參數 {}
以部門為例做新增舉例
POST http://host:port/dept Content-Type: application/json // 添加參數 {}
對于上述兩個API設計可以看到一些大同小異的地方,相同的有都是通過POST進行請求,不同的是后面的路由地址和參數,對于這樣兩組接口可以抽象為下面一個接口
抽象后的新增接口
POST http://host:port/{entity_name} Content-Type: application/json // 添加參數 {}
同樣的其他3個操作也可以通過類似的方式進行抽象。
根據ID查詢接口
GET http://host:port/{entity_name}/{id}
修改接口
PUT http://host:port/{entity_name} Content-Type: application/json // 修改參數 {}
根據ID刪除接口
DELETE http://host:port/{entity_name}/{id}
基礎接口設計完成,可以先將基本的Controller代碼編寫完成。
@RestController public class EntityPluginController { @GetMapping("/{entityPluginName}/{id}") public ResponseEntity<Object> findById( @PathVariable("entityPluginName") String entityPluginName, @PathVariable("id") String id ) { return null; } @PostMapping("/{entityPluginName}") public ResponseEntity<Object> save( @PathVariable("entityPluginName") String entityPluginName, @RequestBody Object insertParam ) { return null; } @PutMapping("/{entityPluginName}") public ResponseEntity<Object> update( @PathVariable("entityPluginName") String entityPluginName, @RequestBody Object updateParam ) { return null; } @DeleteMapping("/{entityPluginName}/{id}") public ResponseEntity<Object> deleteById( @PathVariable("entityPluginName") String entityPluginName, @PathVariable("id") String id ) { return null; } }
本文使用JPA作為數據交互層,以JAP作為交互會有2個關鍵對象,第一個是數據庫實體,第二個是Repository接口。通常情況下會選擇CrudRepository接口來作為數據交互層的根對象,也有會選擇JpaRepository接口來作為數據交互的根對象,這兩種存在間接引用,類圖如下
了解了常用的JPA操作對象后來看一個Entity對象
@Entity @Table(name = "oauth_client", schema = "shands_uc_3_back", catalog = "") public class OauthClientEntity { private Long id; private String clientId; private String clientSecurity; private String redirectUri; private Long version; // 省略getter&setter }
在單表開發過程中我們做的所有行為都是圍繞這個數據庫實體進行操作,比如在新增的時候將新增參數轉換成數據庫對象,在更新的是將更新參數轉換成數據庫對象,在根據ID查詢的時候將查詢結果(數據庫對象)轉換為返回結果對象,總共存在三種數據庫對象的轉換,這三種轉換是必不可少的,當然也可以用一個數據庫對象直接來滿足這個操作從而減少代碼量(不建議這么做),對于這三種轉換先來定義一個接口,該接口表示了三種對象的轉換過程。
數據庫對象的三種轉換
public interface EntityConvert<InsType, UpType, ResType, EntityType> { /** * convert data from insert param db entity * * @param insType insert param * @return db entity */ EntityType fromInsType(InsType insType); /** * convert data from update param to db entity * * @param upType update param * @return db entity */ EntityType fromUpType(UpType upType); /** * convert data from db entity to response entity * * @param entityType db entity * @return response entity */ ResType fromEntity(EntityType entityType); }
在 EntityConvert
接口中定義了4個泛型,含義如下
InsType
:新增時的參數類型
UpType
:修改時的參數類型
ResType
:返回時的參數類型
EntityType
:數據庫實體類型
完成接口定義后需要將這個接口的實現類和實體對象綁定,最簡單的一種綁定模式就是通過注解來表示,注解定義如下
@java.lang.annotation.Target({ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Documented @java.lang.annotation.Inherited public @interface EntityPlugin { /** * name * * @return name */ String name(); /** * {@link EntityConvert} class * * @return class */ Class<? extends EntityConvert> convertClass() default EntityConvert.class; }
注解兩個函數的含義:
name表示實體名稱,
convertClass表示轉換器實現類
下面將注解和實體類進行綁定,具體代碼如下
@EntityPlugin(name = "oauthClient") @Entity @Table(name = "oauth_client", schema = "shands_uc_3_back", catalog = "") public class OauthClientEntity {}
注意:筆者在這里沒有自定義實現 EntityConvert 接口,采用的是默認方式,即參數等于數據庫對象
在完成實體對象和轉換對象之間的關系綁定后后需要做到的事情是如何調用JPA框架將數據插入。解決這個問題首先需要從JPA接口入手,在JPA接口中都需要定義兩個泛型,第一個泛型是實體對象,第二個泛型是ID類型,我們需要通過實體對象來獲取前文所編寫的注解信息,使用ID泛型為根據ID查詢提供參數支持。下面是存儲上述信息的對象。
public class EntityPluginCache { private String name; private Class<? extends EntityConvert> convertClass; private CrudRepository crudRepository; private Class<?> self; private Class<?> idClass; }
name
表示注解EntityPlugin
的name屬性
convertClass
表示EventConvert
實現類的類型
crudRepository
表示JPA數據庫操作對象
self
表示實體類類型
idClass
表示實體類的ID數據類型
完成這些后我們需要解決的問題就是如何從JPA接口提取類和ID類型,如下面代碼所示,我們需要提取CrudRepository
的兩個泛型
@Repository public interface OauthClientRepo extends CrudRepository<OauthClientEntity ,Long> { }
這里需要使用反射,具體操作代碼如下:
public class InterfaceReflectUtils { private InterfaceReflectUtils() { } public static List<Class<?>> getInterfaceGenericLasses(Class<?> check, Class<?> targetClass) { if (check == null || targetClass == null) { return Collections.emptyList(); } List<Class<?>> res = new ArrayList<>(); Class<?> cur = check; while (cur != null && cur != Object.class) { Type[] types = cur.getGenericInterfaces(); for (Type type : types) { // todo: 修改為可以根據類型進行推論 if (type.getTypeName().contains(targetClass.getName())) { Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments(); for (Type typeArgument : typeArguments) { if (typeArgument instanceof Class) { res.add((Class<?>) typeArgument); } } break; } } Class<?>[] interfaces = cur.getInterfaces(); if (interfaces != null) { for (Class<?> inter : interfaces) { List<Class<?>> result = getInterfaceGenericLasses(inter, targetClass); if (result != null) { res.addAll(result); } } } cur = cur.getSuperclass(); } return res; } }
在得到兩個泛型數據后需要進行數據解析和對象組裝并將數據存儲,數據存儲對象如下
public class EntityPluginCacheBean { public Map<String, EntityPluginCache> getCacheMap() { return cacheMap; } private final Map<String, EntityPluginCache> cacheMap = new ConcurrentHashMap<>(64); }
接口解析代碼如下:
@Component public class EntityPluginRunner implements ApplicationRunner, ApplicationContextAware, Ordered { private static final Logger log = LoggerFactory.getLogger(EntityPluginRunner.class); @Autowired private ApplicationContext context; @Autowired private EntityPluginCacheBean entityPluginCacheBean; @Override public void run(ApplicationArguments args) throws Exception { Map<String, CrudRepository> crudRepositoryMap = context.getBeansOfType(CrudRepository.class); crudRepositoryMap.forEach((k, v) -> { Class<?>[] repositoryInterfaces = AopProxyUtils.proxiedUserInterfaces(v); for (Class<?> repositoryInterface : repositoryInterfaces) { List<Class<?>> interfaceGenericLasses = InterfaceReflectUtils .getInterfaceGenericLasses(repositoryInterface, CrudRepository.class); if (!CollectionUtils.isEmpty(interfaceGenericLasses)) { // entity class Class<?> entityClass = interfaceGenericLasses.get(0); EntityPlugin annotation = entityClass.getAnnotation(EntityPlugin.class); if (annotation != null) { Map<String, EntityPluginCache> cacheMap = entityPluginCacheBean.getCacheMap(); EntityPluginCache value = new EntityPluginCache(); value.setName(annotation.name()); value.setSelf(entityClass); value.setIdClass(interfaceGenericLasses.get(1)); value.setConvertClass(annotation.convertClass()); value.setCrudRepository(v); if (cacheMap.containsKey(annotation.name())) { try { if (log.isErrorEnabled()) { log.error("不允許出現相同的EntityPlugin名稱 ,entity = [{}]", entityClass); } throw new Exception("不允許出現相同的EntityPlugin名稱"); } catch (Exception e) { e.printStackTrace(); } } cacheMap.put(annotation.name(), value); } } } }); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } }
注意本例中只支持CrudRepository
接口暫時不支持它的子類接口,子類接口實現會作為后續開發方向。
至此數據準備都已經完成,接下來就是將Controller開發完成,首先定義一個對應Controller的Service
public interface EntityPluginCoreService { Object findById(String entityPluginName, String id); Object save(String entityPluginName, Object insertParam); Object update(String entityPluginName, Object updateParam); Boolean deleteById(String entityPluginName, String id); }
該Service對應了四種操作模式,下面以保存作為一個實例進行說明。保存的Controller相關代碼如下
@PostMapping("/{entityPluginName}") public ResponseEntity<Object> save( @PathVariable("entityPluginName") String entityPluginName, @RequestBody Object insertParam ) { EntityPluginCache entityPluginCache = entityPluginCacheBean.getCacheMap().get(entityPluginName); Class<? extends EntityConvert> convertClass = entityPluginCache.getConvertClass(); if (convertClass != EntityConvert.class) { Object save = coreService.save(entityPluginName, insertParam); return ResponseEntity.ok(save); } else { Object o = gson.fromJson(gson.toJson(insertParam), entityPluginCache.getSelf()); Object save = coreService.save(entityPluginName, o); return ResponseEntity.ok(save); } }
在Controller這段代碼中可以看到有兩個分支,這里兩個分支的判斷是注解EntityPlugin
中的convertClass
屬性是否為EntityConvert.class
,如果是說明沒有轉換過程,即數據庫對象就是參數對象,因此可以直接做出下面的轉換,請求參數轉換成JSON字符串,再通過JSON字符串轉換成實體類本身,如果不是則進入核心實現類。核心實現類的相關代碼如下
@Override public Object save(String entityPluginName, Object insertParam) { EntityPluginCache entityPluginCache = entityPluginCacheBean.getCacheMap().get(entityPluginName); CrudRepository crudRepository = entityPluginCache.getCrudRepository(); Class<? extends EntityConvert> convertClass = entityPluginCache.getConvertClass(); if (convertClass == EntityConvert.class) { return crudRepository.save(insertParam); } // 存在轉換類的情況下 if (convertClass != null) { String[] beanNamesForType = context.getBeanNamesForType(convertClass); // 在 Spring 中能夠搜索到 if (beanNamesForType.length > 0) { String beanName = beanNamesForType[0]; EntityConvert bean = context.getBean(beanName, convertClass); // 轉換成數據庫實體對象 Object insertDbData = bean.fromInsType(insertParam); // 執行插入 return crudRepository.save(insertDbData); } // 不能再 Spring 容器中搜索 else { EntityConvert entityConvert; try { entityConvert = newInstanceFromEntityConvertClass( convertClass); } catch (Exception e) { if (log.isErrorEnabled()) { log.error("無參構造初始化失敗,{}" + e); } return null; } Object insertDbData = entityConvert.fromInsType(insertParam); return crudRepository.save(insertDbData); } } // 如果不存在轉換器類直接進行插入 else { return crudRepository.save(insertParam); } }
在這段代碼中處理流程如下:
情況一:注解EntityPlugin
中的convertClass
屬性是EntityConvert.class
,直接進JPA相關操作,注意此時的參數已經被Controller轉換成實際的數據庫對象。
情況二:注解EntityPlugin
中的convertClass
屬性不是EntityConvert.class
,此時可能存在兩種分類,第一種convertClass
交給Spring管理,第二種convertClass
不是Spring管理,對應這兩種情況分別做出如下兩種操作:
從Spring中根據類型找到所有的bean取第一個作為EntityConvert
接口的實現類,通過得到的bean進行數據轉換在調用JPA相關操作。
直接通過反射創建EntityConvert
實現類,注意必須要有一個無參構造,本例使用無參構造進行創建,創建EntityConvert
實例對象后調用JPA相關操作。
其他代碼編寫同理,其他實現可以查看這個倉庫:https://gitee.com/pychfarm_admin/entity-plugin
完成了各類編寫后進入測試階段。
新增API測試
POST http://localhost:8080/oauthClient Content-Type: application/json // 參數 { "clientId":"asa", "clientSecurity":"123" }
返回結果
{ "id": 10, "clientId": "asa", "clientSecurity": "123", "redirectUri": null, "version": null }
數據庫結果
修改API測試
PUT http://localhost:8080/oauthClient Content-Type: application/json // 參數 { "id": 10, "clientId": "asa", "clientSecurity": "123123123", "redirectUri": null, "version": null }
返回結果
{ "id": 10, "clientId": "asa", "clientSecurity": "123123123", "redirectUri": null, "version": null }
數據庫結果
修改API測試
GET http://localhost:8080/oauthClient/10
返回結果
{ "id": 10, "clientId": "asa", "clientSecurity": "123123123", "redirectUri": null, "version": null }
修改API測試
DELETE http://localhost:8080/oauthClient/10
返回結果
true
數據庫結果
通過上述的測試用例對于數據庫對線作為參數的開發已經符合測試用例后續還有一些其他規劃將在后續進行開發,具體計劃如下
EntityConvert
接口使用完善,目前只支持數據庫對象直接使用,后續對EntityConvert
接口進行更好的應用。
驗證器相關接入,沒以前還未做數據驗證相關操作,后續會接入驗證API。
緩存相關,目前對于數據還未使用緩存,后續接入reds-hash組件
緩存接入后對序列化進行自定義。
到此,相信大家對“SpringDataJpa怎么實體對象增強設計”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。