最近学习了模仿天猫商城项目,对springboot项目中的JPA做了一些了解,做一些必要的笔记,一来是对自己学习的知识的巩固,二来对有同样问题的人有参考作用
文章目录
一 概述
1 概念
JPA是一种规范,而hibernate是实现这种规范的底层实现,spring data 对持久化接口JPA再抽象一层,针对持久层业务再进一步简化。这样开发者就连持久层的业务逻辑也不用写了,只要按照spring data的命名规范,写好接口继承即可。
2 使用场景
根据我自己的开发经验(其实也谈不上经验,本人小菜一匹 )
UserDao extends JpaRepository<User,Integer>
需求难易程度 | 选用方式 | 描述 | 举例 |
---|---|---|---|
简单 | JPA自带的方法 | 能够解决大部分的需求 (CURD) | userDao.save(user) |
复杂 | 自定义 @Query() | 主要用来解决复杂查询 | @Query(nativeQuery = true,value = “select r.name from user_roles urs inner join role r on urs.uid = ?1 and urs.rid = r.id;”) |
特殊需求 | 实现接口Specification | 一些非常复杂或自定义的查询 | 多条件动态查询(下文有代码) |
二 主要接口
1 Repository
根接口,其他接口继承该接口。源码如下:
/**
* Central repository marker interface. Captures the domain type to manage as well as the domain type's id type. General
* purpose is to hold type information as well as being able to discover interfaces that extend this one during
* classpath scanning for easy Spring bean creation.
* <p>
* Domain repositories extending this interface can selectively expose CRUD methods by simply declaring methods of the
* same signature as those declared in {@link CrudRepository}.
*
* @see CrudRepository
* @param <T> the domain type the repository manages
* @param <ID> the type of the id of the entity the repository manages
* @author Oliver Gierke
*/
public interface Repository<T, ID extends Serializable> {
}
基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。
2 CrudRepository
基本的增删改查接口,提供了最基本的对实体类的添删改查操作,其源码如下:
/** Interface for generic CRUD operations on a repository for a specific type.*/
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
/* 保存单个实体:实体存在时进行update操作;实体不存在时进行insert操作 */
<S extends T> S save(S entity);
/* 保存实体集合*/
<S extends T> Iterable<S> save(Iterable<S> entities);
/* 根据id查找实体.*/
T findOne(ID id);
/** 根据id判断实体是否存在*/
boolean exists(ID id);
/* 查询所有实体,不用或慎用! */
Iterable<T> findAll();
/* 根据id集合查找实体集合*/
Iterable<T> findAll(Iterable<ID> ids);
/* 查询实体数量*/
long count();
/** 根据id删除实体 */
void delete(ID id);
/** 删除给定的实体*/
void delete(T entity);
/** 删除给定的实体集合*/
void delete(Iterable<? extends T> entities);
/* 删除所有实体,不用或慎用! */
void deleteAll();
}
继承 Repository,实现了一组 CRUD 相关的方法 。
3 PagingAndSortingRepository
增加了分页和排序操作,源码示例如下:
/**
* Extension of {@link CrudRepository} to provide additional methods to retrieve entities using the pagination and
* sorting abstraction.
*/
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
/* 根据Sort返回所有实体排序过后的集合*/
Iterable<T> findAll(Sort sort);
/* 根据Pageable返回当前页的数据信息*/
//通常会使用PageRequest
Page<T> findAll(Pageable pageable);
}
继承 CrudRepository,实现了一组分页排序相关的方法,不过没法实现带查询条件的分页 。
4 JpaRepository
添加了批量操作,并从写了了父接口一些方法的返回类型,源码示例如下:
@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
/*
*查询所有实体
*/
List<T> findAll();
/*
* 根据sort查找所有实体---返回的实体集合是排序过的
*/
List<T> findAll(Sort sort);
/*
*根据id集合查找对应的实体集合
*/
List<T> findAll(Iterable<ID> ids);
/*
* 保存实体集合
*/
<S extends T> List<S> save(Iterable<S> entities);
/**
* 刷新缓存,与数据库同步
*/
void flush();
/* 保存实体并立即刷新缓存,即强制执行持久化*/
<S extends T> S saveAndFlush(S entity);
/**
* 删除一个实体集合
*/
void deleteInBatch(Iterable<T> entities);
/**
* 删除所有的实体,禁用或慎用!
*/
void deleteAllInBatch();
/*根据id获取一个实体
*/
T getOne(ID id);
/* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
*/
@Override
<S extends T> List<S> findAll(Example<S> example);
/* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
*/
@Override
<S extends T> List<S> findAll(Example<S> example, Sort sort);
}
继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法 。
5 JpaSpecificationExecutor
用来做动态查询,可以实现带查询条件的分页,源码示例如下:
/*Interface to allow execution of {@link Specification}s based on the JPA criteria API.
*/
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);
}
不属于Repository体系,实现一组 JPA Criteria 查询相关的方法 。
6 Specification
Specification封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象。
Spring Data JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件即可。
源码示例如下:
public interface Specification<T> {
/**
* Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for the given
* {@link Root} and {@link CriteriaQuery}.
*
* @param root
* @param query
* @return a {@link Predicate}, may be {@literal null}.
*/
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
三 应用举例
1 简单JPA自带方法
- dao层
public interface UserDao extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> { List<User> findAllByName(String userName,Pageable pageable); }
- service层
//添加用户 public void add(User user){ userDao.save(user); } //根据用户名进行分页查找 Public List<User> findAllByUserName(String userName,Pageable pageable){ Return userDao. findAllByName(username,pageable); }
仅仅在UserDao接口中定义抽象方法 List findAllByName(String userName,Pageable pageable); JPA会根据我们自定义的方法名进行解析和拆分,得知该方法的功能。JPA对自定义方法名的解析规则如下:
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
2 复杂自定义@Query
- dao层
public interface UserDao extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> { List<User> findAllByName(String userName,Pageable pageable); @Query(nativeQuery = true,value = "select r.name from user_roles urs inner join role r on urs.uid = ?1 and urs.rid = r.id;") List<String> getRolesByUser(Integer uid); }
- service层
//根据userId查询用户对应的角色 public List<String> getRolesByUser(Integer uid){ return userDao.getRolesByUser(uid); }
3 特殊需求实现接口Specification
- dao层
public interface UserDao extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> { List<User> findAllByName(String userName,Pageable pageable); @Query(nativeQuery = true,value = "select r.name from user_roles urs inner join role r on urs.uid = ?1 and urs.rid = r.id;") List<String> getRolesByUser(Integer uid); //多条件动态查询 List<User> findAll(Specification mySpec); }
- service层
/* 动态查询的条件: 条件1:性别为 男; 条件2:年龄在25-35之间; 条件3:吴国人; */ public List<User> findByDynamicCases() { return userDao.findAll( new Specification() { @Override public Predicate toPredicate(Root root, CriteriaQuery<?> query, CriteriaBuilder cb) { Predicate predicate1,predicate2,predicate3; Path sex = root.get("sex"); Path age = root.get("age"); Path address = root.get("address"); predicate1 = cb.like(sex,"男"); predicate2 = cb.between(age,25,35); predicate3 = cb.equal(address,"吴国"); query.where(predicate1,predicate2,predicate3); return null; } }); }
匿名内部类有几个对象需要解释下:
- Specification:规则、标准。该对象主要是告诉JPA查询的过滤规则是什么。
- Predicate:谓语、断言。该对象主要是定义具体的判断条件。如predicate1 = cb.like(sex,“男”);即判断条件为性别为男性。
- Root: Root root就是定义引用root指向Student的包装对象。Path sex = root.get(“sex”);即通过root来获取Student的具体属性。
- CriteriaQuery:查询条件的组装。query.where(predicate1,predicate2,predicate3);表示按条件predicate1 and predicate2 and predicate3进行组合条件查询。
- CriteriaBuilder:用来构建CritiaQuery的构建器对象;如:predicate2 = cb.between(age,25,35);表示判断条件为Student.age between 25 and 35;
四 总结
如有错误恳请指正,如有侵权请联系我删除
参考文章: Spring Data JPA入门简解与XML配置实现
Springboot整合JPA以及动态条件查询的实现