神奇好望角 The Magical Cape of Good Hope

庸人不必自扰,智者何需千虑?
posts - 26, comments - 50, trackbacks - 0, articles - 11
  BlogJava :: 首页 ::  :: 联系 :: 聚合  :: 管理

JPA 应用技巧 1:实体类和实体 DAO 模板

Posted on 2011-09-07 17:40 蜀山兆孨龘 阅读(3561) 评论(8)  编辑  收藏 所属分类: Java EE

最近闲来无事(楼主确实太懒了),重翻旧账,捣鼓了下 JPA 2.0,通过不断地写代码和谷歌,又有了一些旧瓶装新酒的发现和吐槽。楼主将在这一系列文章中慢慢道来。本次开篇带来的是两个模板类:用作实体类基础框架的 AbstractEntity, 以及实现了对实体的基本 CRUD 操作的 BasicEntityDao

一个实体类必须实现 java.io.Serializable 接口,必须有一个 ID 字段作为主键,且最好覆盖 equalshashCode 方法。因为实体类和数据表有对应关系,所以往往根据 ID 来实现 equalshashCode。这很自然地可以引出一个模板类,所有的实体类都可以从它继承:

        /**
         * 该类可作为实体类的模板,其 {@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 作为主键:

        @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();
            }
        }
    

继续发挥想象,让它支持乐观锁:

        @MappedSuperclass
        public class VersionedUuidEntity extends UuidEntity {
            @Version
            private int version;
        }
    

这儿顺便插嘴吐槽下主键的类型。用整数还是 UUID 好呢?这个问题在网上也是争论纷纷。在楼主看来,两者各有优劣:整数主键性能高,可读性也好,但会对数据迁移,例如合并两个数据库,造成不小的麻烦,因为可能出现一大堆重复的主键;UUID 性能差些,看起来晃眼,虽然据说有些数据库针对性地做了优化,想来也不大可能优于整数,不过好处就是理论上出现重复主键的概率比中彩票还小(福彩除外)。说这么一大堆,其实还是蛮纠结啊……楼主一般倾向于用 UUID,只要服务器的配置够劲,想来不会出现明显的性能问题。

接下来说说 BasicEntityDao,它提供了基本的 CRUD 实现,可以用来为会话 Bean 做模板:

        /**
         * 提供了对实体进行基本 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 就可以简单地写成:

        @Stateless
        public class CoopDao extends BasicEntityDao<Coop> {
            @Persistence
            private EntityManager em;

            public CoopDao() {
                super(Coop.class);
            }

            @Override
            protected EntityManager getEntityManager() {
                return em;
            }

            // 更多方法……
        }
    

评论

# re: JPA 应用技巧 1:介绍 AbstractEntity 和 BasicEntityDao  回复  更多评论   

2011-09-07 19:54 by 来如风
晕uuid还用这样搞啊,有很多内置的生成器可以了!!!!!!!

# re: JPA 应用技巧 1:介绍 AbstractEntity 和 BasicEntityDao  回复  更多评论   

2011-09-07 20:47 by 蜀山兆孨龘
@来如风
先别忙着晕呵呵。我这儿讨论的是 JPA 规范,而目前的最新版 2.0 不支持 UUID 生成,2.1 才有计划。你一定是用的 Hibernate 之类的专有 API 吧?

# re: JPA 应用技巧 1:实体类和实体 DAO 模板  回复  更多评论   

2011-09-09 08:22 by tb
uuid还有这样搞得吗

# re: JPA 应用技巧 1:实体类和实体 DAO 模板  回复  更多评论   

2011-09-09 09:49 by 蜀山兆孨龘
@tb
有什么质疑请说出来,咱以理服人好不?

# re: JPA 应用技巧 1:实体类和实体 DAO 模板  回复  更多评论   

2011-09-09 23:45 by 来如风
楼主你看看spring的spring-data工程,你会发现dao怎么搞了,

# re: JPA 应用技巧 1:实体类和实体 DAO 模板  回复  更多评论   

2011-09-10 00:27 by 蜀山兆孨龘
@来如风
我这儿只谈 JPA,不涉及 Spring……数据库访问这种东西,封装得越厚,性能降得越恐怖,不信你可以自己去测。功底够好的话,完全可以直接写 SQL 查询语句(EntityManager#createNativeQuery),而只把封装查询结果和做缓存的任务交给 JPA。

# re: JPA 应用技巧 1:实体类和实体 DAO 模板  回复  更多评论   

2011-09-10 22:23 by 来如风
那研究jpa干什么,研究apache dbutil 或者ibatis 就够了,前两者完全可以满足你的需求,在自己在业务层加个缓存,就够了!!!!!!!!!!

# re: JPA 应用技巧 1:实体类和实体 DAO 模板  回复  更多评论   

2011-09-11 19:56 by 蜀山兆孨龘
@来如风
既然 JPA 支持 EntityManager#createNativeQuery,为什么不能研究?用 JPA 就非得要和你说的那些框架一起用吗?我这篇文章有什么问题请你直接指出来,不要乱扯别的框架好不好?

只有注册用户登录后才能发表评论。


网站导航: