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 模式应该有了一个清晰的认识,希望能在日后的开发中帮到你。
评论