JPA基础用法:JPA在springboot中的使用_子书少卿的博客-CSDN博客
目录
问题引出
姿势一、Query by Example
姿势二、Specification
姿势三、entityManager
姿势四、QueryDSL
初使用jpa时,一直使用jpql(原生sql)和规定方法名的实行来实现业务,在特定的场景下需要用到动态查询条件时,也是直接使用jpsql的形式来声明动态条件,通过数据库的动态条件来实现;如下实例:
@Query(value = " SELECT * FROM water.testjpa " +
"WHERE name = ?1 " +
"and if(?2 != '', age = ?2, 1=1)", nativeQuery = true)
List test(String name, String age);
通过动态传入不同的参数值,来通过sql来实现动态条件的控制,sql打印如下
Hibernate: SELECT * FROM water.testjpa WHERE name = ? and if(? != '', age = ?, 1=1)
如果数据表中的数据量不大的情况下,sql执行无压力,但如果数据量达到百万级,这种通过条件判断形式的性能要比直接sql拼接具体条件性能差很多;
那么如果实现动态生成sql成了新的问题;
一种通过操作entity实例来控制动态条件的实现方式,操作简单但缺陷也很明显,往下看↓
使用限制:(缺陷)
1、只支持查询
不支持嵌套或分组的属性约束,如 age = ?1 or (age = ?1 and name= ?2)
只支持字符串的start/contains/ends/regex 及匹配和其他属性类型的精确匹配
不支持 :>、<、in、between等
使用:
1、repository接口-追加继承接口 QueryByExampleExecutor ;
@Repository
public interface TestQueryByExampleRepository extends
JpaRepository
// 泛型为操作的试题类型
,QueryByExampleExecutor {
}
接口原码及功能如下:
public interface QueryByExampleExecutor {
// 根据条件查询一条数据
Optional findOne(Example var1);
// 根据条件查询结果集
Iterable findAll(Example var1);
// 根据条件查询结果集,并排序
Iterable findAll(Example var1, Sort var2);
// 根据条件查询结果集,并分页
Page findAll(Example var1, Pageable var2);
// 计数
long count(Example var1);
// 判断值是否存在
boolean exists(Example var1);
}
2、service
@Slf4j
@Service
@AllArgsConstructor(onConstructor_ = {@Autowired})
@Transactional
public class QueryByExampleService {
/**
* 测试方法
*/
public void test(){
// 通过实体对象来实现查询条件的控制
JPAEntity jpaEntity = new JPAEntity();
// 根据自己的查询条件给实体赋值;
jpaEntity.setName("小黑");
jpaEntity.setTtt(LocalDate.now());
// 使用实体对象构建Example对象
Example of = Example.of(jpaEntity);
List all = testQueryByExampleRepository.findAll(of);
System.out.println(all);
}
TestQueryByExampleRepository testQueryByExampleRepository;
}
3、test
@SpringBootTest(classes = SpringbootJpaApplication.class)
@RunWith(SpringRunner.class)
public class QueryByExampleServiceTest {
@Resource
QueryByExampleService queryByExampleService;
@Test
public void test() {
queryByExampleService.test();
}
}
4、直接结果sql
select jpaentity0_.id as id1_0_, jpaentity0_.address as address2_0_, jpaentity0_.age as age3_0_, jpaentity0_.name as name4_0_, jpaentity0_.phone as phone5_0_, jpaentity0_.ttt as ttt6_0_ from testjpa jpaentity0_ where jpaentity0_.ttt=? and jpaentity0_.name=?
可以看到,sql中只有在构建实体是赋值的name和ttt条件
5、这种方式还支持匹配器,来对条件进行设置,改造下service,添加匹配器和匹配规则
@Slf4j
@Service
@AllArgsConstructor(onConstructor_ = {@Autowired})
@Transactional
public class QueryByExampleService {
/**
* 测试方法
*/
public void test(){
// 通过实体对象来实现查询条件的控制
JPAEntity jpaEntity = new JPAEntity();
// 根据自己的查询条件给实体赋值;
jpaEntity.setName("小黑");
jpaEntity.setTtt(LocalDate.now());
jpaEntity.setAddress("jing");
// 构造一个匹配器,通过匹配器对条件进行设置
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
// 设置忽略的条件(属性)---值为JPAEntity中的属性
.withIgnorePaths("ttt")
// 设置忽略大小写,如果不声明值,则对所有条件进行忽略大小写
// .withIgnoreCase("address")
// string类型的匹配,值使用String匹配器,可以通过枚举选择,这里使用的结尾匹配 注意:这个匹配是对查询所有条件的匹配
// .withStringMatcher(ExampleMatcher.StringMatcher.ENDING)
// 针对单个条件进行限制,两个值为:<对哪个条件>,<限制-一个函数式接口>
// address 字段,结尾匹配 ‘jing’
// .withMatcher("address",s->s.endsWith())
// 或者通过ExampleMatcher的静态方法,
// 注:当使用withMatcher对address进行设置时,上边的.withIgnoreCase("address")会失效,需要单独设置
.withMatcher("address", ExampleMatcher.GenericPropertyMatchers.endsWith().ignoreCase())
;
// 使用实体对象构建Example对象
Example of = Example.of(jpaEntity,exampleMatcher);
List all = testQueryByExampleRepository.findAll(of);
System.out.println(all);
}
TestQueryByExampleRepository testQueryByExampleRepository;
}
6、使用同一个test执行,直接结果如下
Hibernate: select jpaentity0_.id as id1_0_, jpaentity0_.address as address2_0_, jpaentity0_.age as age3_0_, jpaentity0_.name as name4_0_, jpaentity0_.phone as phone5_0_, jpaentity0_.ttt as ttt6_0_ from testjpa jpaentity0_ where (lower(jpaentity0_.address) like ?) and jpaentity0_.name=?
如果只是字段的精确匹配或简单的匹配可以这种方式;但如果要实现更加复杂的条件,继续往下看;
一种通过spring data jpa提供的实现Specification的形式来自定义查询规则的方式,实现相对复杂,但可以满足一般的使用场景;但也不完美,往下看↓
使用限制:(缺陷)
1、不支持分组和聚合函数;(如需要则只能通过玩entityManager了,既姿势三)
使用:
1、repository接口-追加继承接口 JpaSpecificationExecutor ;
@Repository
public interface TestSpecificationRepository extends
JpaRepository,
// 泛型为操作的试题类型
JpaSpecificationExecutor {
}
接口原码及功能如下:功能和QueryByExampleExecutor大体一致;
public interface JpaSpecificationExecutor {
Optional findOne(@Nullable Specification var1);
List findAll(@Nullable Specification var1);
Page findAll(@Nullable Specification var1, Pageable var2);
List findAll(@Nullable Specification var1, Sort var2);
long count(@Nullable Specification var1);
}
2、service
条件设置可以自己根据需在criteriaBuilder中寻找
@Slf4j
@Service
@AllArgsConstructor(onConstructor_ = {@Autowired})
@Transactional
public class SpecificationService {
public void test(){
// 模拟入参
JPAEntity jpaEntity = new JPAEntity();
jpaEntity.setId(2);
jpaEntity.setName("小黑");
LocalDate startTime = LocalDate.of(2023,4,17);
LocalDate endTime = LocalDate.of(2023,4,19);
//构建一个分页实例,用于分页
PageRequest pageRequest = PageRequest.of(0, 1);
// findAll方法的参数需要一个Specification接口,可以直接通过匿名类的形式来实现
Page all = testSpecificationRepository.findAll(new Specification() {
// root :用来获取列
// criteriaBuilder : 设置条件,如:> < in between 等
// criteriaQuery: 组合 where order by 等
@Override
public Predicate toPredicate(Root root, CriteriaQuery> criteriaQuery, CriteriaBuilder criteriaBuilder) {
// 获取列的时候要将结果枚举参数声明为字段对应的类型
// 获取列
Path id = root.get("id");
Path name = root.get("name");
Path ttt = root.get("ttt");
// 创建一个容器,存放要查询的条件
List list = new ArrayList<>();
// 如果id不为空
if (!ObjectUtils.isEmpty(jpaEntity.getId())) {
// id不为空,查询id 小于 传入值
// list.add(criteriaBuilder.le(id, jpaEntity.getId()));
// id不为空,查询id 大于 传入值
list.add(criteriaBuilder.ge(id, jpaEntity.getId()));
}
// 如果name不为空
if (!StringUtils.isEmpty(jpaEntity.getName())) {
// 如果name不为空 查询name 等于 传入值
list.add(criteriaBuilder.equal(name, jpaEntity.getName()));
}
// 如果起始时间不为空
if (!ObjectUtils.isEmpty(startTime) && !ObjectUtils.isEmpty(endTime)) {
// 如果起始时间不为空 查询ttt 介于两个值之间的值
list.add(criteriaBuilder.between(ttt, startTime, endTime));
}
// 汇总查询条件
Predicate and = criteriaBuilder.and(list.toArray(new Predicate[0]));
// 创建一个降序的排序,升序使用asc
// 降序可以按多个条件,使用List即可,参照构造方法
Order order = criteriaBuilder.desc(id);
// 将查询条件和排序添加组合返回
return criteriaQuery.where(and).orderBy(order).getRestriction();
}
// 设置分页实例
}, pageRequest);
// 结果中获取分页数据
List content = all.getContent();
System.out.println(content);
// 获取总条数 all中有分页中所有参数,根据需要获取
long totalElements = all.getTotalElements();
System.out.println(totalElements);
}
// 注入
TestSpecificationRepository testSpecificationRepository;
}
3、test
@SpringBootTest(classes = SpringbootJpaApplication.class)
@RunWith(SpringRunner.class)
public class SpecificationServiceTest {
@Resource
SpecificationService specificationService;
@Test
public void test() {
specificationService.test();
}
}
结果sql如下:
第一条sql
Hibernate: select jpaentity0_.id as id1_0_, jpaentity0_.address as address2_0_, jpaentity0_.age as age3_0_, jpaentity0_.name as name4_0_, jpaentity0_.phone as phone5_0_, jpaentity0_.ttt as ttt6_0_ from testjpa jpaentity0_ where jpaentity0_.id>=2 and jpaentity0_.name=? and (jpaentity0_.ttt between ? and ?) order by jpaentity0_.id desc limit ?
第二条sql
Hibernate: select count(jpaentity0_.id) as col_0_0_ from testjpa jpaentity0_ where jpaentity0_.id>=2 and jpaentity0_.name=? and (jpaentity0_.ttt between ? and ?)
注意:如果使用分页查询,如果返回的数据条数不小于分页的size则会再次执行count原条件查询总数,用于组装findAll的返回Page对象;
如果基于性能考虑只需要根据分页条件查询结果,不需要Page对象中的参数,则这种方式是不适合的;继续往下看↓
todo :待整理
通用查询框架,是一个三方的框架,暂不整理;