Spring 36 Spring BOOT - 另外两种方式实现DAO层

Spring 36 Spring BOOT - 另外两种方式实现DAO层

JPA DAO实现 使用JPA标准的DAO层有很大好处就是不依赖于具体供应商的实现,可以很方便的切换到其他也支持JPA的软件上去。 JPA API有点像原生的Hibernate API,也支持一种叫做JPQL的查询语言。为何两者很像,是因为Hibernate是实际上的JPA规范推动者。 这里简单的看

JPA DAO实现

使用JPA标准的DAO层有很大好处就是不依赖于具体供应商的实现,可以很方便的切换到其他也支持JPA的软件上去。 JPA API有点像原生的Hibernate API,也支持一种叫做JPQL的查询语言。为何两者很像,是因为Hibernate是实际上的JPA规范推动者。 这里简单的看一下两者增删改查方法的对比:
操作 Hibernate原生方法 JPA 方法
创建和保存新对象 sesson.save(...) entityManager.persist(...)
通过id获取对象 session.get(...)/load(...) entityManager.find(...)
获取对象列表 session.createQuery(...) entityManager.createQuery(...)
保存或者更新对象 session.saveOrUpdate(...) entityManager.merge(...)
删除对象 session.delete(...) entityManager.remove(...)
由于接口已经确定好了,直接来编写实现类:
package cc.conyli.sbcrud.dao;

import cc.conyli.sbcrud.entity.Employee;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

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

@Repository
public class EmployeeDAOJPAImpl implements EmployeeDAO {

    private EntityManager entityManager;

    //构造器注入
    public EmployeeDAOJPAImpl(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    @Transactional
    public List<Employee> findAll() {
        System.out.println("JPA API working...");
        return entityManager.createQuery("from Employee", Employee.class).getResultList();
    }

    @Override
    @Transactional
    public Employee findById(int id) {
        System.out.println("JPA API working...");

        return entityManager.find(Employee.class, id);
    }

    @Override
    @Transactional
    public void save(Employee employee) {
        System.out.println("JPA API working...");
        Employee targetEmployee = entityManager.merge(employee);
        //这一步不像Hibernate会自动将对象关联到session然后更新,所以要手动给传入的参数设置上id
        employee.setId(targetEmployee.getId());
    }

    @Override
    @Transactional
    public void deleteById(int id) {
        System.out.println("JPA API working...");
        Employee employee = entityManager.find(Employee.class, id);
        entityManager.remove(employee);
    }
}
这里要注意.save()方法,由于JPA没有Hibernate那种关联技术,在执行了merge操作之后,内存中的参数对象employee不会自动更新id,必须先取出刚才新创建或者更新后的对象,然后给参数对象设置id才行。这样REST API在返回内存中的对象时候才有正确的id 然后需要在Service层里切换一下DAO实现:
@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    public EmployeeServiceImpl(EmployeeDAOJPAImpl employeeDAO) {
        this.employeeDAO = employeeDAO;
    }
    ......
}
这里也可以不使用构造器,使用@Qualifier进行域注入。但这是不推荐的做法。 之后再启动项目,就可以发现每次操作都是使用JPA的API了。

Spring Data DAO实现

Spring Data DAO的理念与前两者有所不同,注意观察Hibernate和JPA的两个实现,很多代码其实很相似。如果我现在需要查询另外一个表映射的类比如Manager,可能需要重新创建一个DAO,将里边的Employee字样全部替换成Manager,这也是重复做样板代码。 Spring Data的理念是提供一个DAO实现,然后只需要将Entity类的类型和主键插入进来,利用这个实现提供的增删改查方法就可以进行操作了。只需要更换一下插入的Entity类的类型,就可以得到其他表的查询结果。 所以使用Spring Data JPA并不需要去编写增删改查代码,只需要定义Entity类和主键,根据文档说法,能节省70%的代码量。 当然,由于无需自己写方法,肯定要使用Spring Data JPA提供好的一些类或者接口,我们必须先编写一个接口继承JpaRepository接口,指定对应的Entity类型和id的类型,相关的文档可以看这里。 先来看接口:
package cc.conyli.sbcrud.dao;

import cc.conyli.sbcrud.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeSpringJPA extends JpaRepository<Employee, Integer> {

}
内部代码无需编写,这个接口主要是明确Entity类和id的类型。而且神奇的是实现类也无需编写。 然后直接可以修改Service层来使用符合这个接口的对象:
package cc.conyli.sbcrud.service;

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

import java.util.List;
import java.util.Optional;

@Service
public class EmployeeServiceImpl implements EmployeeService {

    private EmployeeSpringJPA employeeSpringJPA;

    @Autowired
    public EmployeeServiceImpl(EmployeeSpringJPA employeeSpringJPA) {
        this.employeeSpringJPA = employeeSpringJPA;
    }

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

    @Override
    public Employee findById(int id) {
        Optional<Employee> result = employeeSpringJPA.findById(id);
        Employee employee;
        if (result.isPresent()) {
            employee = result.get();
        } else {
            throw new RuntimeException("NOT FOUND {{" + id + "}} NOT FOUND");
        }
        return employee;
    }

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

    @Override
    public void deleteById(int id) {
        employeeSpringJPA.deleteById(id);
    }
}
实际上在最开始的时候,我们就刻意采用了符合Spring Data JPA接口的方法名称,正常情况下还是应该查看文档来看具体方法的使用。 这里有这么几个点:
  1. 直接注入自定义接口类即可,Spring会自动创建接口的实现类,而无需手动创建
  2. 可以去掉所有的@Transactional注解,DAO对象自带该功能
  3. .findById()略有不同,返回的是一个包装了具体类型的类,可以判断取到的结果是否是空,如果不为空,就可以通过.get()方法获取给接口传入的泛型类。
之后重现启动项目,发现可以正常运行,Spring减少了编写实现类的方法,只需要看接口就可以了。如果项目里的数据表很多,那用起来也很方便,只需要再继承几个泛型的接口即可。
LICENSED UNDER CC BY-NC-SA 4.0
Comment