使用Spring Data JPA开发基于JPA的数据访问层
Spring Data JPA 环境搭建
参考https://www.cnblogs.com/wumingoo1/p/13414718.html
基本 CRUD
满足基本 CRUD 的接口
- 只要让 DAO 层接口 继承 JpaRepository,JpaSpecificationExecutor 接口,就自动具有了很多方法,并且不用实现
- JpaRepository<操作的实体类类型,实体类中主键属性的类型>:封装了基本CRUD操作
- JpaSpecificationExecutor<操作的实体类类型>:封装了复杂查询(分页)
- 注意将 接口写在 applicationContext.xml 配置文件中指定的包下
-
CustomerDao.java源码如下:
package cn.wm.dao; import cn.wm.domain.Customer; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> { }
基本 CRUD 操作测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class CRUDTest {
@Autowired
private CustomerDao customerDao;
// 保存
@Test
@Transactional
@Rollback(false)
public void test01() {
Customer c = new Customer();
c.setCustName("慕课网");
c.setCustIndustry("IT教育");
Customer save = customerDao.save(c);
System.out.println(save == c); // true,且处于 Managed 状态
save.setCustName("学堂在线");
}
// 更新 【注】:find + setter 也可以实现更新
@Test
@Transactional
@Rollback(false)
public void test02() {
Customer c = new Customer();
c.setCustId(1L);
c.setCustName("中国大学MOOC");
Customer save = customerDao.save(c);
// save 处于 Managed 状态,c 不处于 Managed 状态
save.setCustName("网易云课堂");
}
// 删除
@Test
@Transactional
@Rollback(false)
public void test03() {
customerDao.delete(15L);
}
// 立即查询一个
@Test
@Transactional
@Rollback(false)
public void test04() {
Customer c1 = customerDao.findOne(1L);
System.out.println("---");
System.out.println(c1);
/*
Hibernate: select ...
---
Customer{...}
*/
Customer c2 = customerDao.findOne(9999L);
System.out.println("---");
System.out.println(c2);
/*
Hibernate: select ...
---
null
*/
}
// 延迟查询一个
@Test
@Transactional
@Rollback(false)
public void test05() {
Customer c1 = customerDao.getOne(1L);
System.out.println("---");
System.out.println(c1);
/*
---
Hibernate: select ...
Customer{...}
*/
Customer c2 = customerDao.getOne(9999L);
System.out.println("---");
try {
System.out.println(c2);
} catch (EntityNotFoundException e) {
System.out.println("没查到");
}
/*
---
Hibernate: select ...
没查到
*/
}
// 查询所有
@Test
@Transactional
@Rollback(false)
public void test06() {
List<Customer> list = customerDao.findAll();
for (Customer c : list) {
c.setCustLevel("vip");
}
}
// 统计查询
@Test
public void test07() {
long count = customerDao.count();
System.out.println("count = " + count);
}
// 是否存在
@Test
public void test08() {
boolean exists1 = customerDao.exists(1L);
System.out.println("id 为 1 的记录存在:" + exists1);
boolean exists2 = customerDao.exists(9999L);
System.out.println("id 为 9999 的记录存在:" + exists2);
}
}
多字段多方式查询
给接口添加查询方法
基本 CRUD 里的方法只能根据 id 字段精确查询,要想根据多个字段多种查询方式,就需要自定义接口方法。但是只要这些接口方法的方法名和参数的类型和顺序满足要求,就可以不写实现直接使用这些方法。
- 单一字段精确查询: findBy + 属性名(首字母大写,后面的属性名首字母也要大写,省略)
- 单一字段模糊查询: findBy + 属性名 + Like
- 单一字段 IsNull 查询:findBy + 属性名 + IsNull
- 多字段多方式查询:findBy + 属性名 + 查询方式 + 条件连接符(And | Or)属性名 + 查询方式... (参数顺序要与属性顺序一致)
- 底层使用 JPQL 实现
-
添加的接口方法如下:
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> { Customer findByCustName(String custName); List<Customer> findByCustNameLike(String custName); List<Customer> findByCustLevelIsNull(); List<Customer> findByCustNameLikeAndCustLevel(String custName, String custLevel); }
findBy 方法测试
@Test
@Transactional
@Rollback(false)
public void test01() {
Customer c = customerDao.findByCustName("学堂在线");
c.setCustLevel("vip");
}
@Test
@Transactional
@Rollback(false)
public void test02() {
List<Customer> list = customerDao.findByCustNameLike("网易%");
for (Customer c : list) {
c.setCustAddress("广州");
}
}
@Test
@Transactional
@Rollback(false)
public void test03() {
List<Customer> list = customerDao.findByCustLevelIsNull();
for (Customer c : list) {
c.setCustLevel("normal");
}
}
@Test
@Transactional
@Rollback(false)
public void test04() {
List<Customer> list = customerDao.findByCustNameLikeAndCustLevel("网易%", "vip");
for (Customer c : list) {
c.setCustLevel(null);
}
}
JPQL 查询
给接口添加查询方法
通过 @Query 注解指定 JPQL 语句可以实现更自由的查询、更新操作。在 CustomerDao 接口中添加如下方法:
@Query("from Customer where custName like ? and custLevel = ?")
List<Customer> findJpql(String custName, String custLevel);
@Query("update Customer set custName=?2 where custId=?1")
@Modifying
void updateCustomer(Long custId, String custName);
JPQL 方法测试
@Test
@Transactional
@Rollback(false)
public void test01() {
List<Customer> list = customerDao.findJpql("网易%", "vip");
for (Customer c : list) {
c.setCustLevel("svip");
}
}
@Test
@Transactional
@Rollback(false)
public void test02() {
customerDao.updateCustomer(1L,"中国大学MOOC");
}
SQL 查询
给接口添加查询方法
@Query 注解除了可以指定 JPQL 语句,还可以通过 指定 SQL 语句可以实现查询操作,只需要让 @Query 注解的 nativeQuery 属性设置为 true 即可。在 CustomerDao 接口中添加如下方法:
@Query(value = "select * from cst_customer where cust_name like ? and cust_level = ?", nativeQuery = true)
List<Customer> findSql(String custName, String custLevel);
SQL 方法测试
@Test
@Transactional
@Rollback(false)
public void test01() {
List<Customer> list = customerDao.findSql("网易%", "svip");
for (Customer c : list) {
c.setCustLevel("vip");
}
}
Specification 动态查询
Specification 动态查询简介
JpaSpecificationExecutor 接口具有以下方法,这些方法会根据传入实参的不同生成不同的 SQL,并且都含有 Specification 参数,因而称使用它们的查询为 Specification 动态查询。CutomerDao 继承了 JpaSpecificationExecutor,这些方法不用自己实现,可以直接使用。
public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> spec);
}
Specification 动态查询测试
// 查询 客户名称为网易公开课 的客户
@Test
@Transactional
@Rollback(false)
public void test01() {
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//1. 获取比较的属性
Path<Object> custName = root.get("custName");
//2. 构造查询条件
Predicate p = cb.equal(custName, "网易公开课");
return p;
}
};
Customer c = customerDao.findOne(spec);
System.out.println(c);
c.setCustAddress("广州");
}
// 查询 客户名称为网易公开课 且 行业为教育 的客户
@Test
@Transactional
@Rollback(false)
public void test02() {
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//1. 构造 指定客户名为网易公开课 的查询条件
Path<Object> custName = root.get("custName");
Predicate p1 = cb.equal(custName, "网易公开课");
//2. 构造 指定行业为教育 的查询条件
Path<Object> custIndustry = root.get("custIndustry");
Predicate p2 = cb.equal(custIndustry, "教育");
//3. 构造 指定客户名为网易公开课 且 指定行业为教育 的查询条件
Predicate and = cb.and(p1, p2);
return and;
}
};
Customer c = customerDao.findOne(spec);
System.out.println(c);
c.setCustLevel("vip");
}
// 模糊查询 客户名称以网易开头 的客户
@Test
@Transactional
@Rollback(false)
public void test03() {
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//1. 获取比较的属性
Path<Object> custName = root.get("custName");
//2. 构造查询条件
Predicate like = cb.like(custName.as(String.class), "网易%");
return like;
}
};
List<Customer> list = customerDao.findAll(spec);
for (Customer c : list) {
System.out.println(c);
c.setCustLevel(null);
}
}
// 添加排序功能
@Test
@Transactional
@Rollback(false)
public void test04() {
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//1. 获取比较的属性
Path<Object> custName = root.get("custName");
//2. 构造查询条件
Predicate like = cb.like(custName.as(String.class), "网易%");
return like;
}
};
Sort sort = new Sort(Sort.Direction.DESC, "custId");
/**
* Sort(Sort.Direction.DESC, "custId")
* 第一个参数:排序方向
* 第二个参数:排序属性
*/
List<Customer> list = customerDao.findAll(spec, sort);
for (Customer c : list) {
System.out.println(c);
c.setCustLevel("normal");
}
}
// 添加分页功能
@Test
@Transactional
@Rollback(false)
public void test05() {
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//1. 获取比较的属性
Path<Object> custName = root.get("custName");
//2. 构造查询条件
Predicate like = cb.like(custName.as(String.class), "网易%");
return like;
}
};
Pageable pageable = new PageRequest(0,2);
/**
* PageRequest(int page, int size)
* 第一个参数:当前查询的页数(从0开始)
* 第二个参数:每页查询的数量
*/
Page<Customer> page = customerDao.findAll(spec, pageable);
/**
* Page 是 Spring Data 提供好的 pageBean
* page.getContent() 数据列表
* page.getTotalElements() 总记录数
* page.getTotalPages() 总页数
*/
System.out.println(page.getContent());
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
for (Customer c : page.getContent()) {
c.setCustLevel("ssvip");
}
}
// 模糊查询 客户名称以网易开头 的客户的记录数
@Test
public void test06() {
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//1. 获取比较的属性
Path<Object> custName = root.get("custName");
//2. 构造查询条件
Predicate like = cb.like(custName.as(String.class), "网易%");
return like;
}
};
long count = customerDao.count(spec);
System.out.println(count);
}