2
案例概述

数据访问对象(DAO)模式是一种结构模式,它允许我们使用抽象API将应用程序/业务层与持久层(通常是关系数据库,但它可以是任何其他持久性机制)隔离开来。
此API的功能是从应用程序中隐藏在底层存储机制中执行CRUD操作所涉及的所有复杂性。这允许两个层分别进化而不知道彼此之间的任何事情。
在本文中,我们将深入研究模式的实现,并且我们将学习如何使用它来抽象调用JPA实体管理器

简单实施

要了解DAO模式的工作原理,让我们创建一个基本示例。
假设我们想要开发一个管理用户的应用程序。为了使应用程序的域模型完全不受数据库的影响,我们将创建一个简单的DAO类,它将保持这些组件彼此完美地分离。

由于我们的应用程序将与用户一起工作,我们需要定义一个类来实现其模型:

public class User {
     
    private String name;
    private String email;
     
    // constructors / standard setters / getters
}

User类只是用户数据的普通容器,因此它不实现任何其他值得强调的行为。

当然,我们在这里需要做的最相关的设计选择是如何使使用这个类的应用程序与任何可以在某个时候实现的持久性机制隔离开来。

这正是DAO模式试图解决的问题。

DAO API

让我们定义一个基本的DAO层,这样我们就可以看到它如何使域模型与持久层完全分离。

这是DAO API:

public interface Dao<T> {
     
    Optional<T> get(long id);
     
    List<T> getAll();
     
    void save(T t);
     
    void update(T t, String[] params);
     
    void delete(T t);
}

从鸟瞰的角度来看,很明显Dao接口定义了一个抽象API,它对类型为T的对象执行CRUD操作。

由于接口提供的抽象级别很高,因此很容易创建与用户对象一起工作的具体的、细粒度的实现。

UserDao类

让我们定义Dao接口的特定于用户的实现:

public class UserDao implements Dao<User> {
     
    private List<User> users = new ArrayList<>();
     
    public UserDao() {
        users.add(new User("John", "john@domain.com"));
        users.add(new User("Susan", "susan@domain.com"));
    }
     
    @Override
    public Optional<User> get(long id) {
        return Optional.ofNullable(users.get((int) id));
    }
     
    @Override
    public List<User> getAll() {
        return users;
    }
     
    @Override
    public void save(User user) {
        users.add(user);
    }
     
    @Override
    public void update(User user, String[] params) {
        user.setName(Objects.requireNonNull(
          params[0], "Name cannot be null"));
        user.setEmail(Objects.requireNonNull(
          params[1], "Email cannot be null"));
         
        users.add(user);
    }
     
    @Override
    public void delete(User user) {
        users.remove(user);
    }
}

所述的UserDAO类实现所有用于获取,更新和删除所需要的功能的用户的对象。

为简单起见,User List的作用类似于内存数据库,在构造函数中填充了几个User对象。

当然,重构其他方法很容易,因此它们可以用于关系数据库。

虽然User和UserDao类在同一个应用程序中独立共存,但我们仍然需要看看后者如何用于保持持久层对应用程序逻辑的隐藏:

public class UserApplication {
 
    private static Dao userDao;
 
    public static void main(String[] args) {
        userDao = new UserDao();
         
        User user1 = getUser(0);
        System.out.println(user1);
        userDao.update(user1, new String[]{"Jake", "jake@domain.com"});
         
        User user2 = getUser(1);
        userDao.delete(user2);
        userDao.save(new User("Julie", "julie@domain.com"));
         
        userDao.getAll().forEach(user -> System.out.println(user.getName()));
    }
 
    private static User getUser(long id) {
        Optional<User> user = userDao.get(id);
         
        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
}

这个例子是人为的,但简而言之,它显示了DAO模式背后的动机。在这种情况下,main方法只使用UserDao实例对几个User对象执行CRUD操作。

此过程最相关的方面是UserDao如何从应用程序中隐藏有关如何持久,更新和删除对象的所有低级详细信息。

将模式与JPA一起使用

开发人员普遍认为JPA的发布将DAO模式的功能降级为零,因为该模式只是JPA实体经理提供的另一层抽象和复杂性。

毫无疑问,在某些情况下这是事实。即便如此,有时我们只想向我们的应用程序公开实体管理器API的一些特定于域的方法。在这种情况下,DAO模式有其自己的位置。

JpaUserDao类

话虽如此,让我们创建一个Dao接口的新实现,这样我们就可以看到它如何封装JPA实体管理器提供的开箱即用功能:

public class JpaUserDao implements Dao<User> {
     
    private EntityManager entityManager;
     
    // standard constructors
     
    @Override
    public Optional<User> get(long id) {
        return Optional.ofNullable(entityManager.find(User.class, id));
    }
     
    @Override
    public List<User> getAll() {
        Query query = entityManager.createQuery("SELECT e FROM User e");
        return query.getResultList();
    }
     
    @Override
    public void save(User user) {
        executeInsideTransaction(entityManager -> entityManager.persist(user));
    }
     
    @Override
    public void update(User user, String[] params) {
        user.setName(Objects.requireNonNull(params[0], "Name cannot be null"));
        user.setEmail(Objects.requireNonNull(params[1], "Email cannot be null"));
        executeInsideTransaction(entityManager -> entityManager.merge(user));
    }
     
    @Override
    public void delete(User user) {
        executeInsideTransaction(entityManager -> entityManager.remove(user));
    }
     
    private void executeInsideTransaction(Consumer<EntityManager> action) {
        EntityTransaction tx = entityManager.getTransaction();
        try {
            tx.begin();
            action.accept(entityManager);
            tx.commit(); 
        }
        catch (RuntimeException e) {
            tx.rollback();
            throw e;
        }
    }
}

该JpaUserDao类是能够与由JPA实现所支持的任何关系数据库的工作。

此外,如果我们仔细研究这个类,我们将会意识到使用CompositionDependency Injection如何允许我们只调用应用程序所需的实体管理器方法。

简而言之,我们有一个特定于域的定制API,而不是整个实体管理器的API。

重构User类

在这种情况下,我们将使用Hibernate作为JPA默认实现,因此我们将相应地重构User类:

@Entity
@Table(name = "users")
public class User {
     
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
     
    private String name;
    private String email;
     
    // standard constructors / setters / getters
}
以编程方式引导JPA实体管理器

假设我们已经在本地或远程运行MySQL的工作实例,并且数据库表“users”填充了一些用户记录,我们需要获得一个JPA实体管理器,因此我们可以使用JpaUserDao类来执行CRUD操作数据库。

在大多数情况下,我们通过典型的“persistence.xml”文件来实现这一点,这是标准方法。

在这种情况下,我们将采用“xml-less”方法,通过Hibernate方便的EntityManagerFactoryBuilderImpl类获取具有普通Java的实体管理器。

有关如何使用Java引导JPA实现的详细说明,请查看用Java编程引导JPA

UserApplication类

最后,让我们重构一下初始的UserApplication类,这样它就可以使用JpaUserDao实例并在User实体上执行CRUD操作:

public class UserApplication {
 
    private static Dao<User> jpaUserDao;
 
    // standard constructors
     
    public static void main(String[] args) {
        User user1 = getUser(1);
        System.out.println(user1);
        updateUser(user1, new String[]{"Jake", "jake@domain.com"});
        saveUser(new User("Monica", "monica@domain.com"));
        deleteUser(getUser(2));
        getAllUsers().forEach(user -> System.out.println(user.getName()));
    }
     
    public static User getUser(long id) {
        Optional<User> user = jpaUserDao.get(id);
         
        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
     
    public static List<User> getAllUsers() {
        return jpaUserDao.getAll();
    }
     
    public static void updateUser(User user, String[] params) {
        jpaUserDao.update(user, params);
    }
     
    public static void saveUser(User user) {
        jpaUserDao.save(user);
    }
     
    public static void deleteUser(User user) {
        jpaUserDao.delete(user);
    }
}

即使示例确实非常有限,它仍然有助于演示如何将DAO模式的功能与实体管理器提供的功能集成。

在大多数应用程序中,有一个DI框架,它负责将JpaUserDao实例注入UserApplication类。为简单起见,我们省略了此过程的详细信息。

最相关的点这里要强调的是如何在JpaUserDao类有助于保持UserApplication类完全无关关于持久层如何执行CRUD操作

此外,由于Dao接口和实体管理器提供的抽象级别,我们可以将MySQL交换到任何其他RDBMS(甚至是平面数据库),而且,我们的应用程序仍将按预期继续工作。

案例结论

在本文中,我们深入研究了DAO模式的关键概念,如何在Java中实现它,以及如何在JPA的实体管理器之上使用它。


qianmoQ
412 声望23 粉丝