定义
数据访问对象模式(Data Access Object Pattern)又称为 DAO 模式,是一种面向对象的数据访问接口,DAO 一般都是和数据库打交道,属于业务逻辑和数据库中间的环节,负责业务逻辑数据的持久化。
DAO 模式在开发 MVC 应用十分普遍,不管是 JPA 的 DAO,还是 Mybatis 的 Mapper,其实都是面向持久化的操作,我们都可以将其成为数据访问对象。
下图为阿里巴巴 JAVA 开发手册中介绍的应用分层,其中数据访问对象承担的就是 DAO 层工作,负责和数据源打交道。感兴趣的同学可以去看看阿里巴巴 JAVA 开发手册,比较推荐。
组成角色
数据访问对象模式包含角色如下:
- 数据访问对象接口(Data Access Object Interface):提供数据持久化或数据访问的抽象接口定义;
- 数据访问对象具体实现类(Data Access Object Concrete Class):负责实现数据访问对象接口,真正对数据进行操作的实现类,底层数据源可以是数据库、内存、Xml、文件数据等等;
- 模型对象或值对象(Model Object/Value Object):传统的 POJO(Plain Ordinary Java Object),可以理解为简单的实体类。
数据访问对象模式的 UML 类图如下:
数据访问对象模式代码实现
上面的 UML 类图是以用户为模型对象进行说明的,这里我们用代码实现下:
UserDAO 数据访问接口
/**
* DAO接口
* @author Administrator
*
*/
public interface UserDAO {
/**
* 新增
* @param user
* @return
*/
User addUser(User user);
/**
* 查询
* @return
*/
List<User> getUsers();
/**
* 查询单条
* @param id
* @return
*/
User getUserById(Integer id);
/**
* 删除
* @param id
* @return
*/
boolean deleteUserById(Integer id);
/**
* 修改
* @param user
* @return
*/
User updateUser(User user);
}
UserDAO 访问接口实现类
/**
* UserDAO的实现类
* @author Administrator
*
*/
public class UserDAOImpl implements UserDAO{
private List<User> userList = new ArrayList<User>();
public User addUser(User user) {
userList.add(user);
return user;
}
public List<User> getUsers() {
userList.stream().forEach(System.out::println);
System.out.println();
return userList;
}
public User getUserById(Integer id) {
return userList.stream().filter(user -> user.getUserId() == id).findFirst().get();
}
public boolean deleteUserById(Integer id) {
Iterator<User> iterator = userList.iterator();
while (iterator.hasNext()) {
User user = (User) iterator.next();
if (user.getUserId() == id) {
iterator.remove();
}
}
return true;
}
public User updateUser(User user) {
for (User u : userList) {
if (u.getUserId() == user.getUserId()) {
userList.set(userList.indexOf(u), user);
}
}
return user;
}
}
User 实体类
/**
* 用户实体类
* @author Administrator
*
*/
import java.util.Date;
public class User {
private Integer userId;
private String userName;
private int userAge;
private Date userBirth;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getUserAge() {
return userAge;
}
public void setUserAge(int userAge) {
this.userAge = userAge;
}
public Date getUserBirth() {
return userBirth;
}
public void setUserBirth(Date userBirth) {
this.userBirth = userBirth;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Integer userId;
private String userName;
private int userAge;
private Date userBirth;
public Builder userId(Integer userId) {
this.userId = userId;
return this;
}
public Builder userName(String userName) {
this.userName = userName;
return this;
}
public Builder userAge(int userAge) {
this.userAge = userAge;
return this;
}
public Builder userBirth(Date userBirth) {
this.userBirth = userBirth;
return this;
}
public User build() {
User user = new User();
user.setUserAge(userAge);
user.setUserBirth(userBirth);
user.setUserId(userId);
user.setUserName(userName);
return user;
}
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + ", userAge=" + userAge + ", userBirth=" + userBirth
+ "]";
}
}
测试类
public static void main(String[] args) {
UserDAO userDAO = new UserDAOImpl();
userDAO.addUser(User.builder().userId(1).userAge(23).userBirth(new Date()).userName("caiya").build());
userDAO.addUser(User.builder().userId(2).userAge(24).userBirth(new Date()).userName("laowang").build());
userDAO.addUser(User.builder().userId(3).userAge(26).userBirth(new Date()).userName("lily").build());
userDAO.getUsers();
userDAO.deleteUserById(1);
userDAO.getUsers();
userDAO.updateUser(User.builder().userId(3).userAge(40).userBirth(new Date()).userName("修改后的").build());
userDAO.getUsers();
}
输出结果如下:
User [userId=1, userName=caiya, userAge=23, userBirth=Sun Dec 22 11:40:45 CST 2019]
User [userId=2, userName=laowang, userAge=24, userBirth=Sun Dec 22 11:40:45 CST 2019]
User [userId=3, userName=lily, userAge=26, userBirth=Sun Dec 22 11:40:45 CST 2019]
User [userId=2, userName=laowang, userAge=24, userBirth=Sun Dec 22 11:40:45 CST 2019]
User [userId=3, userName=lily, userAge=26, userBirth=Sun Dec 22 11:40:45 CST 2019]
User [userId=2, userName=laowang, userAge=24, userBirth=Sun Dec 22 11:40:45 CST 2019]
User [userId=3, userName = 修改后的,userAge=40, userBirth=Sun Dec 22 11:40:45 CST 2019]
优缺点
数据访问对象模式的优点:
- 业务层和数据持久层分离,减轻系统耦合度;
- 数据访问对象单独抽离出来,可以适配各种底层持久化类型,提高系统的拓展性。
数据访问对象模式的缺点: - 每添加一个实体类,就必须添加一套 DAO 接口和一套 DAO 实现类,会导致代码重复臃肿(可以借助类似 JPA、Mybatis Plus 这种 ORM 框架可以自动帮我们实现 DAO 的实现类,这样一来只需要定义 DAO 接口即可)。
应用场景
数据访问对象模式的应用场景如下:
- DAO 的引入,帮助我们实现对持久化层的操作,只关心业务逻辑;
- 一切和持久化层打交道的应用场景都会感受到数据访问对象模式的影子,有数据访问就会有 DAO 的存在。
使用实例
这里我们以 Spring Data JPA 实现一套业务接口,感受下 DAO 模式带来的魅力,首先是新建 Spring Boot Web 项目,pom 依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
application.yml 配置
server:
port: 8087
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
jpa:
hibernate:
ddl-auto: update
show-sql: true
用户实体 UserEntity
@Entity(name = "sys_user")
@Data
public class UserEntity {
@Id
@GeneratedValue
@Column(name = "id", length = 32)
private Long id;
@Column(length = 50)
private String name;
@Column(length = 3)
private Integer age;
}
UserDAO 层接口
public interface UserDao extends JpaRepository<UserEntity, Long> {
/**
* 根据姓名查询
* @param name
* @return
*/
List<UserEntity> findByNameLike(String name);
}
这里我们要说明下,为什么我们这里只写了一个接口,因为继承 JpaRepository 后, JPA 会自动帮我们实现很多常用的 DAO 接口以及 DAO 实现类,感兴趣的同学可以去了解下 JPA。
UserService 实现类
@Service
public class UserService {
@Autowired
private UserDao userDao;
/**
* 插入
* @param userEntity
*/
public void addUser(UserEntity userEntity) {
userDao.save(userEntity);
}
/**
* 修改
* @param userEntity
*/
public void updateUser(UserEntity userEntity) {
userDao.save(userEntity);
}
/**
* 删除
* @param id
*/
public void deleteUserById(Long id) {
userDao.deleteById(id);
}
/**
* 查询所有
* @return
*/
public List<UserEntity> findAll() {
return userDao.findAll();
}
/**
* 查询单个
* @param id
* @return
*/
public UserEntity findUserById(Long id) {
return userDao.findById(id).get();
}
/**
* 根据姓名查询
* @param name
* @return
*/
public List<UserEntity> findUsersByName(String name) {
return userDao.findByNameLike(name);
}
}
可以看到,UserService 引用了 UserDao 进行持久化操作,UserService 就是我们的业务逻辑处理层,UserDAO 默认实现了类似 findById、deleteById、save 等很多 JPA 自动帮我们实现的接口,十分方便。
UserController 控制器层
@RestController
@RequestMapping("/users")
public class UserController {
@Resource
private UserService userService;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Object addUser(@RequestBody UserEntity user) {
userService.addUser(user);
return user;
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Object deleteUser(@PathVariable(name = "id", required = true) Long id) {
userService.deleteUserById(id);
return null;
}
@PutMapping
@ResponseStatus(HttpStatus.NO_CONTENT)
public Object updateUser(@RequestBody UserEntity user) {
userService.updateUser(user);
return null;
}
@GetMapping
public Object findAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public Object findUserById(@PathVariable(name = "id", required = true) Long id) {
return userService.findUserById(id);
}
@GetMapping("/findUsersWithName")
public Object findAllUsersByName(String name) {
return userService.findUsersByName(name);
}
}
总结
DAO 模式的引入,使得我们业务层和数据访问层解耦,再加上现在层出不穷的 ORM 框架,数据访问这块可以说是十分便捷,类似 JPA 这种只需要定义几个接口,JPA 就能自动帮我们实现持久化操作,可以说是很普惠了。