案例概述
数据访问对象(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 实现所支持的任何关系数据库的工作。
此外,如果我们仔细研究这个类,我们将会意识到使用 Composition 和 Dependency 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 的实体管理器之上使用它。