SprinBoot 系列文章:
Spring Boot入门之Hello Spring Boot
SpringBoot 配置多个JdbcTemplate
SpringBoot 整合Mybatis
CAS统一登录认证(3): CAS 客户端接入实践
SpringBoot 整合Mail实现邮件发送
数据库连接池优化配置(druid,dbcp,c3p0)
SpringBoot+SpringSecurity+mysql实现认证与授
SpringBoot+Spring Security基于内存用户认证
SpringBoot+WebSocket在线聊天室、消息推送
SpringBoot+SpringData JPA操作Mysql数据库
SpringBoot热部署值devtools配置
Spring Boot 资源文件属性配置
Spring Boot Server等内容的配置
Spring Boot + FreeMarker模板
SpringBoot+thymeleaf模板
SpringBoot +JDBC连接Mysql数据库
Zipkin分布式任务追踪
SpringBoot应用部署到外置Tomcat
Spring Boot + Swagger2 自动生成api接口文档
SpringBoot整合Shiro安全框架
SpringBoot+CAS Client 实现单点登录
SpringBoot 整合MyBatis-Plus
SpringBoot + validation 接口参数校验
Springboot+Redis 实现API接口防刷限流
ShardingSphere-ShardingJdbc 数据分片(分库、分表)
ShardingSphere-ShardingJdbc 读写分离
ShardingSphere-ShardingJdbc 数据脱敏
springboot+sms 集成腾讯云短信平台
SpringBoot+RabbitMQ 实现消息队列
快速从零搭建一个SpringBoot Web项目
从零快速搭建一个SpringBoot Web项目
SpringBoot+ElasticSearch 实现全文检索
访问ElasticSearch的几种方式
SpringBoot + Activiti 工作流引擎(一、基本概念与环境搭建)
SpringBoot + Activiti 工作流引擎(二、流程&任务操作)
SpringBoot 定时任务 实现方式
SpringBoot + EhCache实现本地缓存
SpringBoot + Redis 实现分布式缓存
一、什么是JPA
JPA本身并不是一种框架,是一种规范,其全称是Java Persistence API,是是Sun官方提出的Java持久化规范,而他的出现主要是为了简化现有的持久化开发工作和整合ORM技术,并且其是在充分吸收了现有Hibernate,TopLink,JDO等ORM框架的基础上发展而来的,具有易于使用,伸缩性强等优点。
Spring Data JPA的官网对介绍:Spring Data JPA是Spring Data系列的一部分,可以轻松实现基于JPA的存储库。该模块处理对基于JPA的数据访问层的增强的支持。这使得使用数据访问技术构建Spring供电的应用程序变得更加容易。
Spring Data JPA旨在通过减少实际需要的数量来显着提高数据访问层的实现。作为开发人员,您编写存储库接口(包括自定义查找器方法),Spring将自动提供实现。
主要特点:
- 基于Spring和JPA构建存储库的复杂支持
- 支持Querydsl谓词,从而支持类型安全的JPA查询
- 域类的透明审计
- 分页支持,动态查询执行,整合自定义数据访问代码的能力
- @Query引导时间验证注释查询
- 支持基于XML的实体映射
二、Spring Data JPA 方法介绍
- 常用规则速查
关键字 | 示例 | 说明 | JPQL片段示例 |
---|---|---|---|
|
| 并且 |
|
|
| 或 |
|
|
| 等于 |
|
|
| 两者之间 |
|
|
| 小于 |
|
|
| 小于等于 |
|
|
| 大于 |
|
|
| 大于等于 |
|
|
| 之后(时间) |
|
|
| 之前(时间) |
|
|
| 等于Null |
|
|
| 不等于Null |
|
|
| 模糊查询。查询件中需要自己加 % |
|
|
| 不在模糊范围内。查询件中需要自己加 % |
|
|
| 以某开头 |
|
|
| 以某结束 |
|
|
| 包含某 |
|
|
| 排序 |
|
|
| 不等于 |
|
|
| 某范围内 |
|
|
| 某范围外 |
|
|
| 真 |
|
|
| 假 |
|
|
| 忽略大小写 |
|
- Spring Data 解析方法名--规则说明
规则描述
按照Spring data 定义的规则,查询方法以find|read|get开头(比如 find、findBy、read、readBy、get、getBy),涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
如果方法的最后一个参数是 Sort 或者 Pageable 类型,也会提取相关的信息,以便按规则进行排序或者分页查询。
举例说明
比如 findByUserAddressZip()。框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,详细规则如下(此处假设该方法针对的域对象为 AccountInfo 类型):
- 先判断 userAddressZip (根据 POJO 规范,首字母变为小写,下同)是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
- 从右往左截取第一个大写字母开头的字符串(此处为 Zip),然后检查剩下的字符串是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为 AccountInfo 的一个属性;
- 接着处理剩下部分( AddressZip ),先判断 user 所对应的类型是否有 addressZip 属性,如果有,则表示该方法最终是根据 "AccountInfo.user.addressZip" 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 "AccountInfo.user.address.zip" 的值进行查询。
可能会存在一种特殊情况,比如 AccountInfo 包含一个 user 的属性,也有一个 userAddress 属性,此时会存在混淆。读者可以明确在属性之间加上 "_" 以显式表达意图,比如 "findByUser_AddressZip()" 或者 "findByUserAddress_Zip()"。(强烈建议:无论是否存在混淆,都要在不同类层级之间加上"_" ,增加代码可读性)
当查询条件为null时。
举例说明如下:
- 实体定义:对于一个客户实体Cus,包含有name和sex,均是String类型。
- 查询方法定义:List<Cus> findByNameAndSex(String name,String sex);
- 使用时:dao.findByNameAndSex(null, "男");
- 后台生成sql片断:where (cus0_.name is null) and cus0_.sex=?
- 结论:当查询时传值是null时,数据库中只有该字段是null的记录才符合条件,并不是说忽略这个条件。也就是说,这种查询方式,只适合于明确查询条件必须传的业务,对于动态查询(条件多少是动态的,例如一般的查询列表,由最终用户使用时决定输入那些查询条件),这种简单查询是不能满足要求的。
排序
List<Cus> findBySexOrderByName(String sex); //名称正序(正序时,推荐此方式,简单)
List<Cus> findBySexOrderByNameAsc(String sex); //名称正序(效果同上)
List<Cus> findBySexOrderByNameDesc(String sex); //名称倒序
结果限制
/**
* 根据父ID,得到排序号最大的bo。
* 用于预计算新资源的排序号。
*/
Resource findFirstByFather_idOrderByOrderNumDesc(Long fatherId);
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
Spring Data JPA使用findAllOrderBy坑
List<ActivityEntity> findAllOrderByCreateTimeDesc();
可以看到,我希望在一个表中查询所有的数据,并按照createTime
这个字段进行排序,这样的写法看似正确的,会报错
其实,正确的写法是:
List<ActivityEntity> findAllByOrderByCreateTimeDesc();
4、计数
Long count();
Long countByLastname(String lastname);
5、删除
void deleteByProject_Id(Long id);
void deleteByProject_Cus_id(Long id);
三、复杂条件查询-多条件动态查询
刚开始使用springdata的时候,只会用findByName这样的简单查询,这样写dao层确实非常的快,但是在我们做筛选功能的时候,这样的查询似乎很难满足我们的需求,但是都已经用上的springdata又不想再去写mybatis这样在xml里面判断是否为Null。
我们首先会想到可以使用@Query注解,这种方式可以直接在Repository里面写sql,但是这种方式的问题就是太麻烦了,而且非常容易出错,扩展性也很差,还不如直接用mybatis。
3.1 Example
用example可以最快速的完成支持所有参数的筛选功能,像这样的代码:
/**
* 查询用户列表
*
* @return 用户列表
*/
@GetMapping("listByKeyWord")
public String list(ModelMap model, OyUser user) {
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching()
//模糊查询匹配开头,即{username}% .withMatcher("nickName", ExampleMatcher.GenericPropertyMatchers.startsWith())
.withMatcher("nickName", ExampleMatcher.GenericPropertyMatchers.contains())
//全部模糊查询,即%{address}%
.withMatcher("name" ,ExampleMatcher.GenericPropertyMatchers.contains())
.withMatcher("account" ,ExampleMatcher.GenericPropertyMatchers.contains())
//忽略字段,即不管password是什么值都不加入查询条件
.withIgnorePaths("sex","age")
// 忽略属性:id。因为是基本类型,需要忽略掉
.withIgnorePaths("id");
//创建实例
Example<OyUser> example = Example.of(user ,matcher);
//查询
List<OyUser> userList = oyUserRepository.findAll(example);
model.put("users",userList);
return "user";
}
Example会将为null的字段自动过滤掉,不会作为筛选条件,ExampleMatch是为了支持一些稍微复杂一些的查询,比如如果有int类型的id就需要用withIgnorePaths()忽略掉,因为Int类型默认为0,而不是Null。
如果只是简单的字符串匹配的话,可以直接用:
Example<User> example = Example.of(user); 来构造Example。
限制
- 属性不支持嵌套或者分组约束,比如这样的查询 firstname = ?0 or (firstname = ?1 and lastname = ?2)
- 灵活匹配只支持字符串类型,其他类型只支持精确匹配。
3.2 Criteria
Criteria查询是Jpa中最强的使用方式了,所有的场景应该都能完成。
首先Repository要继承JpaSpecificationExecutor:
public interface OyUserRepository extends JpaRepository<OyUser,Integer> , JpaSpecificationExecutor {
}
然后就是构造动态查询:
/**
* 查询用户列表2
*
* @return 用户列表
*/
@GetMapping("listByKeyWord2")
public String listByKeyWord2(ModelMap model, OyUser oyUser) {
Specification querySpeci = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList();
if (oyUser.getName() != null) {
predicates.add(criteriaBuilder.like(root.get("name"), "%" + oyUser.getName() + "%"));
}
if (oyUser.getAccount() != null) {
predicates.add(criteriaBuilder.like(root.get("account"), "%" + oyUser.getAccount() + "%"));
}
if (null != oyUser.getAge()) {
predicates.add(criteriaBuilder.gt(root.get("age"), oyUser.getAge()));
}
if (null != oyUser.getSex()) {
predicates.add(criteriaBuilder.equal(root.get("sex"), oyUser.getSex()));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
Pageable pageable = PageRequest.of(0, 3);
Page<OyUser> userListPage = oyUserRepository.findAll(querySpeci, pageable);
model.put("users", userListPage.getContent());
return "user";
}
其他
@Resource
private EntityManager entityManager;
/**
* 查询用户列表1
*
* @return 用户列表
*/
@GetMapping("listByKeyWord1")
public String listByKeyWord1(ModelMap model, OyUser user) {
//创建CriteriaBuilder安全查询工厂
//CriteriaBuilder是一个工厂对象,安全查询的开始.用于构建JPA安全查询.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
//创建CriteriaQuery安全查询主语句
//CriteriaQuery对象必须在实体类型或嵌入式类型上的Criteria 查询上起作用。
CriteriaQuery<OyUser> query = criteriaBuilder.createQuery(OyUser.class);
//Root 定义查询的From子句中能出现的类型
Root<OyUser> userRoot = query.from(OyUser.class);
//Predicate 过滤条件 构建where字句可能的各种条件
//这里用List存放多种查询条件,实现动态查询
List<Predicate> predicatesList = new ArrayList<>();
//name模糊查询 ,like语句
if (user.getName() != null) {
predicatesList.add(
criteriaBuilder.and(
criteriaBuilder.like(
userRoot.get("name"), "%" + user.getName() + "%")));
}
//account模糊查询 ,like语句
if (user.getAccount() != null) {
predicatesList.add(
criteriaBuilder.and(
criteriaBuilder.like(
userRoot.get("account"), "%" + user.getAccount() + "%")));
}
// itemPrice 小于等于 <= 语句
if (user.getAge() != null) {
predicatesList.add(
criteriaBuilder.and(
criteriaBuilder.le(
userRoot.get("age"), user.getAge())));
}
//itemStock 大于等于 >= 语句 criteriaBuilder.ge
//where()拼接查询条件
query.where(predicatesList.toArray(new Predicate[predicatesList.size()]));
TypedQuery<OyUser> typedQuery = entityManager.createQuery(query);
//查询
List<OyUser> userList = typedQuery.getResultList();
model.put("users", userList);
return "user";
}
四、SpringBoot 整合Spring Data JPA
- 在springboot中引入依赖
<!--JDBC--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.29</version> </dependency> <!--spring-data-jpa--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
- 配置数据源
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/ouyangblog?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8 username: ouyangcheng password: 123456 driver-class-name: com.mysql.jdbc.Driver druid: initialSize: 1 minIdle: 1 maxActive: 50 maxWait: 6000 timeBetweenEvictionRunsMillis: 6000 minEvictableIdleTimeMillis: 30000 testWhileIdle: true testOnBorrow: true testOnReturn: true validationQuery: SELECT 1 from dual connectionProperties: config.decrypt=false # database connection plain password
- 持久层继承JpaRepository
/** * @author oyc * @Title: OuYangUserRepositoryTest * @ProjectName ouyangblog * @Description: TODO * @date 2018/11/15 21:48 */ @Repository public interface OuYangUserRepository extends JpaRepository<OuYangUser, Integer> { OuYangUser findByAccount(String account); }
- 编写接口调用测试JPA的方法,从下图可以看到,我们可以直接注入持久层OuYangUserRepository ,使用JPA提供的方法来进行对数据库的操作
package cn.com.ouyangblog.web.rest;
import cn.com.ouyangblog.domain.OuYangUser;
import cn.com.ouyangblog.repository.OuYangUserRepository;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* @author oyc
* @Title: OuYangUserController
* @ProjectName ouyangblog
* @Description: TODO
* @date 2018/11/15 21:41
*/
@Api(value = "用户模块")
@Controller
@RequestMapping("/user")
public class OuYangUserController {
@Resource
private OuYangUserRepository ouYangUserRepository;
@ApiOperation(value = "获取用户列表")
@RequestMapping(value = "/list", method = {RequestMethod.GET, RequestMethod.POST})
public String list(HttpServletRequest request, HttpServletResponse response, Model model) {
List<OuYangUser> userList = ouYangUserRepository.findAll();
model.addAttribute("userList", userList);
return "pages/userList";
}
@ApiOperation(value = "添加用户")
@PostMapping("/adduser")
public String addUser(OuYangUser bean,Model model) throws Exception {
try {
ouYangUserRepository.saveAndFlush(bean);
} catch (Exception e) {
e.printStackTrace();
}
List<OuYangUser> userList = ouYangUserRepository.findAll();
model.addAttribute("userList", userList);
return "pages/userList";
}
@ApiOperation(value = "删除用户")
@PostMapping("/deluser")
public ResponseEntity<Boolean> delUser(Integer id) throws Exception {
try {
ouYangUserRepository.deleteById(id);
return ResponseEntity.ok(true);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.ok(false);
}
}
}