这里我们要用三种DAO实现方法来编写增删改查项目,大部分的内容之前都编写过,这里就是看如何使用Spring Boot来支撑这个项目和配置一些DAO方面的内容。

项目设计

了解了基础知识之后,就是用Spring Boot来写一个增删改查程序了,而且要用到REST API,设计如下:

方法 路径 操作
POST /api/employees 创建新对象
GET /api/employees 获取全部对象
GET /api/employees/{employeeId} 获取某个对象
PUT /api/employees 修改某个对象
DELETE /api/employees/{employeeId} 删除某个对象

我们的开发步骤如下:

  1. 创建数据库和表
  2. 使用Spring Boot Initializer来创建项目
  3. 编写查询功能
  4. 编写创建功能
  5. 编写修改功能
  6. 编写删除功能

在业务逻辑上,依然分为Controller,Service和DAO三层。所有的配置均采用Java和注解,不采用XML配置方式。

创建数据库

创建数据库的脚本如下:

CREATE DATABASE  IF NOT EXISTS `employee_directory`;
USE `employee_directory`;

DROP TABLE IF EXISTS `employee`;

CREATE TABLE `employee` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) DEFAULT NULL,
  `last_name` varchar(45) DEFAULT NULL,
  `email` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

INSERT INTO `employee` VALUES
	(1,'Leslie','Andrews','leslie@gmail.com'),
	(2,'Emma','Baumgarten','emma@sina.com'),
	(3,'Avani','Gupta','avani@163.com'),
	(4,'Yuri','Petrov','yuri@aliyun.com'),
	(5,'Juan','Vega','juan@v2ex.com');

这个表很简单。

创建Spring Boot项目

https://start.spring.io/,依次选择Maven项目,Java 8,Spring最新稳定版,输入自己的包名和项目名。然后在依赖里选择如下:

  1. Web
  2. JPA
  3. DevTools
  4. MySQL

就可以下载项目文件了。之后导入到Intellij里等待配置结束。

在导入项目的时候,如果遇到pom.xml文件中提示failed to read artifact descriptor错误,按照这里的方法,将Intellij的Maven中的Always update snapshots打开即可

注意此时还不能运行项目,如果运行的话,会被提示必须配置一个数据库驱动。

DAO层的创建

在之前的项目中,我们的DAO层使用了Spring提供的类,注入连接池,最终使用了Hibernate的Session Factory对象。在XML里或者Java配置类里写上数据库连接的内容。

现在无需这么麻烦,Spring Boot会自动从pom.xml中读取JDBC Driver(MySQL)和ORM(JPA依赖),我们只需要在application.properties中配置一下数据库的具体连接信息即可:

spring.datasource.url=jdbc:mysql://localhost:3306/employee_directory?useSSL=false&serverTimezone=UTC
spring.datasource.username=springstudent
spring.datasource.password=springstudent

除了这些基本信息,还有很多属性可以配置,看文档

在这里还有一个需要说明的是,Spring Boot会自动创建DataSourceEntityManager等Bean,可以直接将其注入到自己编写的DAO层中。

这里的EntityManager是一个Java Persistence API(JPA,Java持久化标准)的类,实际上就是一个包装了Hibernate的SessionFactory的对象。关于JPA也就是Java持久化标准,其实主要就来自于Hibernate,这一块将来还要去读一下《Java Persistence with Hibernate》这本书了。

在这里一次性把DAO学透,将采用三种方式来编写DAO:

  1. 使用EntityManager对象,但使用Hibernate的API
  2. 直接使用EntityManager对象和标准JPA API
  3. 使用Spring Data 的JPA实现

先来创建Employee类用于映射对应的数据表:

package cc.conyli.sbcrud.entity;

import javax.persistence.*;

@Entity
@Table(name = "employee")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;


    @Column(name = "first_name")
    private String firstName;


    @Column(name = "last_name")
    private String lastName;

    @Column(name = "email")
    private String email;

    public Employee(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    public Employee() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

Entity类已经很熟悉了,要设置空参构造方法和setter/getter方法。

然后是DAO的接口:

package cc.conyli.sbcrud.dao;

import cc.conyli.sbcrud.entity.Employee;

import java.util.List;

public interface EmployeeDAO {

    public List<Employee> findAll();
}

接口先只写一个方法。

使用Hibernate的API来编写DAO层

来编写基于Hibernate API的实现类:

package cc.conyli.sbcrud.dao;

import cc.conyli.sbcrud.entity.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.hibernate.*;
import javax.persistence.EntityManager;
import java.util.List;

@Repository
public class EmployeeDAOHibernateImpl implements EmployeeDAO {

    //注入EntityManager
    private EntityManager entityManager;


    //EntityManager会被Spring Boot自动创建
    @Autowired
    public EmployeeDAOHibernateImpl(EntityManager entityManager) {
        this.entityManager = entityManager;
    }


    @Override
    public List<Employee> findAll() {
        //获取Hibernate的SessionFactory
        Session currentSession = entityManager.unwrap(Session.class);

        Query<Employee> query = currentSession.createQuery("from Employee", Employee.class);

        List<Employee> employees = query.getResultList();

        return employees;
    }
}

这里要注意的是,可以通过EntityManager对象来获取hibernate的Session实现类。Hibernate里的Session和SessionFactory都是接口。

之后创建Query查询来获取列表结果,不过这里要注意的是,IDE提示Query已经过期。具体Hibernate的API,还要单独看一看。

之后为了验证是否OK,来编写一个Controller返回JSON。

package cc.conyli.sbcrud.rest;

import cc.conyli.sbcrud.dao.EmployeeDAO;
import cc.conyli.sbcrud.entity.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api")
public class EmployeeRESTController {

    private EmployeeDAO employeeDAO;

    @Autowired
    public EmployeeRESTController(EmployeeDAO employeeDAO) {
        this.employeeDAO = employeeDAO;
    }

    @GetMapping("/employees")
    public List<Employee> getEmployees() {
        return employeeDAO.findAll();
    }
}

启动项目,访问http://localhost:8080/api/employees,可以发现成功的显示除了JSON字符串。

之后我们把DAO接口的方法补全,编写好完整的HibernateDAO类和控制器方法,这样可以在后边顺利切换不同的DAO实现。

完整的DAO层

完整的DAO接口:

package cc.conyli.sbcrud.dao;

import cc.conyli.sbcrud.entity.Employee;

import java.util.List;

public interface EmployeeDAO {

    public List<Employee> findAll();

    public Employee findById(int id);

    public void save(Employee employee);

    public void deleteById(int id);
}

完整的HibernateDAO实现类:

package cc.conyli.sbcrud.dao;

import cc.conyli.sbcrud.entity.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.hibernate.*;

import javax.persistence.EntityManager;
import java.util.List;

@Repository
public class EmployeeDAOHibernateImpl implements EmployeeDAO {

    //注入EntityManager
    private EntityManager entityManager;


    //EntityManager会被Spring Boot自动创建
    @Autowired
    public EmployeeDAOHibernateImpl(EntityManager entityManager) {
        this.entityManager = entityManager;
    }


    @Override
    public List<Employee> findAll() {
        //获取Hibernate的SessionFactory
        Session currentSession = entityManager.unwrap(Session.class);

        Query<Employee> query = currentSession.createQuery("from Employee", Employee.class);

        return query.getResultList();
    }

    @Override
    public Employee findById(int id) {
        Session currentSession = entityManager.unwrap(Session.class);

        return currentSession.get(Employee.class, id);
    }

    @Override
    public void save(Employee employee) {
        Session currentSession = entityManager.unwrap(Session.class);

        currentSession.saveOrUpdate(employee);
    }

    @Override
    public void deleteById(int id) {
        Session currentSession = entityManager.unwrap(Session.class);
        Employee employee = currentSession.get(Employee.class, id);
        currentSession.delete(employee);
    }
}

完整的Service层

既然要三层,就创建Service类,也都是套路代码了,接口+实现类:

package cc.conyli.sbcrud.service;

import cc.conyli.sbcrud.entity.Employee;

import java.util.List;

public interface EmployeeService {

    public List<Employee> findAll();

    public Employee findById(int id);

    public void save(Employee employee);

    public void deleteById(int id);
}
package cc.conyli.sbcrud.service;

import cc.conyli.sbcrud.dao.EmployeeDAO;
import cc.conyli.sbcrud.entity.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class EmployeeServiceImpl implements EmployeeService {

    private EmployeeDAO employeeDAO;

    @Autowired
    public EmployeeServiceImpl(EmployeeDAO employeeDAO) {
        this.employeeDAO = employeeDAO;
    }

    @Override
    @Transactional
    public List<Employee> findAll() {
        return employeeDAO.findAll();
    }

    @Override
    @Transactional
    public Employee findById(int id) {
        return employeeDAO.findById(id);
    }

    @Override
    @Transactional
    public void save(Employee employee) {
        employeeDAO.save(employee);
    }

    @Override
    @Transactional
    public void deleteById(int id) {
        employeeDAO.deleteById(id);
    }
}

完整的Controller层

然后需要修改控制器,注入Service层的对象,完整的控制器如下,也都是套路代码:

package cc.conyli.sbcrud.rest;

import cc.conyli.sbcrud.entity.Employee;
import cc.conyli.sbcrud.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api")
public class EmployeeRESTController {

    private EmployeeService employeeService;

    @Autowired
    public EmployeeRESTController(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @GetMapping("/employees")
    public List<Employee> getEmployees() {
        return employeeService.findAll();
    }

    @GetMapping("/employees/{employeeId}")
    public Employee getEmployee(@PathVariable int employeeId) {
        Employee employee = employeeService.findById(employeeId);
        if (employee == null) {
            throw new RuntimeException("Employee id {{" + employeeId + "}} NOT FOUND");
        }
        return employee;
    }

    @PostMapping("/employees")
    public Employee addEmployee(@RequestBody Employee employee) {
        employee.setId(0);
        employeeService.save(employee);
        return employee;
    }

    @PutMapping("/employees")
    public Employee updateEmployee(@RequestBody Employee employee) {
        //无需先判断是否存在,否则会造成重复引用同一个数据,会报错。
        employeeService.save(employee);
        return employee;
    }

    @DeleteMapping("/employees/{employeeId}")
    public Employee deleteEmployee(@PathVariable int employeeId) {
        Employee targetEmployee = employeeService.findById(employeeId);
        if (targetEmployee == null) {
            throw new RuntimeException("Employee id {{" + employeeId + "}} NOT FOUND");
        }
        employeeService.deleteById(employeeId);
        return targetEmployee;
    }
}

之后用Postman测试,发现可以正常工作,我们就写好了基于Hibernate API DAO的增删改查项目。

之后我们要用另外两种方式来实现DAO,并且将项目切换到这两个DAO上。