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减少了编写实现类的方法,只需要看接口就可以了。如果项目里的数据表很多,那用起来也很方便,只需要再继承几个泛型的接口即可。