2025-02-10

最近閑來無事(樓主確實太懶瞭),重翻舊賬,搗鼓瞭下 JPA 2.0,通過不斷地寫代碼和谷歌,又有瞭一些舊瓶裝新酒的發現和吐槽。樓主將在這一系列文章中慢慢道來。本次開篇帶來的是兩個模板類:用作實體類基礎框架的 AbstractEntity, 以及實現瞭對實體的基本 CRUD 操作的 BasicEntityDao。

一個實體類必須實現 java.io.Serializable 接口,必須有一個 ID 字段作為主鍵,且最好覆蓋 equals 和 hashCode 方法。因為實體類和數據表有對應關系,所以往往根據 ID 來實現 equals 和 hashCode。這很自然地可以引出一個模板類,所有的實體類都可以從它繼承:

?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 /**
 * 該類可作為實體類的模板,其 {@link #equals(Object)} 和 {@link hashCode()} 方法基於主鍵實現。
 * 子類隻需要實現 {@link #getId()} 方法。
 */
public abstract class AbstractEntity implements Serializable {
    /**
     * 返回主鍵。
     */
    public abstract Object getId();
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return getId() == null ? false
                : getId().equals(((AbstractEntity) obj).getId());
    }
 
    @Override
    public int hashCode() {
        return Objects.hashCode(getId());
    }

針對主鍵的類型,AbstractEntity 可以進一步擴展。例如,可以擴展出一個 UuidEntity,它使用隨機生成的 UUID 作為主鍵:

?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 @MappedSuperclass
public class UuidEntity extends AbstractEntity {
    @Id
    private String id;
 
    @Override
    public String getId() {
        return id;
    }
 
    @PrePersist
    private void generateId() {
        // 僅在持久化前生成 ID,提升一點性能。
        id = UUID.randomUUID().toString();
    }

繼續發揮想象,讓它支持樂觀鎖:

?1
2
3
4
5 @MappedSuperclass
public class VersionedUuidEntity extends UuidEntity {
    @Version
    private int version;

這兒順便插嘴吐槽下主鍵的類型。用整數還是 UUID 好呢?這個問題在網上也是爭論紛紛。在樓主看來,兩者各有優劣:整數主鍵性能高,可讀性也好,但會對數據遷移,例如合並兩個數據庫,造成不小的麻煩,因為可能出現一大堆重復的主鍵;UUID 性能差些,看起來晃眼,雖然據說有些數據庫針對性地做瞭優化,想來也不大可能優於整數,不過好處就是理論上出現重復主鍵的概率比中彩票還小(福彩除外)。說這麼一大堆,其實還是蠻糾結啊……樓主一般傾向於用 UUID,隻要服務器的配置夠勁,想來不會出現明顯的性能問題。

接下來說說 BasicEntityDao,它提供瞭基本的 CRUD 實現,可以用來為會話 Bean 做模板:

?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 /**
 * 提供瞭對實體進行基本 CRUD 操作的實現,可作為會話 Bean 的模板。
 */
public abstract class BasicEntityDao<T> {
    private Class<T> entityClass;
    private String entityClassName;
    private String findAllQuery;
    private String countQuery;
 
    protected BasicEntityDao(Class<T> entityClass) {
        this.entityClass = Objects.requireNonNull(entityClass);
        entityClassName = entityClass.getSimpleName();
        findAllQuery = "select e from " + entityClassName + " e";
        countQuery = "select count(e) from " + entityClassName + " e";
    }
 
    /**
     * 返回用於數據庫操作的 {@link EntityManager} 實例。
     */
    protected abstract EntityManager getEntityManager();
 
    public void persist(T entity) {
        getEntityManager().persist(entity);
    }
 
    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }
 
    public List<T> findAll() {
        return getEntityManager().createQuery(findAllQuery, entityClass).getResultList();
    }
 
    public List<T> findRange(int first, int max) {
        return getEntityManager().createQuery(findAllQuery, entityClass)
                .setFirstResult(first).setMaxResults(max).getResultList();
    }
 
    public long count() {
        return (Long) getEntityManager().createQuery(countQuery).getSingleResult();
    }
 
    public T merge(T entity) {
        return getEntityManager().merge(entity);
    }
 
    public void remove(T entity) {
        getEntityManager().remove(merge(entity));
    }

子類隻需要提供 getEntityManager() 的實現即可。假設樓主要做一個養雞場管理系統,對雞圈進行操作的會話 Bean 就可以簡單地寫成:

?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 @Stateless
public class CoopDao extends BasicEntityDao<Coop> {
    @Persistence
    private EntityManager em;
 
    public CoopDao() {
        super(Coop.class);
    }
 
    @Override
    protected EntityManager getEntityManager() {
        return em;
    }
 
    // 更多方法……

作者“神奇好望角”
 
 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *