JPA 应用技巧 1:实体类和实体 DAO 模板
Posted on 2011-09-07 17:40 蜀山兆孨龘 阅读(1350) 评论(8) 编辑 收藏 所属分类: Java EE最近闲来无事(楼主确实太懒了),重翻旧账,捣鼓了下 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;
}
// 更多方法……
}
|