最近閑來無事(樓主確實太懶了),重翻舊賬,搗鼓了下 JPA 2.0,通過不斷地寫代碼和谷歌,又有了一些舊瓶裝新酒的發(fā)現(xiàn)和吐槽。樓主將在這一系列文章中慢慢道來。本次開篇帶來的是兩個模板類:用作實體類基礎(chǔ)框架的 AbstractEntity
, 以及實現(xiàn)了對實體的基本 CRUD 操作的 BasicEntityDao
。
一個實體類必須實現(xiàn) java.io.Serializable
接口,必須有一個 ID 字段作為主鍵,且最好覆蓋 equals
和 hashCode
方法。因為實體類和數(shù)據(jù)表有對應(yīng)關(guān)系,所以往往根據(jù) ID 來實現(xiàn) equals
和 hashCode
。這很自然地可以引出一個模板類,所有的實體類都可以從它繼承:
/** * 該類可作為實體類的模板,其 {@link #equals(Object)} 和 {@link hashCode()} 方法基于主鍵實現(xiàn)。 * 子類只需要實現(xiàn) {@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 作為主鍵:
@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(); } }
繼續(xù)發(fā)揮想象,讓它支持樂觀鎖:
@MappedSuperclass public class VersionedUuidEntity extends UuidEntity { @Version private int version; }
這兒順便插嘴吐槽下主鍵的類型。用整數(shù)還是 UUID 好呢?這個問題在網(wǎng)上也是爭論紛紛。在樓主看來,兩者各有優(yōu)劣:整數(shù)主鍵性能高,可讀性也好,但會對數(shù)據(jù)遷移,例如合并兩個數(shù)據(jù)庫,造成不小的麻煩,因為可能出現(xiàn)一大堆重復的主鍵;UUID 性能差些,看起來晃眼,雖然據(jù)說有些數(shù)據(jù)庫針對性地做了優(yōu)化,想來也不大可能優(yōu)于整數(shù),不過好處就是理論上出現(xiàn)重復主鍵的概率比中彩票還小(福彩除外)。說這么一大堆,其實還是蠻糾結(jié)啊……樓主一般傾向于用 UUID,只要服務(wù)器的配置夠勁,想來不會出現(xiàn)明顯的性能問題。
接下來說說 BasicEntityDao
,它提供了基本的 CRUD 實現(xiàn),可以用來為會話 Bean 做模板:
/** * 提供了對實體進行基本 CRUD 操作的實現(xiàn),可作為會話 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"; } /** * 返回用于數(shù)據(jù)庫操作的 {@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()
的實現(xiàn)即可。假設(shè)樓主要做一個養(yǎng)雞場管理系統(tǒng),對雞圈進行操作的會話 Bean 就可以簡單地寫成:
@Stateless public class CoopDao extends BasicEntityDao<Coop> { @Persistence private EntityManager em; public CoopDao() { super(Coop.class); } @Override protected EntityManager getEntityManager() { return em; } // 更多方法…… }