本篇內(nèi)容主要講解“SpringDataJpa怎么實體對象增強設(shè)計”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“SpringDataJpa怎么實體對象增強設(shè)計”吧!
站在用戶的角度思考問題,與客戶深入溝通,找到甘孜州網(wǎng)站設(shè)計與甘孜州網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:成都網(wǎng)站設(shè)計、成都做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、域名注冊、網(wǎng)絡(luò)空間、企業(yè)郵箱。業(yè)務(wù)覆蓋甘孜州地區(qū)。
在日常的 Java-web 開發(fā)過程中時常需要做一些單表的數(shù)據(jù)操作,常用操作有:單表的新增、單表根據(jù)ID查詢,單表根據(jù)ID刪除,單表根據(jù)ID修改。對于這四種單表的基本操作時常需要編寫很多重復(fù)代碼如何避免編寫重復(fù)編寫這類代碼成了一個問題。面對這樣的一個問題我們常規(guī)的解決方案有代碼生成器,代碼生成器可以通過數(shù)據(jù)庫建表語句直接得到Controoler、service、dao三者從而避免重復(fù)編寫。除此之外筆者思考了另一種處理方式,不通過代碼生成器通過一個注解來完成上述操作。
首先需要考慮的是四種單表操作的API設(shè)計,一般情況下筆者會定義這樣的幾個API,下面是關(guān)于新增的API筆者會設(shè)計出如下接口。
以用戶做新增舉例
POST http://host:port/user Content-Type: application/json // 添加參數(shù) {}
以部門為例做新增舉例
POST http://host:port/dept Content-Type: application/json // 添加參數(shù) {}
對于上述兩個API設(shè)計可以看到一些大同小異的地方,相同的有都是通過POST進行請求,不同的是后面的路由地址和參數(shù),對于這樣兩組接口可以抽象為下面一個接口
抽象后的新增接口
POST http://host:port/{entity_name} Content-Type: application/json // 添加參數(shù) {}
同樣的其他3個操作也可以通過類似的方式進行抽象。
根據(jù)ID查詢接口
GET http://host:port/{entity_name}/{id}
修改接口
PUT http://host:port/{entity_name} Content-Type: application/json // 修改參數(shù) {}
根據(jù)ID刪除接口
DELETE http://host:port/{entity_name}/{id}
基礎(chǔ)接口設(shè)計完成,可以先將基本的Controller代碼編寫完成。
@RestController public class EntityPluginController { @GetMapping("/{entityPluginName}/{id}") public ResponseEntity
本文使用JPA作為數(shù)據(jù)交互層,以JAP作為交互會有2個關(guān)鍵對象,第一個是數(shù)據(jù)庫實體,第二個是Repository接口。通常情況下會選擇CrudRepository接口來作為數(shù)據(jù)交互層的根對象,也有會選擇JpaRepository接口來作為數(shù)據(jù)交互的根對象,這兩種存在間接引用,類圖如下
了解了常用的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 }
在單表開發(fā)過程中我們做的所有行為都是圍繞這個數(shù)據(jù)庫實體進行操作,比如在新增的時候?qū)⑿略鰠?shù)轉(zhuǎn)換成數(shù)據(jù)庫對象,在更新的是將更新參數(shù)轉(zhuǎn)換成數(shù)據(jù)庫對象,在根據(jù)ID查詢的時候?qū)⒉樵兘Y(jié)果(數(shù)據(jù)庫對象)轉(zhuǎn)換為返回結(jié)果對象,總共存在三種數(shù)據(jù)庫對象的轉(zhuǎn)換,這三種轉(zhuǎn)換是必不可少的,當(dāng)然也可以用一個數(shù)據(jù)庫對象直接來滿足這個操作從而減少代碼量(不建議這么做),對于這三種轉(zhuǎn)換先來定義一個接口,該接口表示了三種對象的轉(zhuǎn)換過程。
數(shù)據(jù)庫對象的三種轉(zhuǎn)換
public interface EntityConvert{ /** * 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
:新增時的參數(shù)類型
UpType
:修改時的參數(shù)類型
ResType
:返回時的參數(shù)類型
EntityType
:數(shù)據(jù)庫實體類型
完成接口定義后需要將這個接口的實現(xiàn)類和實體對象綁定,最簡單的一種綁定模式就是通過注解來表示,注解定義如下
@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; }
注解兩個函數(shù)的含義:
name表示實體名稱,
convertClass表示轉(zhuǎn)換器實現(xiàn)類
下面將注解和實體類進行綁定,具體代碼如下
@EntityPlugin(name = "oauthClient") @Entity @Table(name = "oauth_client", schema = "shands_uc_3_back", catalog = "") public class OauthClientEntity {}
注意:筆者在這里沒有自定義實現(xiàn) EntityConvert 接口,采用的是默認方式,即參數(shù)等于數(shù)據(jù)庫對象
在完成實體對象和轉(zhuǎn)換對象之間的關(guān)系綁定后后需要做到的事情是如何調(diào)用JPA框架將數(shù)據(jù)插入。解決這個問題首先需要從JPA接口入手,在JPA接口中都需要定義兩個泛型,第一個泛型是實體對象,第二個泛型是ID類型,我們需要通過實體對象來獲取前文所編寫的注解信息,使用ID泛型為根據(jù)ID查詢提供參數(shù)支持。下面是存儲上述信息的對象。
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
實現(xiàn)類的類型
crudRepository
表示JPA數(shù)據(jù)庫操作對象
self
表示實體類類型
idClass
表示實體類的ID數(shù)據(jù)類型
完成這些后我們需要解決的問題就是如何從JPA接口提取類和ID類型,如下面代碼所示,我們需要提取CrudRepository
的兩個泛型
@Repository public interface OauthClientRepo extends CrudRepository{ }
這里需要使用反射,具體操作代碼如下:
public class InterfaceReflectUtils { private InterfaceReflectUtils() { } public static List> getInterfaceGenericLasses(Class> check, Class> targetClass) { if (check == null || targetClass == null) { return Collections.emptyList(); } List > res = new ArrayList<>(); Class> cur = check; while (cur != null && cur != Object.class) { Type[] types = cur.getGenericInterfaces(); for (Type type : types) { // todo: 修改為可以根據(jù)類型進行推論 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 > result = getInterfaceGenericLasses(inter, targetClass); if (result != null) { res.addAll(result); } } } cur = cur.getSuperclass(); } return res; } }
在得到兩個泛型數(shù)據(jù)后需要進行數(shù)據(jù)解析和對象組裝并將數(shù)據(jù)存儲,數(shù)據(jù)存儲對象如下
public class EntityPluginCacheBean { public MapgetCacheMap() { return cacheMap; } private final Map 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 { MapcrudRepositoryMap = context.getBeansOfType(CrudRepository.class); crudRepositoryMap.forEach((k, v) -> { Class>[] repositoryInterfaces = AopProxyUtils.proxiedUserInterfaces(v); for (Class> repositoryInterface : repositoryInterfaces) { List > 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 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("不允許出現(xiàn)相同的EntityPlugin名稱 ,entity = [{}]", entityClass); } throw new Exception("不允許出現(xiàn)相同的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
接口暫時不支持它的子類接口,子類接口實現(xiàn)會作為后續(xù)開發(fā)方向。
至此數(shù)據(jù)準(zhǔn)備都已經(jīng)完成,接下來就是將Controller開發(fā)完成,首先定義一個對應(yīng)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對應(yīng)了四種操作模式,下面以保存作為一個實例進行說明。保存的Controller相關(guān)代碼如下
@PostMapping("/{entityPluginName}") public ResponseEntitysave( @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
,如果是說明沒有轉(zhuǎn)換過程,即數(shù)據(jù)庫對象就是參數(shù)對象,因此可以直接做出下面的轉(zhuǎn)換,請求參數(shù)轉(zhuǎn)換成JSON字符串,再通過JSON字符串轉(zhuǎn)換成實體類本身,如果不是則進入核心實現(xiàn)類。核心實現(xiàn)類的相關(guān)代碼如下
@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); } // 存在轉(zhuǎn)換類的情況下 if (convertClass != null) { String[] beanNamesForType = context.getBeanNamesForType(convertClass); // 在 Spring 中能夠搜索到 if (beanNamesForType.length > 0) { String beanName = beanNamesForType[0]; EntityConvert bean = context.getBean(beanName, convertClass); // 轉(zhuǎn)換成數(shù)據(jù)庫實體對象 Object insertDbData = bean.fromInsType(insertParam); // 執(zhí)行插入 return crudRepository.save(insertDbData); } // 不能再 Spring 容器中搜索 else { EntityConvert entityConvert; try { entityConvert = newInstanceFromEntityConvertClass( convertClass); } catch (Exception e) { if (log.isErrorEnabled()) { log.error("無參構(gòu)造初始化失敗,{}" + e); } return null; } Object insertDbData = entityConvert.fromInsType(insertParam); return crudRepository.save(insertDbData); } } // 如果不存在轉(zhuǎn)換器類直接進行插入 else { return crudRepository.save(insertParam); } }
在這段代碼中處理流程如下:
情況一:注解EntityPlugin
中的convertClass
屬性是EntityConvert.class
,直接進JPA相關(guān)操作,注意此時的參數(shù)已經(jīng)被Controller轉(zhuǎn)換成實際的數(shù)據(jù)庫對象。
情況二:注解EntityPlugin
中的convertClass
屬性不是EntityConvert.class
,此時可能存在兩種分類,第一種convertClass
交給Spring管理,第二種convertClass
不是Spring管理,對應(yīng)這兩種情況分別做出如下兩種操作:
從Spring中根據(jù)類型找到所有的bean取第一個作為EntityConvert
接口的實現(xiàn)類,通過得到的bean進行數(shù)據(jù)轉(zhuǎn)換在調(diào)用JPA相關(guān)操作。
直接通過反射創(chuàng)建EntityConvert
實現(xiàn)類,注意必須要有一個無參構(gòu)造,本例使用無參構(gòu)造進行創(chuàng)建,創(chuàng)建EntityConvert
實例對象后調(diào)用JPA相關(guān)操作。
其他代碼編寫同理,其他實現(xiàn)可以查看這個倉庫:https://gitee.com/pychfarm_admin/entity-plugin
完成了各類編寫后進入測試階段。
新增API測試
POST http://localhost:8080/oauthClient Content-Type: application/json // 參數(shù) { "clientId":"asa", "clientSecurity":"123" }
返回結(jié)果
{ "id": 10, "clientId": "asa", "clientSecurity": "123", "redirectUri": null, "version": null }
數(shù)據(jù)庫結(jié)果
修改API測試
PUT http://localhost:8080/oauthClient Content-Type: application/json // 參數(shù) { "id": 10, "clientId": "asa", "clientSecurity": "123123123", "redirectUri": null, "version": null }
返回結(jié)果
{ "id": 10, "clientId": "asa", "clientSecurity": "123123123", "redirectUri": null, "version": null }
數(shù)據(jù)庫結(jié)果
修改API測試
GET http://localhost:8080/oauthClient/10
返回結(jié)果
{ "id": 10, "clientId": "asa", "clientSecurity": "123123123", "redirectUri": null, "version": null }
修改API測試
DELETE http://localhost:8080/oauthClient/10
返回結(jié)果
true
數(shù)據(jù)庫結(jié)果
通過上述的測試用例對于數(shù)據(jù)庫對線作為參數(shù)的開發(fā)已經(jīng)符合測試用例后續(xù)還有一些其他規(guī)劃將在后續(xù)進行開發(fā),具體計劃如下
EntityConvert
接口使用完善,目前只支持?jǐn)?shù)據(jù)庫對象直接使用,后續(xù)對EntityConvert
接口進行更好的應(yīng)用。
驗證器相關(guān)接入,沒以前還未做數(shù)據(jù)驗證相關(guān)操作,后續(xù)會接入驗證API。
緩存相關(guān),目前對于數(shù)據(jù)還未使用緩存,后續(xù)接入reds-hash組件
緩存接入后對序列化進行自定義。
到此,相信大家對“SpringDataJpa怎么實體對象增強設(shè)計”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!