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