一、持久化Entity
首先我们创建一个UserRepository,继承接口JpaRepository
(该接口集成了所有常用接口方法),进行基础CURD的操作测试。
注意:
<User, Integer>
,分别代表:实体类以及实体类主键属性封装类型
UserRepository也就是对应常规开发模式下的DAO接口
public interface UserRepository extends JpaRepository<User, Integer> {
// 保存(添加)和更新
@Override
<S extends User> S save(S s);
// 根据Id删除
@Override
void deleteById(Integer integer);
// 根据Id查询
@Override
Optional<User> findById(Integer integer);
}
Service层实现
-
save:保存(添加)、更新
// 添加和更新实现 public Integer addUser(User user){ user.setName("张三"); userDao.save(user); user.setName(user.getName() + "_"); userDao.save(user); return user.getId(); }
测试结果:
Hibernate: insert into user (create_time, department_id, name) values (?, ?, ?)
Hibernate: update user set create_time=?, department_id=?, name=? where id=?
数据库返回结果:
分析:
在定义User Entity时,对id成员变量进行了@Id、@GeneratedValue(strategy=GenerationType.IDENTITY)标注,表名该成员变量对应数据库表中的主键,并且采用了主键自增的策略。
通过addUser方法save User对象时,没有setId(),主键为空情况下,插入user对象到数据库。看到的SQL语句打印也并没有Id字段,是因为主键自增策略的缘故,插入后主键Id赋值为4。当第二个save方法执行的时候,User对象已经有主键了。因此执行了更新操作,Id是通过user.getId获取的,因此最终结果name字段变为了 '张三_'
-
findById:根据Id进行查询
public User findUser(int id) { Optional<User> user = userDao.findById(id); return user; }
测试结果:
Hibernate: select user0_.id as id1_1_0_, user0_.create_time as create_t2_1_0_, user0_.department_id as departme4_1_0_, user0_.name as name3_1_0_, department1_.id as id1_0_1_, department1_.name as name2_0_1_ from user user0_ left outer join department department1_ on user0_.department_id=department1_.id where user0_.id=?
-
deleteById:根据Id进行删除
public boolean deleteUser(int id) { userDao.deleteById(id); return userDao.existsById(id); }
测试结果:false
Hibernate: delete from user where id=? Hibernate: select count(*) as col_0_0_ from user user0_ where user0_.id=?
总结下:通过简单的CURD测试发现,基本的CURD功能可以实现,也无需使用XML配置SQL。但是打印的执行SQL着实有点复杂,难道这就是自动生成的SQL该有的样子?当然针对非过多复杂查询的业务场景下是可以的,牵扯复杂查询之后,避免不了SQL语句的自定义。
二、Sort 排序
测试内置排序方法:
List findAll(Sort sort); 返回所有实体,按照指定排序(默认升序)返回。
Sort
对象的构造方法:
方法 | 说明 |
---|---|
public Sort(String… properties) | 按照指定的属性列表升序排序 |
public Sort(Direction direction, String… properties) | 按照指定属性列表排序,排序方式由Direction指定,Direction是枚举类,有Direction.ASC和Direction.DESC两类 |
public Sort(Order… orders) | Order 可以通过Order静态方法来创建 public static Order asc(String propertyName); public static Order desc(String propertyName); |
-
getAllUserList:返回用户实体集合
public List<User> getAllUserList() { // 按照Id降序查询 Sort sort = new Sort(Sort.Direction.DESC, "id"); List<User> all = userDao.findAll(sort); return all; }
测试结果:
Hibernate: select user0_.id as id1_1_, user0_.create_time as create_t2_1_, user0_.department_id as departme4_1_, user0_.name as name3_1_ from user user0_ order by user0_.id desc
返回截图:
三、Page和Pageable
Pageable
接口用于构造翻页查询,PageRequest
是其实现类
测试内置分页方法(含排序):
Page findAll(Pageable pageable); 返回实体列表,实体列表的offset、limit通过方法参数 Pageable 控制。
这里采用PageRequest中的如下构造方法实例化Pageable接口:
public PageRequest(int page, int size,
Direction direction, String... properties) {
this(page, size, Sort.by(direction, properties));
}
注意:
page 总是从0
开始,标识查询页,size标识每页期望的行数。
Spring Data 分页返回Page对象,Page对象提供了以下常用方法:
方法 | 说明 |
---|---|
int getTotalPages() | 总页数 |
long getTotalElements() | 返回元素总数 |
List getContent() | 返回此次查询的结果集 |
-
getAllUser(int page, int size):返回分页实体列表
public List<User> getAllUser(int page, int size) { PageRequest pageable = PageRequest.of(page, size, Sort.Direction.DESC, "id"); Page<User> pageObject = userDao.findAll(pageable); int totalPage = pageObject.getTotalPages(); long count = pageObject.getTotalElements(); return pageObject.getContent(); }
这里按照
size = 2
处理,一共5条数据,理论分3页。
测试前数据:
测试page0&size=2时的请求:
四、JPQL
Spring Data
可以通过查询的方法名和参数名自动构造 JPA OQL
查询。
JPA OQL
简称 JPQL
:[Java Persistence Query Language] Java持久化查询语言,旨在以面向对象表达式
语言的表达式,将SQL语法
和简单查询语义
绑定在一起。这种语言编写的查询是可移植的
,可以被编译成主流数据库服务器
上的SQL
。
其特征与原生SQL语句
类似,并且完全面向对象
,通过类名
和属性
访问,而不是表名
和表的属性
。
-
基于方法名字查询
public interface UserRepository extends JpaRepository<User, Integer> { public User findByName(String name); }
注意:方法名和参数名需要遵循一定的规则,
Spring Data JPA
才能自动转化为JPQL
。- 方法名通常包含多个实体属性(成员变量)用于查询,属性(成员变量)之间可以使用
AND
和OR
连接,也支持Between
、LessThan
、GreaterThan
、Like
; - 方法名可以以
findBy
、getBy
、queryBy
开头; - 查询结果可以排序,方法名需要包含
OrderBy+属性+Asc(Desc)
; - 可以通过
Top
、First
来限定查询结果集; - 一些特殊的参数可以出现在参数列表(签名)中,如:
Pageable
、Sort
。
举堆栗子:
-
根据名字查询,结果升序,且分页
Page<User> findByLastnameOrderByFirstNameAsc(String lastname, Pageable pageable);
-
查询满足条件的前10个用户
List<User> findFirst10ByLastname(String lastname, Sort sort);
-
使用And联合查询
List<User> findByLastnameAndFirstname(String lastname, String firstname);
-
使用Or查询
List<User> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
-
使用like查询,name中必须包含like中的
%
或者?
User findByNameLike(String name);
- 方法名通常包含多个实体属性(成员变量)用于查询,属性(成员变量)之间可以使用
-
Spring Data
支持的关键字关键字 例子 转化的JPQL片段 And findByLastnameAndFirstname …where x.lastname = ?1 and x.firstname = ?2 Or findByLastnameOrFirstname …where x.lastname = ?1 or x.firstname = ?2 Between findByStartDateBetween …where x.startDate between ?1 and ?2 LessThan findByAgeLessThan
findByAgeLessThanEqual…where x.age < ?1
…where x.age <= ?1GreaterThan findByAgeGreaterThan
findByAgeGreaterThanEqual…where x.age > ?1
…where x.age >= ?1After(DateTime) findByStartDateAfter …where x.startDate > ?1 Before(DateTime) findByStartDateBefore …where x.startDate < ?1 IsNull findByAgeIsNull
findByAge(Is)NotNull…where x.age is null
…where x.age not nullOrderBy findByAgeOrderByLastnameDesc …where x.age = ?1 order by x.lastname desc Not findByLastnameNot …where x.lastname <>?1 In findByAgeIn(Collection ages)
findByAgeNotIn(Collection ages)…where x.age in ?1
…where x.age not in ?1True findByActiveTrue
finByActiveFlase…where x.active = true
…where x.active = falseIgnoreCase findByFirstnameIgnoreCase …where UPPER(x.firstname) = UPPER(?1) Like findByFirstnameLike
findByFirstnameNotLike…where x.firstname like ?1
…where x.firstname not like ?1突然觉得这种JPA查询好像很高大上的样子,完全无SQL查询。真是厉害了我的JPQL!
五、@Query查询
@Query
注解允许在方法使用JPQL
-
JPQL
:面向对象和对象属性@Query(value = "select u from User u where u.name = ?1 and u.department.id = ?2") public User findUser(String name, Integer departmentId);
-
SQL
:面向数据表名和字段名 (nativeQuery = true)@Query(value = "select * from User where name = ?1 and department_id = ?2", nativeQuery = true) public User findUser(String name, Integer departmentId);
-
无论是
JPQL
还是SQL
,均支持命名参数
。例如:@Query(value = "select * from User where name = :name and department_id = :departmentId", nativeQuery = true) public User findUser(String name, Integer departmentId);
命名参数在上一章节JDBC Template 中也提到了,这样有利于SQL语句参数的和方法参数的映射,常规的索引方式固然方便,但是方法参数签名顺序变更后,SQL语句中的
?占位符
顺序也要变更。各有优劣。 -
Object[]数组
:非Entity
查询返回结果的替代方案例如:分组统计每个部门的用户数目
@Query(value = "select department_id, count(*) from User group by department_id", nativeQuery = true) public List<Object[]> queryUserCount();
-
@Modifying:JPQL更新、删除操作时搭配使用
@Modifying @Query(value = "update User u set u.name = ?1 where u.id = ?2") public int updateName(String name, Integer id);
六、Example 查询
上边提到了方法名构造JPQL
,@Query构造JPQL
进行查询,Example对象
的查询方案是指:根据实体(Entity)创建一个Example对象,Spring Data
通过 Example对象
来构造JPQL
。
public List<User> getByExample(String name) {
// 创建对象
User user = new User();
Department dept = new Department();
// 属性赋值
user.setName(name);
dept.setId(1);
// 组合对象user赋值
user.setDepartment(dept);
// Exanple.of静态方法构造example对象
Example<User> example = Example.of(user);
// 调用findAll进行查询
List<User> list = userDao.findAll(example);
return list;
}
总结下:通过构造实体查询条件user,结合Example.of(user)的静态方法构造Example对象,最后将Example对象作为参数传入JpaRepository(UserRepository)实现方法中进行执行查询。
非完全匹配查询
:ExampleMatcher
// ExampleMatcher提供了条件指定。下述代码条件为:名称Tom开头的所有用户
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("Tom", GenericPropertyMatchers.startsWith().ignoreCase());
Example<User> example = Example.of(user, matcher);
七、EntityManager:JPA提供的底层数据库访问接口
书中这里提到了一个例子:动态分页查询中使用EntityManager弥补Repository的不足。
这里就直接上代码说明了:
public Page<User> queryUser(Integer departmentId, Pageable page) {
//构造基础JPQL,这个JPQL将作为求和以及分页查询的基础JPQL
StringBuilder baseJpql = new StringBuilder("from User u where 1 = 1 ");
// 初始化查询参数Map
Map<String, Object> paras = new HashMap<String, Object>();
if (departmentId != null) {
// 添加附加添加参数,拼装JPQL
baseJpql.append("and u.department.id = :deptId");
// 配置附加查询参数 deptId
paras.put("deptId", departmentId);
}
//功能1:查询满足条件的总数
long count = getQueryCount(baseJpql, paras);
if (count == 0) {
return new PageImpl(Collections.emptyList(), page, 0);
}
//功能2:查询满足分页条件结果集
List list = getQueryResult(baseJpql, paras, page);
//返回结果
Page ret = new PageImpl(list, page, count);
return ret;
}
通用:查询参数配置方法:setQueryParameter()
private void setQueryParameter(Query query, Map<String, Object> paras) {
for (Entry<String, Object> entry : paras.entrySet()) {
query.setParameter(entry.getKey(), entry.getValue());
}
}
功能1:求和查询 getQueryCount()
private Long getQueryCount(StringBuilder baseJpql,
Map<String, Object> paras) {
// 创建Query 查询对象 结合传递的JPQL
Query query = em.createQuery("select count(1) " + baseJpql.toString());
// 调用通用查询参数配置方法(见上)
setQueryParameter(query, paras);
// 调用query对象的查询方法:getSingleResult()得到满足条件的记录总数
Number number = (Number) query.getSingleResult();
return number.longValue();
}
功能2:分页查询 getQueryResult()
private List getQueryResult(StringBuilder baseJpql, Map<String, Object> paras, Pageable page) {
// 创建Query 查询对象 结合传递的JPQL
Query query = em.createQuery("select u " + baseJpql.toString());
// 调用通用查询参数配置方法(见上上)
setQueryParameter(query, paras);
// 配置分页相关信息
query.setFirstResult((int) page.getOffset());
query.setMaxResults(page.getPageNumber());
// 调用查询方法得到list
List list = query.getResultList();
return list;
}
queryUser()
的逻辑梳理为:首先通过拼装好的JPQL进行求和查询
,根据返回count是否为 0,为 0 则返回一个空页(Collections.emptyList( )),非 0 便根据总数进行记录分页,分页参数对象作为参数传入分页查询
方法,最后返回分页结果数据集。