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接口的方法名称,正常情况下还是应该查看文档来看具体方法的使用。
这里有这么几个点:
- 直接注入自定义接口类即可,Spring会自动创建接口的实现类,而无需手动创建
- 可以去掉所有的
@Transactional
注解,DAO对象自带该功能 .findById()
略有不同,返回的是一个包装了具体类型的类,可以判断取到的结果是否是空,如果不为空,就可以通过.get()
方法获取给接口传入的泛型类。
之后重现启动项目,发现可以正常运行,Spring减少了编写实现类的方法,只需要看接口就可以了。如果项目里的数据表很多,那用起来也很方便,只需要再继承几个泛型的接口即可。