SpringBoot 整合 Sping Data JPA,堪称快速搭建项目,快速开发的典范。
JPA: 是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
Hibernate3.2+、TopLink 10.1.3以及OpenJPA都提供了JPA的实现。JPA的总体思想和现有Hibernate、TopLink、JDO等ORM框架大体一致。
JPA是需要Provider来实现其功能的,Hibernate就是JPA Provider中很强的一个,应该说无人能出其右。
开始写SpringBoot整合JPA之前先了解Spring Data JPA,可以看看这两篇博客:
Spring-Data-JPA 学习笔记(一)
Spring-Data-JPA 学习笔记(二)
本篇博客是对上面两篇博客的总结,Demo更加完整适用。这里只列出了关键类代码,有些工具类,查询类代码就不列了,在下面的源码地址中可找到:
项目GitHub地址
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.springbootgroupId>
<artifactId>jpaartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>jpaname>
<description>Demo project for Spring Boot Jpadescription>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.2.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.9version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.8.0version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.8.0version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.7version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>3.17version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
application.yml
server:
port: 8080
# 数据库访问配置,主数据源,默认的
spring:
jackson:
serialization:
fail-on-empty-beans: false
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/springboot?useUnicode=true&characterEncoding=UTF-8
username: mistra
password: 123456
# 初始化大小,最小,最大
druid:
initial-size: 1
min-idle: 1
max-active: 20
# 配置获取连接等待超时的时间
max-wait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
use-global-data-source-stat: true
jpa:
hibernate:
# 自动创建、更新、验证数据库表结构
ddl-auto: update # 第一次简表create 后面用update
# 显示sql
show-sql: true
jpa.hibernate.ddl-auto: update 自动维护表结构,当实体类属性改变时,启动项目时会自动更新数据库表结构。
User.java
package com.springboot.jpa.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.springboot.jpa.util.hibernate.BaseEntity;
import io.swagger.annotations.ApiParam;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* Author: RoronoaZoro丶WangRUi
* Time: 2018/6/14/014
* Describe:
*/
@Data
@Entity
@Table(name = "wm_user")
@JsonIgnoreProperties(value = {"hibernateLazyInitializer","handler","fieldHandler"},ignoreUnknown = true)
public class User extends BaseEntity {
@ApiParam("用户名")
private String userName;
@ApiParam("昵称")
private String nickName;
@ApiParam("岗位")
private String position;
@ApiParam("年龄")
private Integer age;
}
@Data : Lombok注解
@Entity :指名这是一个实体Bean
@Table(name = “wm_user”) :指定了Entity所要映射带数据库表,其中@Table.name()用来指定映射表的表名。如果缺省@Table注释,系统默认采用类名作为映射表的表名
为什么加@JsonIgnoreProperties注解可以看这里:No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer,运行项目的时候就是报这个错。
实体类公共父类 BaseEntity.java
package com.springboot.jpa.util.hibernate;
import io.swagger.annotations.ApiParam;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* Author: WangRui
* Date: 2018/5/20
* Describe: 实体类基础属性
*/
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements Serializable {
private static final long serialVersionUID = -7674269980281525370L;
@Id
@GeneratedValue
@ApiParam("主键ID")
protected Long id;
@CreatedDate
@ApiParam("创建时间")
protected Date createTime;
@LastModifiedDate
@ApiParam("更新时间")
protected Date modifyTime;
}
@Id :指定主键
@GeneratedValue:指定主键生成规则,默认自增
@MappedSuperclass:加了此注解的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。不能再标注@Entity或@Table注解,也无需实现序列化接口。这样的类还可以直接标注@EntityListeners实体监听器。
@EntityListeners:声明实体监听器,用于实体修改时做处理(切面编程)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GeneratedValue {
GenerationType strategy() default GenerationType.AUTO;
String generator() default "";
}
package javax.persistence;
public enum GenerationType {
TABLE,
SEQUENCE,
IDENTITY,
AUTO;
private GenerationType() {
}
}
详细了解戳这里:
JPA注解之“@GeneratedValue”详解
JPA @Id 和 @GeneratedValue 注解详解
Dao 接口 UserRepository.java
package com.springboot.jpa.repoistory;
import com.springboot.jpa.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
/**
* Author: RoronoaZoro丶WangRUi
* Time: 2018/6/14/014
* Describe:
*/
public interface UserRepository extends JpaRepository<User,Long>,JpaSpecificationExecutor<User> {
//符合JPA命名规则的方法定义
User findFirstByUserName(String name);
@Query(value = "from User where position =?1")
List<User> selectByCustomSqlTest1(String position);
@Query(value = "select * from wm_user where position = ?1",nativeQuery = true)
List<User> selectByCustomSqlTest2(String position);
Page<User> findByNickName(String nickName, Pageable pageable);
}
JpaRepository< User,Long>:User为对应实体类,Long为实体类主键类型。
JpaRepository继承PagingAndSortingRepository,QueryByExampleExecutor,CrudRepository定义了一系列的增删查改,分页,条件查询方法。
findFirstByUserName()符合JPA命名规则的方法,见名知意,根据用户姓名查询第一条数据。这种方法定义时会根据实体类的属性进行提示:
戳这里了解JPA方法命名规范:Spring Data JPA方法命名规则
selectByCustomSqlTest1和selectByCustomSqlTest2是自定义sql的方法,
注意:
findByNickName()方法是用来测试JPA自带的分页方法的。
UserService.java
package com.springboot.jpa.service;
import com.springboot.jpa.entity.User;
import com.springboot.jpa.util.hibernate.PageCondition;
import com.springboot.jpa.util.hibernate.Pager;
import com.springboot.jpa.vo.UserQueryVo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
/**
* Author: RoronoaZoro丶WangRUi
* Time: 2018/6/14/014
* Describe:
*/
public interface UserService {
void save(User user);
User getOne(Long id);
void delete(Long id);
Page<User> getPage(Pageable pageable,UserQueryVo userQueryVo);
Pager<User> getPager(PageCondition condition, UserQueryVo userQueryVo);
List<User> findByCustomSqlTest1(UserQueryVo userQueryVo);
List<User> findByCustomSqlTest2(UserQueryVo userQueryVo);
User jpaName(UserQueryVo userQueryVo);
Page<User> jpaPageSelect(PageCondition condition,UserQueryVo userQueryVo);
Page<User> jpaSpecificationTest1(PageCondition condition,UserQueryVo userQueryVo);
Page<User> jpaSpecificationTest2(PageCondition condition,UserQueryVo userQueryVo);
}
UserServiceImpl.java
package com.springboot.jpa.service.impl;
import com.springboot.jpa.entity.User;
import com.springboot.jpa.repoistory.UserRepository;
import com.springboot.jpa.service.UserService;
import com.springboot.jpa.util.hibernate.PageCondition;
import com.springboot.jpa.util.hibernate.Pager;
import com.springboot.jpa.util.hibernate.QueryHelper;
import com.springboot.jpa.util.hibernate.dao.CommonDAO;
import com.springboot.jpa.vo.UserQueryVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.persistence.criteria.*;
import java.util.List;
/**
* Author: RoronoaZoro丶WangRUi
* Time: 2018/6/14/014
* Describe:
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private CommonDAO commonDAO;
@Override
public void save(User user) {
userRepository.save(user);
}
@Override
public User getOne(Long id) {
return userRepository.getOne(id);
}
@Override
public void delete(Long id) {
userRepository.deleteById(id);
}
@Override
public Page<User> getPage(Pageable pageable, UserQueryVo userQueryVo) {
return userRepository.findAll(pageable);
}
@Override
public Pager<User> getPager(PageCondition condition, UserQueryVo userQueryVo) {
/*QueryHelper helper = new QueryHelper(User.class)
.addCondition(StringUtils.isNoneBlank(userQueryVo.getUserName()), "userName like ", "%" + userQueryVo.getUserName() + "%")
.addCondition(StringUtils.isNoneBlank(userQueryVo.getNickName()), "nickName like ", "%" + userQueryVo.getNickName() + "%")
.addCondition(StringUtils.isNoneBlank(userQueryVo.getPosition()), "position = ?", userQueryVo.getPosition())
.setPageCondition(condition)
.useNativeSql(false);*/
QueryHelper helper = new QueryHelper("id,user_name as userName,nick_name as nickName,position,age"," wm_user")
.setPageCondition(condition)
.useNativeSql(true);
Pager<User> pager = commonDAO.findPager(helper,User.class);
return pager;
}
@Override
public List<User> findByCustomSqlTest1(UserQueryVo userQueryVo) {
return userRepository.selectByCustomSqlTest1(userQueryVo.getPosition());
}
@Override
public List<User> findByCustomSqlTest2(UserQueryVo userQueryVo) {
return userRepository.selectByCustomSqlTest2(userQueryVo.getPosition());
}
@Override
public User jpaName(UserQueryVo userQueryVo) {
return userRepository.findFirstByUserName(userQueryVo.getUserName());
}
/**
* Jpa自带的分页,排序和条件查询测试
*
* @param condition
* @param userQueryVo
* @return
*/
@Override
public Page<User> jpaPageSelect(PageCondition condition, UserQueryVo userQueryVo) {
PageRequest pageRequest = PageRequest.of(condition.getPageNum(), condition.getPageSize(), Sort.Direction.ASC, condition.getOrder());
Page<User> users = userRepository.findByNickName(userQueryVo.getNickName(), pageRequest);
return users;
}
/**
* JpaSpecificationExecutor条件查询接口测试1
*
* @param condition
* @param userQueryVo
* @return
*/
@Override
public Page<User> jpaSpecificationTest1(PageCondition condition, UserQueryVo userQueryVo) {
PageRequest pageRequest = PageRequest.of(condition.getPageNum(), condition.getPageSize(), Sort.Direction.DESC, condition.getOrder());
Page<User> page = userRepository.findAll(new Specification<User>() {
@Override
public javax.persistence.criteria.Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<String> userName = root.get("userName");
Path<String> position = root.get("position");
//这里可以设置任意条查询条件
query.where(cb.like(userName, "%" + userQueryVo.getUserName() + "%"), cb.like(position, "%" + userQueryVo.getPosition() + "%"));
//这种方式使用JPA的API设置了查询条件,所以不需要再返回查询条件Predicate给Spring Data Jpa,故最后return null;即可。
return null;
}
}, pageRequest);
return page;
}
/**
* JpaSpecificationExecutor条件查询接口测试2
*
* @param condition
* @param userQueryVo
* @return
*/
@Override
public Page<User> jpaSpecificationTest2(PageCondition condition, UserQueryVo userQueryVo) {
PageRequest pageRequest = PageRequest.of(condition.getPageNum(), condition.getPageSize(), Sort.Direction.DESC, condition.getOrder());
Page<User> page = userRepository.findAll(new Specification<User>() {
@Override
public javax.persistence.criteria.Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate1 = cb.like(root.get("userName"),"%" + userQueryVo.getUserName() + "%");
Predicate predicate2 = cb.like(root.get("position"),"%" + userQueryVo.getPosition() + "%");
query.where(cb.or(predicate1,predicate2));
//等于:query.where(cb.or(cb.like(root.get("userName"),"%" + userQueryVo.getUserName() + "%"),cb.like(root.get("position"),"%" + userQueryVo.getPosition() + "%")));
return null;
}
}, pageRequest);
return page;
}
}
CommonDAO 、QueryHelper 、Pager、PageCondition都是自定义的查询辅助类,可查询GitHub源码了解。
主要介绍下JPA自带的分页,条件查询方法。
Jpa自带的分页,排序和条件查询测试:jpaPageSelect();
PageRequest pageRequest = PageRequest.of(condition.getPageNum(), condition.getPageSize(), Sort.Direction.ASC, condition.getOrder());
Page<User> users = userRepository.findByNickName(userQueryVo.getNickName(), pageRequest);
findByNickName()就是userRepository中定义那个符合JPA命名规范的条件查询、分页方法。
JPA的条件构造查询方法,感觉有点难用,没有上面自定义的那个查询辅助类好用,介绍一下,要使用JPA自带的条件构造查询,UserRepository必须要继承JpaSpecificationExecutor。
方法:jpaSpecificationTest1();
PageRequest pageRequest = PageRequest.of(condition.getPageNum(), condition.getPageSize(), Sort.Direction.DESC, condition.getOrder());
Page<User> page = userRepository.findAll(new Specification<User>() {
@Override
public javax.persistence.criteria.Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<String> userName = root.get("userName");
Path<String> position = root.get("position");
//这里可以设置任意条查询条件
query.where(cb.like(userName, "%" + userQueryVo.getUserName() + "%"), cb.like(position, "%" + userQueryVo.getPosition() + "%"));
//这种方式使用JPA的API设置了查询条件,所以不需要再返回查询条件Predicate给Spring Data Jpa,故最后return null;即可。
return null;
}
}, pageRequest);
return page;
Specification:封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象。使用Specification的要点就是CriteriaBuilder,通过这个对象来创建条件,之后返回一个Predicate对象。想必这个JPA自带的构造条件查询,我更喜欢用那个自定义的QueryHelper。
UserController.java
package com.springboot.jpa.controller;
import com.springboot.jpa.entity.User;
import com.springboot.jpa.service.UserService;
import com.springboot.jpa.util.hibernate.PageCondition;
import com.springboot.jpa.util.hibernate.Pager;
import com.springboot.jpa.util.web.annotation.*;
import com.springboot.jpa.vo.UserQueryVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Author: RoronoaZoro丶WangRUi
* Time: 2018/6/14/014
* Describe:
*/
@RestController
@RequestMapping("/user")
@Api("UserController")
public class UserController {
@Autowired
private UserService userService;
@ApiOperation("新增")
@AddUrl
public void save(User user){
userService.save(user);
}
@ApiOperation("查询单条")
@GetOneUrl
public User getOne(Long id){
return userService.getOne(id);
}
@ApiOperation("删除")
@DeleteUrl
public void delete(Long id){
userService.delete(id);
}
@ApiOperation("分页查询1-自定义的查询辅助类")
@SelectPageUrl
public Pager<User> getPager(PageCondition condition, UserQueryVo userQueryVo){
return userService.getPager(condition,userQueryVo);
}
@ApiOperation("分页查询2-Jpa自带的查询辅助类")
@PageUrl
public Page<User> getPage(Pageable pageable,UserQueryVo userQueryVo){
return userService.getPage(pageable,userQueryVo);
}
@ApiOperation("自定义sql测试1")
@GetMapping("/customSql1")
public List<User> customSql1(UserQueryVo userQueryVo){
return userService.findByCustomSqlTest1(userQueryVo);
}
@ApiOperation("自定义sql测试2")
@GetMapping("/customSql2")
public List<User> customSql2(UserQueryVo userQueryVo){
return userService.findByCustomSqlTest2(userQueryVo);
}
@ApiOperation("Jpa命名规范接口测试")
@GetMapping("/jpaName")
public User jpaName(UserQueryVo userQueryVo){
return userService.jpaName(userQueryVo);
}
@ApiOperation("Jpa自带的分页,排序和条件查询测试")
@GetMapping("/jpaPageSelect")
public Page<User> jpaPageSelect(PageCondition condition,UserQueryVo userQueryVo){
return userService.jpaPageSelect(condition,userQueryVo);
}
@ApiOperation("JpaSpecificationExecutor条件查询接口测试1")
@GetMapping("/jpaSpecification1")
public Page<User> jpaSpecificationTest1(PageCondition condition,UserQueryVo userQueryVo){
return userService.jpaSpecificationTest1(condition,userQueryVo);
}
@ApiOperation("JpaSpecificationExecutor条件查询接口测试2")
@GetMapping("/jpaSpecification2")
public Page<User> jpaSpecificationTest2(PageCondition condition,UserQueryVo userQueryVo){
return userService.jpaSpecificationTest2(condition,userQueryVo);
}
}
我的: