Web 开发时经常讲的 Data Access Object (DAO) 其实是一种设计模式。它将应用业务逻辑与数据持久化层解耦,使得业务层无需关心复杂的 CRUD 操作及底层的数据存储实现。
01-经典 DAO 模式实现
一个典型的 DAO 模式实现包含三个部分:实体类(简单的 POJO)、DAO 抽象接口及具体实现、应用层业务逻辑。下面我们以用户的 CRUD 来实现一个经典的 DAO 模式。
首先,需要有一个实体类 User,它是系统保存用户信息的载体。如前所属,它基本上就是一个 POJO,所以只包含一些属性值及其对应的 getter/setter。
public class Event { private Long id; private Date time; private String title; /** 省略属性的 getter/setter 及无参构造器 */}
复制代码
然后,我们要定义针对实体类进行持久化操作的抽象接口 DAO 接口,它定义了对实体类的各种持久化操作,例如典型的 CRUD。
public interface IDao<T> { T get(Long id); List<T> getAll(); void update(T t); void save(T t); void delete(T t);}
复制代码
之后,我们可以实现一个存储在内存的中的 DAO 具体实现,例如:
public class EventMemDao implements IDao<Event> { private List<Event> events = new ArrayList<>(32);
@Override public Event get(Long id) { return events.stream().filter(e -> e.getId().equals(id)).findFirst().orElse(null); }
@Override public List<Event> getAll() { return events; } /** 省略 IDao 中其他方法的实现 */}
复制代码
最后,我们可以在业务逻辑层使用 DAO 实现。例如:
private static void memDao(Event e1, Event e2) {
IDao<Event> memDao = new EventMemDao(); memDao.save(e1); memDao.save(e2);
final List<Event> all = memDao.getAll(); all.forEach(System.out::println);}
复制代码
以上仅为模拟 DAO 实现的使用。在实际的业务逻辑中,memDao 可能并不需要自己创建,如果使用了 Spring 容器,可以由容器注入一个具体的实现。
02-JPA 与 DAO 模式的集成
我们在 EventMemDao 中实现了将 Event 对象存储在内存中。本节中,我们将介绍如何将 JPA 持久化与上面的 DAO 模式集成,最终的目标是实现数据存储层的替换,而尽量不影响业务层接口。这也是 DAO 模式主要应对的场景。
基于上述的 DAO 模式抽象,我们如果要实现将 Event 对象存储到数据库中,我们只需要实现一个 EventJpaDao,在具体实现中将信息持久化到数据库中。
public class EventJpaDao implements IDao<Event> {
private EntityManager entityManager;
public EventJpaDao(EntityManager entityManager) { this.entityManager = entityManager; }
@Override public Event get(Long id) { return entityManager.find(Event.class, id); }
@Override public List<Event> getAll() { final Query query = entityManager.createQuery("from Event"); return query.getResultList(); } /** 省略 IDao 中其他方法的实现 */}
复制代码
如何实例化 EntityManager 可以参考之前的文章 1。
可以看到,我们只需要修改具体的 Dao 实现,应用层基本不需要改动,就替换掉了底层持久化实现。这中 DAO 模式的设计,让数据持久化层与应用层解耦,两者可以独立升级、改造。例如,如果我们不想使用 JPA,而希望使用 Hibernate 原生 API,那应该如何改造上面的实现呢?
其实很简单,只需要将 EventJpaDao 中的 EntityManager 替换为 Hibernate 原生 API 中的 SessionFactory,需要使用 EntityManager 的地方替换为 SessionFactory#getCurrentSession() 获得当前 Session 即可。
03-使用泛型优化 DAO 模式
前两节介绍的 DAO 模式虽然在设计上实现了业务层与持久化层的解耦,但由于应用中的实体类与 DAO 实现类的关系往往是一对一对应的,意味着我们在开发应用时需要实现、维护大量的 DAO 实现类。而且这些 DAO 实现类大多代码都比较相似,重复代码较多。随着业务发展和项目规模增长,这一层的代码将越来越多。本节将介绍一种使用 Java 泛型来减少 DAO 实现类的方法。
我们继续复用前两节中使用的抽象 DAO 接口 IDao。然后,我们使用泛型实现一个 GenericDao 接口。
public class GenericDao<T extends Serializable> implements IDao<T> { private Class<T> clazz;
private EntityManager entityManager;
public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; }
public void setClazz(Class<T> clazz) { this.clazz = clazz; }
@Override public T get(Long id) { return entityManager.find(clazz, id); }
@Override public List<T> getAll() { return entityManager.createQuery("From " + clazz.getName()).getResultList(); } /** 省略其他的 getter/setter 以及其他 IDao 中定义的方法实现 */}
复制代码
然后,我们增加一个实体类 Message,来验证一下如何使用 GenericDao 来对两个不同类型的实体类进行持久化操作。
@Entity@Table(name = "MESSAGES")public class Message implements Serializable { @Id @GeneratedValue private Long id; private String topic; private String content;
/** 省略其他的 getter/setter 以及无参构造器 */}
复制代码
在使用时,需要按照不同的实体类型创建不同的 GenericDao 实例,例如:
private static void genericDao() { GenericDao<Event> eventGenericDao = new GenericDao<>(); eventGenericDao.setEntityManager(entityManager); eventGenericDao.setClazz(Event.class);
eventGenericDao.save(new Event(new Date(), "Our very first event!")); eventGenericDao.save(new Event(new Date(), "A follow up event!")); final List<Event> events = eventGenericDao.getAll(); events.forEach(System.out::println);
final GenericDao<Message> messageGenericDao = new GenericDao<>(); messageGenericDao.setEntityManager(entityManager); messageGenericDao.setClazz(Message.class);
messageGenericDao.save(new Message("greeting", "hello!")); messageGenericDao.save(new Message("greeting", "how are you?")); final List<Message> messages = messageGenericDao.getAll(); messages.forEach(System.out::println);}
复制代码
到此为止,大功告成。如果看到这里,相信你对 DAO 模式应该有了一个清晰的认识,希望能在日后的开发中帮到你。
评论