一個(gè)遲來(lái)的贊,送給JPA。AbstractEntity需要準(zhǔn)備些什么?
本文轉(zhuǎn)載自微信公眾號(hào)「小姐姐味道」,作者小姐姐養(yǎng)的狗02號(hào)。轉(zhuǎn)載本文請(qǐng)聯(lián)系小姐姐味道公眾號(hào)。
本篇屬于代碼解析系列文章之一,主要內(nèi)容是JPA的基礎(chǔ)父類(lèi)設(shè)計(jì)。參考代碼:https://github.com/xjjdog/bcMall/blob/master/bc-utils/src/main/java/cn/xjjdog/bcmall/utils/db/AbstractEntity.java
關(guān)系型數(shù)據(jù)庫(kù)其實(shí)很討人厭,尤其是在你使用數(shù)據(jù)庫(kù)驅(qū)動(dòng)的開(kāi)發(fā)模式時(shí)。需要首先把表給創(chuàng)建好了,然后再使用代碼生成器反向生成一堆幾乎無(wú)法可讀的代碼。當(dāng)字段有變更的時(shí)候,又是一番折騰。
這其中的典型,就是MyBatis,所以催生了更加簡(jiǎn)潔的MyBatis Plus。
了解到一些大廠(阿里、騰訊、抖音等),JPA的使用也越來(lái)越廣泛了,包括我們公司,這是把合適的工具放到了合適的地方。如果想要快速開(kāi)發(fā),JPA無(wú)疑是一個(gè)比較好的選擇。你無(wú)需關(guān)注數(shù)據(jù)庫(kù)表的結(jié)構(gòu),使用代碼驅(qū)動(dòng)即可完成工作,管它后面是MySQL還是Oracle。JPA把數(shù)據(jù)庫(kù)相關(guān)的知識(shí)給弱化了,讓你專(zhuān)注于業(yè)務(wù)開(kāi)發(fā)。
我個(gè)人曾是非常排斥JPA這種弱化SQL的工具的,這源于對(duì)早起Hibernate版本的錯(cuò)誤認(rèn)識(shí)。但嘗試過(guò)mybatis、spring-data-jdbc、jooq后,發(fā)現(xiàn)這個(gè)東西是真的香!一個(gè)遲到的贊,送給JPA。
這對(duì)一些管理系統(tǒng)來(lái)說(shuō),非常合適。因?yàn)樾阅懿⒉皇沁@些系統(tǒng)主要的痛點(diǎn),業(yè)務(wù)復(fù)雜性才是。
本文將介紹一個(gè)簡(jiǎn)單的實(shí)體類(lèi),需要準(zhǔn)備哪些基本字段。這些字段,又是如何在代碼中被使用的。
1. 基本字段介紹
首先看一下我們的基礎(chǔ)定義類(lèi)。
代碼不多,信息卻不少。
下面來(lái)一行行解析。
- @Data
 
Data注解是屬于lombok類(lèi)的,lombok是地球人都知道的代碼簡(jiǎn)化工具,提供了非常多的注解。如果你不想記憶太多的注解,直接加上一個(gè)Data,是最偷懶的選擇。
- @MappedSuperclass
 
這個(gè)注解是JPA的,用來(lái)標(biāo)識(shí)父類(lèi)。標(biāo)注為@MappedSuperclass的類(lèi)將不是一個(gè)完整的實(shí)體類(lèi),不會(huì)映射到數(shù)據(jù)庫(kù)表,但是它的屬性都將映射到子類(lèi)的數(shù)據(jù)庫(kù)字段中。放在這里再合適不過(guò)了。
- @EntityListeners(AuditingEntityListener.class)
 
開(kāi)啟自動(dòng)審計(jì)功能,這個(gè)和下面的兩個(gè)日期字段是相互配合的,我們稍后介紹。
- @JsonIgnoreProperties(value = {"hibernateLazyInitializer", "handler"}) //直接使用bean時(shí),避免json序列號(hào)失敗
 
有時(shí)候,我們想要再controller層直接使用JPA的實(shí)體。但JPA內(nèi)部其實(shí)是有很多附加變量的,比如hibernateLazyInitializer。
為了讓實(shí)體在json序列化的時(shí)候能夠正常進(jìn)行,需要忽略這兩個(gè)字段。所以這個(gè)注解,是屬于jackson json的。
2. 自定義ID生成器
JPA其實(shí)提供了非常多的ID生成策略。不過(guò),在互聯(lián)網(wǎng)應(yīng)用下,應(yīng)用較多的還是雪花算法,因?yàn)樗兄己玫臄U(kuò)展性,在數(shù)據(jù)遷移的時(shí)候也不會(huì)有很多沖突。
為了指定雪花算法,我們需要下面幾行代碼。
- static final String ID_GEN = "cn.xjjdog.bcmall.utils.db.DistributedId";
 - @Id
 - @GenericGenerator(name = "IdGen", strategy = ID_GEN)
 - @GeneratedValue(generator = "IdGen")
 
其中的一個(gè)關(guān)鍵,就是使用我們名稱(chēng)叫做IdGen的ID生成器。這里的代碼,是有一點(diǎn)小遺憾的。由于JVM類(lèi)加載的緣故,我們無(wú)法在注解中直接使用類(lèi)的名稱(chēng)(*.class.getName()) 來(lái)獲取它的包路徑,只能作為字符串寫(xiě)死在這里。
下面我們就來(lái)看一下這個(gè)ID生成器的處理。
- public class DistributedId implements IdentifierGenerator {
 - @Override
 - public Serializable generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object obj) throws HibernateException {
 - if (obj == null) throw new HibernateException(new NullPointerException()) ;
 - if ((((AbstractEntity) obj).getId()) == null) {
 - return String.valueOf(Snowflake.createId());
 - } else {
 - return ((AbstractEntity) obj).getId();
 - }
 - }
 - }
 
代碼如上。在直接使用之前,我們還做了一點(diǎn)小處理。當(dāng)我們判斷實(shí)體的ID為空的時(shí)候,才使用雪花算法構(gòu)造一個(gè)新的ID;否則使用實(shí)體原來(lái)設(shè)置好的ID,保持不變。
為什么這樣做?因?yàn)檫@是有需求的。像訂單這種業(yè)務(wù),你需要先生成一個(gè)訂單號(hào),然后再更新一些數(shù)據(jù)庫(kù)信息,發(fā)布一些消息等;而不是在保存動(dòng)作出發(fā)的時(shí)候才生成一個(gè)。
如果你不做上面代碼的處理。JPA將每次保存的時(shí)候都自動(dòng)生成一個(gè),覆蓋了你原有的。我就在這里吃過(guò)虧,通過(guò)debug代碼才進(jìn)行的修復(fù)。
3. 自動(dòng)填充字段
上面說(shuō)到createdDate和lastModifiedDate兩個(gè)字段,其實(shí)在使用的時(shí)候,是不需要手動(dòng)去設(shè)值的。這兩個(gè)值,將通過(guò)審計(jì)功能自動(dòng)完成。
- @EntityListeners(AuditingEntityListener.class)
 
當(dāng)然,我們還要用特有的注解,來(lái)標(biāo)識(shí)這兩個(gè)字段。
- /**
 - * 創(chuàng)建時(shí)間
 - */
 - @CreatedDate
 - private Date createdDate;
 - /**
 - * 更新時(shí)間
 - */
 - @LastModifiedDate
 - private Date lastModifiedDate;
 
最后,不要忘了在全局配置中通過(guò)Config開(kāi)啟這個(gè)功能。
- @Configuration
 - @EnableJpaAuditing
 - public class JpaConfig {
 - }
 
當(dāng)然,審計(jì)是不能沒(méi)有用戶(hù)的。所以這個(gè)系列還有@CreatedBy注解,用來(lái)標(biāo)注是誰(shuí)創(chuàng)建的。你需要在代碼中組裝它們,比如下面的代碼,就是從Spring Sercurity中獲取用戶(hù)信息。
- @Configuration
 - @Slf4j
 - public class UserAuditor implements AuditorAware<String> {
 - @Override
 - public Optional<String> getCurrentAuditor() {
 - UserDetails user;
 - try {
 - user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 - return Optional.ofNullable(user.getUsername());
 - }catch (Exception e){
 - return Optional.empty();
 - }
 - }
 - }
 
4. End
JPA寫(xiě)管理系統(tǒng),真的是神器。當(dāng)你不需要考慮極限的代碼效率時(shí),是一個(gè)非常好的選擇。再看看最近的MyBatis版本,包括MyBatis Plus設(shè)計(jì),很多東西已經(jīng)和JPA越來(lái)越像了。因?yàn)樵谠O(shè)計(jì)上來(lái)說(shuō),JPA是最接近面向?qū)ο缶幊痰乃枷氲摹?/p>
B端復(fù)雜業(yè)務(wù)的技術(shù)棧,并不需要和C端的技術(shù)棧相雷同。JPA顯然通過(guò)極少的代碼和約定,就能把事情搞定,讓開(kāi)發(fā)者真正的把重點(diǎn)關(guān)注到業(yè)務(wù)開(kāi)發(fā)上來(lái)。后面的文章,我們還會(huì)用到MyBatis和MyBatis Plus,到時(shí)候,我們?cè)僭敿?xì)分析它們使用的場(chǎng)景。
作者簡(jiǎn)介:小姐姐味道 (xjjdog),一個(gè)不允許程序員走彎路的公眾號(hào)。聚焦基礎(chǔ)架構(gòu)和Linux。十年架構(gòu),日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。我的個(gè)人微信xjjdog0,歡迎添加好友,進(jìn)一步交流。

















 
 
 





 
 
 
 