写点什么

Spring Boot「23」DAO 模式

作者:Samson
  • 2022-11-03
    上海
  • 本文字数:2742 字

    阅读完需:约 9 分钟

Web 开发时经常讲的 Data Access Object (DAO) 其实是一种设计模式。它将应用业务逻辑与数据持久化层解耦,使得业务层无需关心复杂的 CRUD 操作及底层的数据存储实现。

01-经典 DAO 模式实现

一个典型的 DAO 模式实现包含三个部分:实体类(简单的 POJO)、DAO 抽象接口及具体实现、应用层业务逻辑。下面我们以用户的 CRUD 来实现一个经典的 DAO 模式。


  1. 首先,需要有一个实体类 User,它是系统保存用户信息的载体。如前所属,它基本上就是一个 POJO,所以只包含一些属性值及其对应的 getter/setter。


public class Event {    private Long id;    private Date time;    private String title;    /** 省略属性的 getter/setter 及无参构造器 */}
复制代码


  1. 然后,我们要定义针对实体类进行持久化操作的抽象接口 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 中其他方法的实现 */}
复制代码


  1. 最后,我们可以在业务逻辑层使用 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 模式应该有了一个清晰的认识,希望能在日后的开发中帮到你。

发布于: 刚刚阅读数: 4
用户头像

Samson

关注

还未添加个人签名 2019-07-22 加入

InfoQ签约作者 | 阿里云社区签约作者

评论

发布
暂无评论
Spring Boot「23」DAO 模式_Java_Samson_InfoQ写作社区