# Springboot-Jpa
**Repository Path**: salute83/springboot-jpa
## Basic Information
- **Project Name**: Springboot-Jpa
- **Description**: Springboot 整合数据库工具 Jpa
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-08-18
- **Last Updated**: 2022-09-08
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Springboot 整合 JPA
## 1 参考资料
1. [Spring Boot(三):Spring Boot Jpa的介绍及Jpa的使用](http://t.csdn.cn/XH5sv)
2. [spring-boot 整合 JPA](http://docs.tumo.tycoding.cn/#/docs/boot/spring-boot-jpa?id=delete)
3. [springboot整合jpa,步骤详细(图文结合讲解)](http://t.csdn.cn/VCUbW)
4. [Spring Data JPA 简单查询-接口方法](https://www.cnblogs.com/logsharing/p/8462026.html)
5. [Spring Data JPA 简单查询--方法定义规则](https://www.cnblogs.com/rulian/p/6434631.html)
6. [Spring Data JPA 进阶(自定义查询,原生 SQL 查询)](https://blog.csdn.net/chaotiantian/article/details/115933280)
7. [Jpa 实体类常用注解](http://t.csdn.cn/UgWmF)
## 2 Springboot JPA简介
### 2.1 什么是JPA
JPA顾名思义就是Java Persistence API的意思,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。Jpa 是一套规范,不是一套产品。
Jpa (Java Persistence API) 是 Sun 官方提出的 Java 持久化规范。它为 Java 开发人员提供了一种对象/关联映射工具来管理 Java 应用中的关系数据。它的出现主要是为了简化现有的持久化开发工作和整合 ORM 技术,结束现在 Hibernate,TopLink,JDO 等 ORM 框架各自为营的局面。Jpa是在充分吸收了现有 Hibernate,TopLink,JDO 等 ORM 框架的基础上发展而来的,具有易于使用,伸缩性强等优点。从目前的开发社区的反应上看,Jpa 受到了极大的支持和赞扬,其中就包括了 Spring 与 EJB3. 0的开发团队。
### 2.2 什么是Springboot JPA
Spring Boot Jpa 是 Spring 基于 ORM 框架、Jpa 规范的基础上封装的一套 Jpa 应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data Jpa 可以极大提高开发效率!
## 3 Springboot 整合 JPA
### 3.1 创建项目
1. 创建Springboot -maven 项目
2. Springboot 的版本为 `2.1.17.RELEASE`
3. 项目的名称为 `Springboot-Jpa`
### 3.2 文件结构

1. dao:dao/Repository层
2. pojo:pojo/entity(实体类)
3. service:服务类接口
- impl:服务类实现类
4. test:测试方法
5. resources:配置文件
### 3.3 导入Maven依赖
```xml
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
8.0.17
org.projectlombok
lombok
org.springframework.boot
spring-boot-devtools
runtime
true
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter
```
### 3.4 编写配置文件
> 由于使用了spring-boot开发,spring-boot能够减轻我们在XML文件中繁琐的配置,因此在spring-boot中配置JPA也格外简单。
**application.properties**
```properties
# 应用名称
spring.application.name=jpa
#自动生成数据库表(关键)
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
#jpa配置:在控制台显示Hibernate的sql(可选)
spring.jpa.show-sql = true
#mysql数据库连接配置(非常重要)
spring.datasource.url = jdbc:mysql://localhost:3306/springboot_jpa?serverTimezone=Asia/Shanghai
#数据库用户名
spring.datasource.username = root
#数据库密码
spring.datasource.password = root
#mysql数据库驱动程序(重要)
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
#其他配置:关闭Thymeleaf 的缓存
spring.thymeleaf.cache = false
server.port=8080
```
**application.yml**
```yml
spring:
application:
# 应用名称
name: jpa
# springboot - jpa 配置
jpa:
hibernate:
# 自动生成数据库表(关键)
ddl-auto: update
dialect: org.hibernate.dialect.MySQL5Dialect
# 在控制台显示hibernate的sql
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
# mysql 数据库连接配置(非常重要)
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost.3306/springboot_jpa?serverTimezone=Asia/Shanghai
username: root
password: root
# 关闭 thymeleaf 的缓存
thymeleaf:
cache: false
server:
port: 8080
```
- `spring.jpa.hibernate.ddl-auto=update`:`jpa`是一套规范,而如`hibernate`仅是基于`jpa`的一套产品。开启update,hibernate可以根据`Entity`属性自动创建数据库,如果想要手动创建表结构,就不要配置`auto=update`
- `spring.datasource.url`:`datasource`下的`url`数据库连接在`数据库名`后面一定要拼接`?characterEncoding=utf-8`,目的是避免调用JPA的方法存入数据库中的数据**中文乱码**
### 3.5 自动创建数据表
> `hibernate` 是`jpa` 的一套产品。`hibernate` 可以根据 `Entity/Pojo` 实体类自动创建数据库,`Jpa`也是可以使用的。
>
> - 需要在配置文件`application.yml` 中开启`spring.jpa.hibernate.ddl-auto=update`。
> - JPA不能自动创建数据库,数据库需要手动创建。
#### 3.5.1 修改配置文件
以`application.properties`为例
```xml
spring.jpa.properties.hibernate.hbm2ddl.auto=update
```
#### 3.5.2 创建实体类
```java
package com.test.springboot.jpa.pojo;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* @author thinkpad - 83start
* @version v1.0
* @create 2022/8/18 2:24
* @package com.test.springboot.jpa.pojo
* @description 根据实体类自动创建 表
* Java类型和对应的表属性关系
* JPA 常见注解 和 操作
*/
@Entity //指定当前类是实体类,对应数据库中的一个表
@Data
@Table(name = "tb_create_table")
@EntityListeners(AuditingEntityListener.class)// 封装生成自动时间
public class CreateTable implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 设定主键生成策略:IDENTITY 表示由数据库自动生成
private Long id; // Long 对应 MySQL 数据库的 bigint 类型
@Column(name = "string_value",nullable = false,unique = true,length = 20) // 不为空,唯一,且字符最大长度为 20
private String stringValue; // String 对应 MySQL 数据库中的 varchar 类型
@Column(name = "integer_value")
private Integer integerValue = 3; // 赋值默认值
@Column(name = "double_value")
private Double doubleValue;
/**
* 自动生成时间:create 生效
*/
@CreatedDate
@Column(name = "create_time")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 定义时间格式
private Date createTime;
/**
* 自动生成时间:update 生效
*/
@LastModifiedDate
@Column(name = "update_time")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 定义时间格式
private Date updateTime;
@Transient //如果你想要Entity中的某个属性不被识别为表字段,可以用@Transient注解标记。
private String transientValue;
}
```
#### 3.5.3 重启 Springboot 项目
重新启动项目,就会自动生成数据库表。

### 3.6 编写实体类
> 实体类于数据库表相对应。
>
> class –> table
>
> 常量 –> column
#### 3.6.1 Java 和 数据库 数据类型对应
> 对于不同的DB可能有所差异
| Java数据类型 | Hibernate数据类型 | 标准SQL数据类型 |
| ---------------------------------- | ----------------- | ----------------- |
| byte、java.lang.Byte | byte | TINYINT |
| short、java.lang.Short | short | SMALLINT |
| int、java.lang.Integer | integer | INGEGER |
| long、java.lang.Long | long | BIGINT |
| float、java.lang.Float | float | FLOAT |
| double、java.lang.Double | double | DOUBLE |
| java.math.BigDecimal | big_decimal | NUMERIC |
| char、java.lang.Character | character | CHAR(1) |
| boolean、java.lang.Boolean | boolean | BIT |
| java.lang.String | string | VARCHAR |
| boolean、java.lang.Boolean | yes_no | CHAR(1)(‘Y’或‘N’) |
| boolean、java.lang.Boolean | true_false | CHAR(1)(‘Y’或‘N’) |
| java.util.Date、java.sql.Date | date | DATE |
| java.util.Date、java.sql.Time | time | TIME |
| java.util.Date、java.sql.Timestamp | timestamp | TIMESTAMP |
| java.util.Calendar | calendar | TIMESTAMP |
| java.util.Calendar | calendar_date | DATE |
| byte[] | binary | VARBINARY、BLOB |
| java.lang.String | text | CLOB |
| java.io.Serializable | serializable | VARBINARY、BLOB |
| java.sql.Clob | clob | CLOB |
| java.sql.Blob | blob | BLOB |
| java.lang.Class | class | VARCHAR |
| java.util.Locale | locale | VARCHAR |
| java.util.TimeZone | timezone | VARCHAR |
| java.util.Currency | currency | VARCHAR |
#### 3.6.2 常见注解(Class)
##### 3.6.2.1 @Entity
**作用:**
被Entity标注的实体类将会被JPA管理控制,在程序运行时,JPA会识别并映射到指定的数据库表
**参数:**
- name:指定实体类名称,默认为当前实体类的非限定名称。
> 若给了name属性值即@Entity(name="XXX"),则jpa在仓储层(数据层)进行自定义查询时,所查的表名应是XXX。 如:select s from XXX s
##### 3.6.2.2 @Table
**作用:**
当数据库表名与实体类名称不同时,使用 @Table(name="数据库表名"),与@Entity标注并列使用,置于实体类声明语句之前
**参数:**
- name:指定对应数据库中的表名
- catalog(不常用):用于设置表所映射到的数据库的目录
- schema(不常用):用于设置表所映射到的数据库的模式
- uniqueConstraints(不常用): 设置约束条件
#### 3.6.3 常见注解(常量)
##### 3.6.3.1 @Id
**作用:**被标注的常量将映射为数据库的主键
> JPA官方规定数据表必须有主键(id),而之前权限管理项目中一些关联表(用户角色关联表)是不需要主键的,但如果使用JPA就必须配置主键,不管你用不用。
##### 3.6.3.2 @GeneratedValue
**作用:**与@Id一同使用,用于标注主键的生成策略。
**参数:**
- strategy:
- IDENTITY:采用数据库ID自增长的方式产生主键,Oracle 不支持这种方式。
- AUTO: JPA 自动选择合适的策略,是默认选项。
- SEQUENCE:通过序列产生主键,通过@SequenceGenerator标注指定序列名,MySQL 不支持这种方式。
- TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略更易于做数据库移植。
##### 3.6.3.3 @Basic
**作用:**一个简单的属性到数据库表的字段的映射,对于没有任何标注的 getXxxx() 方法,默认即为@Basic(fetch=FetechType.EAGER)
**参数:**
- fetch:表示该属性的加载读取策略
- EAGER 主动抓取 (默认为EAGER)
- LAZY 延迟加载,只有用到该属性时才会去加载
- optional (默认为 true):表示该属性是否允许为null
##### 3.6.3.4 @Column
**作用:**声明实体的属性
**参数:**
- name: 指定映射到数据库中的字段名
2. unique: 是否唯一,默认为false
3. nullable: 是否允许为null,默认为true
4. insertable: 是否允许插入,默认为true
5. updatetable: 是否允许更新,默认为true
- columnDefinition: 指定该属性映射到数据库中的实际类型,通常是自动判断。
##### 3.6.3.5 @Transient
**作用:**JPA会忽略该属性,不会映射到数据库中,即程序运行后数据库中将不会有该字段
##### 3.6.3.6 @Temporal
**作用:**Java中没有定义 Date 类型的精度,而数据库中,表示时间类型的数据有 DATE,TIME,TIMESTAMP三种精度
**参数:**
- @Temporal(TemporalType.DATE) 表示映射到数据库中的时间类型为 DATE,只有日期
- @Temporal(TemporalType.TIME) 表示映射到数据库中的时间类型为 TIME,只有时间
- @Temporal(TemporalType.TIMESTAMP) 表示映射到数据库中的时间类型为 TIMESTAMP,日期和时间都有
##### 3.6.3.7 @Embedded 和 @Embeddable:
**作用:**用于一个实体类要在多个不同的实体类中进行使用,而本身又不需要独立生成一个数据库表
> 具体参考:[Hibernate中@Embedded和@Embeddable注解的使用](https://blog.csdn.net/lmy86263/article/details/52108130)
##### 3.6.7.8 @JoinColumn
**作用:**定义表关联的外键字段名
**参数:**
- name: 指定映射到数据库中的外键的字段名
- unique: 是否唯一,默认为false
- nullable: 是否允许为null,默认为true
- insertable: 是否允许插入,默认为true
- updatetable: 是否允许更新,默认为true
- columnDefinition: 指定该属性映射到数据库中的实际类型,通常是自动判断。
- foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT):指定外键相关信息,这里用法是指定外联关系但是不建立数据库外键
##### 3.6.7.9 @OneToOne
**参数:**
- targetEntity: 指定关联实体类型,默认为被注解的属性或方法所属的类
- cascade: 级联操作策略
- CascadeType.ALL 级联所有操作
- CascadeType.PERSIST 级联新增
- CascadeType.MERGE 级联归并更新
- CascadeType.REMOVE 级联删除
- CascadeType.REFRESH 级联刷新
- CascadeType.DETACH 级联分离
- fetch: fetch 表示该属性的加载读取策略 (默认值为 EAGER)
- EAGER 主动抓取
- LAZY 延迟加载,只有用到该属性时才会去加载
- optional:
- 默认为true,关联字段是否为空
- 如果为false,则常与@JoinColumn一起使用
- mappedBy: 指定关联关系,该参数只用于关联关系被拥有方只用于双向关联@OneToOne,@OneToMany,@ManyToMany。而@ManyToOne中没有@OneToOne(mappedBy = “xxx”)
表示xxx所对应的类为关系被拥有方,而关联的另一方为关系拥有方
- 关系拥有方:对应拥有外键的数据库表
- 关系被拥有方:对应主键被子表引用为外键的数据库表
- orphanRemoval:默认值为false
判断是否自动删除与关系拥有方不存在联系的关系被拥有方(关系被拥有方的一个主键在关系拥有方中未被引用,当jpa执行更新操作时,是否删除数据库中此主键所对应的一条记录,若为true则删除)
**实例:**
```java
//单向 一对一
@Entity
public class Emp{//员工
@Id
@GeneratedValue
privte Integer eId;
@Column(length = 40)
private String empName;
@OneToOne(cascade = CascadeType.ALL)
private Identity identity;
//get,set方法省略
}
@Entity
public class Identity{//身份证
//...
}
```
```java
//双向 一对一
@Entity
public class Emp{
@Id
@GeneratedValue
privte Integer eId;
@Column(length = 40)
private String empName;
@OneToOne(cascade = CascadeType.ALL)
private Identity identity;
//get,set方法省略
}
@Entity
public class Identity{
@Id
@GeneratedValue
privte Integer iId;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "identity")
private Emp emp;
//...
}
```
以上例子,双向一对一,Emp 为关系拥有方,Identity 为关系被拥有方。
执行spring-data-jpa新增操作时,如果通过Identity的数据访问层进行新增操作(IdentityRepository.save())
,Emp表和Identity表都有数据,但是不会设置这两条数据的关系,Emp表中的外键为null。
反之,以关系拥有方Emp的数据访问层进行新增操作(EmpRepository.save()),Emp表和Identity表都有数据,并且设置了两条数据的关系,即Emp表中的外键也得到正确新增。
##### 3.6.7.10 @ManyToOne、@OneToMany
**作用:**
多对一(也可叫一对多,只是前后表颠倒一下而已),只有双向多对一时才用得到@OneToMany。多对一中多的一方必定是对应数据库中拥有外键的表,即是关系拥有方,@ManyToOne只用在多对一中代表多的一类中,因为mappedBy只用于关系被拥有方,所以@ManyToOne参数中不包含mappedBy。
**@ManyToOne参数:**
- targetEntity: 指定关联实体类型,默认为被注解的属性或方法所属的类
- cascade: 级联操作策略
- CascadeType.ALL 级联所有操作
- CascadeType.PERSIST 级联新增
- CascadeType.MERGE 级联归并更新
- CascadeType.REMOVE 级联删除
- CascadeType.REFRESH 级联刷新
- CascadeType.DETACH 级联分离
- fetch: fetch 表示该属性的加载读取策略(@ManyToOne 的默认值是 EAGER,@OneToMany 的默认值是 LAZY)
- EAGER 主动抓取
- LAZY 延迟加载,只有用到该属性时才会去加载
- optional:
- 默认为true,关联字段是否为空
- 如果为false,则常与@JoinColumn一起使用
**@OneToMany参数**
除上述以外还有:
- mappedBy: 指定关联关系,该参数只用于关联关系被拥有方
只用于双向关联@OneToOne,@OneToMany,@ManyToMany。而@ManyToOne中没有
@OneToMany(mappedBy = “xxx”)
表示xxx所对应的类为关系被拥有方,而关联的另一方为关系拥有方
- 关系拥有方:对应拥有外键的数据库表
- 关系被拥有方:对应主键被子表引用为外键的数据库表
- orphanRemoval:默认值为false
判断是否自动删除与关系拥有方不存在联系的关系被拥有方(关系被拥有方的一个主键在关系拥有方中未被引用,当jpa执行更新操作时,是否删除数据库中此主键所对应的一条记录,若为true则删除)
**举例:**
```java
//单向 多对一
@Entity
public class Emp{
@Id
@GeneratedValue
privte Integer eId;
@Column(length = 40)
private String empName;
@ManyToOne(cascade = CascadeType.ALL)
private Dept dept;
//...
}
@Entity
public class Dept{
@Id
@GeneratedValue
privte Integer dId;
@Column(length = 40)
private String deptName;
//...
}
```
```java
//双向 多对一
@Entity
public class Emp{
@Id
@GeneratedValue
privte Integer eId;
@Column(length = 40)
private String empName;
@ManyToOne(cascade = CascadeType.ALL)
private Emp emp;
//...
}
@Entity
public class Dept{
@Id
@GeneratedValue
privte Integer dId;
@Column(length = 40)
private String deptName;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "emp")
private List emps;
//...
}
```
> 无论双向关联还是单向关联,数据库中均会在Emp表中自动生成一个外键(dept_d_id)
##### 3.6.7.11 @ManyToMany
**参数:**
- targetEntity: 指定关联实体类型,默认为被注解的属性或方法所属的类
- cascade: 级联操作策略
- CascadeType.ALL 级联所有操作
- CascadeType.PERSIST 级联新增
- CascadeType.MERGE 级联归并更新
- CascadeType.REMOVE 级联删除
- CascadeType.REFRESH 级联刷新
- CascadeType.DETACH 级联分离
- fetch: fetch 表示该属性的加载读取策略 (默认值为 LAZY)
- EAGER 主动抓取
- LAZY 延迟加载,只有用到该属性时才会去加载
- mappedBy: 指定关联关系,该参数只用于关联关系被拥有方
只用于双向关联@OneToOne,@OneToMany,@ManyToMany。而@ManyToOne中没有。
@ManyToMany(mappedBy = “xxx”)表示xxx所对应的类为关系被拥有方,而关联的另一方为关系拥有方:
- 关系拥有方:对应拥有外键的数据库表
- 关系被拥有方:对应主键被子表引用为外键的数据库表
**举例:**
```java
//单向 多对多
@Entity
public class Student{
@ManyToMany(cascade = CascadeType.ALL)
private List courses;
//...
}
@Entity
public class Course{
//...
}
```
```java
//双向 多对多
@Entity
public class Student{
@ManyToMany(cascade = CascadeType.ALL)
private List courses;
//...
}
@Entity
public class Course{
@ManyToMany(cascade = CascadeType.ALL, mappedBy = "courses")
private List students;
//...
}
```
> 所有双向关联使用时需谨慎,查询时容易引起栈内存溢出,尽量使用单向关联
##### 3.6.7.12 @Enumerated
**作用:**
当[实体类](https://so.csdn.net/so/search?q=实体类&spm=1001.2101.3001.7020)中有枚举类型的属性时,默认情况下自动生成的数据库表中对应的字段类型是枚举的索引值,是数字类型的,若希望数据库中存储的是枚举对应的String类型,在属性上加入`@Enumerated(EnumType.STRING)`注解即可。
**举例:**
```java
@Enumerated(EnumType.STRING)
@Column(nullable = true)
private RoleEnum role;
```
#### 3.6.4 实现自动时间
> 对应的SQL语句:
>
> - `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
> - `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
>
> `create_time`根据自动封装创建时间、`update_time`自动更新为最新操作的时间。
##### 3.6.4.1 修改Entity 类
```JAVA
@EntityListeners(AuditingEntityListener.class)
public class User implements Serializable {
@CreatedDate
@Column(name = "create_time")
private Date createTime;
@LastModifiedDate
@Column(name = "update_time")
private Date updateTime;
}
```
##### 3.6.4.2 修改启动文件
```java
@EnableJpaAuditing
public class SpringBootJpaApplication { ... }
```
### 3.7 创建Dao文件
#### 3.7.1 Repository的继承关系

> 可能你会见到有的案例中`extends CrudRepository`等,其实都是可以的,翻看源码就知道,继承`JpaRepository`也可以使用`CrudRepository`的方法
#### 3.7.2 继承创建Dao文件
```java
public interface UserDao extends JpaRepository { }
```
> `JpaRepository`,要指定泛型类型,否则Spring注入这个Bean的时候发现该Bean泛型类型不定而注入失败。
### 3.8 内置的查询方法
#### 3.8.1 方法列表
**JpaRepository:**

**CrudRepository:**

1. 先按照功能进行分类整理,分为保存、删除、查找单个、查找多个、其他5类。
2. 将不建议使用的方法置灰,此类方法多为CrudRepository接口、PagingAndSortingRepository接口中定义,后来JpaRepository接口中又定义了替代方法,更方便使用,比如:查找多个对象时,返回 List 比返回 Iterable 更容易处理。

#### 3.8.2 编写service 方法
```java
// >>>>>>>>>>>>>>>>>>>>>>>>>>>> (interface) UserCrudService.java <<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.service;
import com.test.springboot.jpa.pojo.User;
import org.springframework.data.domain.Example;
import java.util.List;
public interface UserCrudService {
// ================================= count exists ==============================
/**
* 计数
* @return
*/
long countUser();
/**
* 判断用户是否存在
* @param userExample
* @return
*/
boolean existUser(Example userExample);
/**
* 通过主键判断记录是否存在
* @param userId
* @return
*/
boolean existByPrimaryKey(Long userId);
// ================================= select ==============================
/**
* 查询所有的记录
* @return
*/
List selectAllUser();
/**
* 根据主键查询记录
* @param userId
* @return
*/
User selectByPrimaryKey(Long userId);
/**
* 动态查询记录
* @param userExample
* @return
*/
List selectByUserExample(Example userExample);
// ================================= create ==============================
/**
* 添加一条记录
* @param user
* @return
*/
boolean insertUser(User user);
/**
* 批量添加记录
* @param userList
* @return
*/
boolean insertAllUsers(List userList);
// ================================= update ==============================
/**
* 更新记录
* @param user
* @return
*/
boolean updateUser(User user);
// ================================= delete ==============================
/**
* 删除表中所有的记录
* @return
*/
boolean deleteAll();
/**
* 删除指定主键的记录
* @param userId
* @return
*/
boolean deleteByPrimaryKey(Long userId);
/**
* 批量删除记录
* @param userList
* @return
*/
boolean deleteInBranch(List userList);
/**
* 动态删除记录
* @param user
* @return
*/
boolean deleteByUser(User user);
}
```
```java
// >>>>>>>>>>>>>>>>>>>>>>>>>>>> UserCrudServiceImpl.java <<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.service.impl;
import com.test.springboot.jpa.dao.UserDao;
import com.test.springboot.jpa.pojo.User;
import com.test.springboot.jpa.service.UserCrudService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
@Service
@Slf4j
public class UserCrudServiceImpl implements UserCrudService {
@Autowired
private UserDao userDao;
@Override
public long countUser() {
return userDao.count();
}
@Override
public boolean existUser(Example userExample) {
return userDao.exists(userExample);
}
@Override
public boolean existByPrimaryKey(Long userId) {
return userDao.existsById(userId);
}
@Override
public List selectAllUser() {
return userDao.findAll();
}
@Override
public User selectByPrimaryKey(Long userId) {
User user = null;
try {
user = userDao.findById(userId).get();
}catch (NoSuchElementException e){
log.info("java.util.NoSuchElementException: No value present:没有这条记录:====>>> user_id = {}",userId);
}
catch (Exception e) {
log.info("Exception: 有其他错误");
}
return user;
}
@Override
public List selectByUserExample(Example userExample) {
List userExampleList = userDao.findAll(userExample);
return userExampleList;
}
@Override
public boolean insertUser(User user) {
User createUser = null;
try {
createUser = userDao.save(user);
log.info("insert user info log ===>>> {}",createUser);
}catch (DataIntegrityViolationException e){
log.info("org.springframework.dao.DataIntegrityViolationException: 唯一值冲突");
}
catch (Exception e) {
log.info("Exception: 其他错误");
e.printStackTrace();
}
return createUser != null;
}
@Override
public boolean insertAllUsers(List userList) {
List createUserList = userDao.saveAll(userList);
log.info("insert into nums log ===>>> {}",createUserList.size());
return createUserList.size() != 0;
}
@Override
public boolean updateUser(User user) {
if (user.getUserId() == null){
log.info("没有主键,请执行 insert 操作");
return false;
}
User startUser = selectByPrimaryKey(user.getUserId());
userDao.save(user);
User updateUser = selectByPrimaryKey(user.getUserId());
return startUser != updateUser;
}
@Override
public boolean deleteAll() {
long startNum = countUser();
userDao.deleteAll();
userDao.flush();
long endNum = countUser();
return startNum != endNum;
}
@Override
public boolean deleteByPrimaryKey(Long userId) {
long startCount = userDao.count();
userDao.deleteById(userId);
long endCount = userDao.count();
return startCount != endCount;
}
@Override
public boolean deleteInBranch(List userList) {
long startCount = userDao.count();
userDao.deleteInBatch(userList);
long endCount = userDao.count();
return startCount != endCount;
}
/**
* TODO need to test
* @param user
* @return
*/
@Override
public boolean deleteByUser(User user) {
return false;
}
}
```
#### 3.8.3 编写test方法
使用Idea的话,在`UserDao.java`接口名前按`Alt + Enter`键(mac),快速生成测试类`UserDaoTest.java`
```JAVA
// >>>>>>>>>>>>>>>>>>>>>>>>>>>> UserCrudServiceImplTest.java <<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.service.impl;
import com.test.springboot.jpa.JpaApplicationTests;
import com.test.springboot.jpa.dao.UserDao;
import com.test.springboot.jpa.pojo.User;
import com.test.springboot.jpa.service.UserCrudService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class UserCrudServiceImplTest extends JpaApplicationTests {
@Autowired
private UserCrudService userCrudService;
@Autowired
private UserDao userDao;
@Test
public void countUser() {
long userCount = userCrudService.countUser();
log.info("count user number log ===>>> {}",userCount);
}
@Test
public void existUser(){
Long userId = 15L;
User user = userCrudService.selectByPrimaryKey(userId);
Example userExample = Example.of(user);
boolean flag = userCrudService.existUser(userExample);
log.info("user is exist flag by user log ===>>> {}",flag);
}
@Test
public void existByPrimaryKey(){
Long userId = 75L;
boolean flag = userCrudService.existByPrimaryKey(userId);
log.info("user is exist by primary key flag log ===>>> {}",flag);
}
@Test
public void selectAllUser() {
List userList = userCrudService.selectAllUser();
userList.forEach(user -> {
log.info("select all user log ===>>> {}",user);
});
}
@Test
public void selectByPrimaryKey() {
Long user_id = 1L;
User user = userCrudService.selectByPrimaryKey(user_id);
if(user != null){
log.info("select user by primary key log ===>>> {}",user);
}
}
@Test
public void selectByUserExample() {
User user1 = new User();
user1.setUserName("caojurfsdan");
Example userExample = Example.of(user1);
List userList = userCrudService.selectByUserExample(userExample);
userList.forEach(user -> {
log.info("select all user log ===>>> {}",user);
});
}
@Test
public void insertUser() {
User user = new User();
user.setUserName("cao78j3un");
user.setUserPassword("java");
boolean flag = userCrudService.insertUser(user);
log.info("create user log ===>>> {}",flag);
}
@Test
public void insertAllUsers() {
List userList = new ArrayList<>();
userList.add(new User("java12","language"));
userList.add(new User("java23","language"));
userList.add(new User("java313","language"));
boolean flag = userCrudService.insertAllUsers(userList);
System.out.println(flag);
}
@Test
public void updateUser() {
User user = new User(1L,"chenggong","jkflsd");
boolean flag = userCrudService.updateUser(user);
log.info("update user flag log ===>>> {}",flag);
User updateUser = userCrudService.selectByPrimaryKey(user.getUserId());
log.info("update user info log ===>>> {}",updateUser);
}
@Test
public void deleteAll() {
boolean flag = userCrudService.deleteAll();
log.info("delete all user flag log ===>>> {}",flag);
}
@Test
public void deleteByPrimaryKey() {
User user = new User(12L);
boolean flag = userCrudService.deleteByPrimaryKey(user.getUserId());
log.info("delete by primary key flag log ===>>> {}",flag);
}
@Test
public void deleteInBranch() {
List userList = new ArrayList<>();
userList.add(new User(13L));
userList.add(new User(14L));
boolean flag = userCrudService.deleteInBranch(userList);
log.info("delete in branch flag log ===>>> {}",flag);
}
@Test
public void deleteByUser() {
}
}
```
### 3.9 自定义简单查询
自定义简单查询就是根据方法名自动生成SQL。前面是用JPA内置的方法进行简单的CRUD,下面可以自定义方法名实现自定义SQL:主要语法是:`findByXX`,`readByXX`,`queryByXX`,`countByXX`。`XX`代表属性名。
#### 3.9.1 常用关键字
基本上SQL体系中的关键字在JPA上都有对应的,具体的关键字和使用方法生成的SQL如下表:
| 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) |
| top | findTop100 | top 10/where ROWNUM <=10 |
#### 3.9.2 编写dao文件
```java
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> UserDao.java <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.dao;
import com.test.springboot.jpa.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserDao extends JpaRepository {
// ================================ 自定义简单查询 =================================
/*
主要语法:(……代表属性名)
* findBy……
* readBy……
* queryBy……
* countBy……
其他关键字:(具体参考文档)
And
Or
Is,Equals
Between
……
*/
/**
* 根据密码查询记录
* @param userPassword 用户密码
* @return 用户列表
*/
List findByUserPassword(String userPassword);
/**
* 根据密码删除记录
* @param userPassword 用户密码
* @return 删除的数目
*/
void deleteByUserPassword(String userPassword);
/**
* 根据密码统计记录
* @param userPassword 用户密码
* @return 统计数
*/
long countByUserPassword(String userPassword);
}
```
#### 3.9.3 编写service 方法
```java
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> UserEasyService.java <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.service;
import com.test.springboot.jpa.pojo.User;
import java.util.List;
public interface UserEasyService {
/**
* 根据密码查询记录
* @param userPassword 用户密码
* @return 用户列表
*/
List findByUserPassword(String userPassword);
/**
* 根据密码删除记录
* @param userPassword 用户密码
* @return 删除的数目
*/
void deleteByUserPassword(String userPassword);
/**
* 根据密码统计记录
* @param userPassword 用户密码
* @return 统计数
*/
long countByUserPassword(String userPassword);
}
```
```java
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>> UserEasyServiceImpl.java <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.service.impl;
import com.test.springboot.jpa.dao.UserDao;
import com.test.springboot.jpa.pojo.User;
import com.test.springboot.jpa.service.UserEasyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
@Service
public class UserEasyServiceImpl implements UserEasyService {
@Autowired
private UserDao userDao;
@Override
public List findByUserPassword(String userPassword) {
return userDao.findByUserPassword(userPassword);
}
@Override
@Transactional
public void deleteByUserPassword(String userPassword) {
userDao.deleteByUserPassword(userPassword);
}
@Override
public long countByUserPassword(String userPassword) {
return userDao.countByUserPassword(userPassword);
}
}
```
#### 3.9.4 编写test方法
```java
// >>>>>>>>>>>>>>>>>>>>>>>>> UserEasyServiceImplTest <<<<<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.service.impl;
import com.test.springboot.jpa.JpaApplicationTests;
import com.test.springboot.jpa.pojo.User;
import com.test.springboot.jpa.service.UserEasyService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import javax.transaction.Transactional;
import java.util.List;
import static org.junit.Assert.*;
@Slf4j
public class UserEasyServiceImplTest extends JpaApplicationTests {
@Autowired
private UserEasyService userEasyService;
@Test
public void findByUserPassword() {
List userList = userEasyService.findByUserPassword("NVOWM1");
userList.forEach(user -> {
log.info("Easy find user list by password ===>>> {}",user);
});
}
@Test
public void deleteByUserPassword() {
String userPassword = "I2VYDC";
userEasyService.deleteByUserPassword(userPassword);
List userList = userEasyService.findByUserPassword(userPassword);
}
@Test
public void countByUserPassword() {
String userPassword = "S4KJ72";
long countUser = userEasyService.countByUserPassword(userPassword);
log.info("count user by password ===>>> {}",countUser);
}
}
```
### 3.10 自定义SQL查询
1. 使用自定义SQL查询用`@Query(sql)`即可,如果涉及更新、添加、删除(事务)操作需要再添加`@Modifying`注解。也可以根据需要添加`@Transactional`注解对事务的支持和、查询超时的设置等。
2. 之前在用`PageHelper`的时候,默认第一页是`1`,这样查询出来就是数据库第一页的内容。**But**,使用了JPA发现它的页码是从`0`开始的,也就是说0=第一页、1=第二页…
这和PageHelper差别还是蛮大的,若:数据库一共两条记录,你查询`new PageRequest(1,3)`得到的`content`是`[]`
#### 3.10.1 编写dao文件
```java
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> UserDao.java <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.dao;
import com.test.springboot.jpa.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserDao extends JpaRepository {
// ================================= 自定义 SQL 查询 =================================
/*
使用自定义SQL查询用@Query(sql)即可
如果涉及更新、添加、删除(事务)操作需要再添加@Modifying注解。
也可以根据需要添加@Transactional注解对事务的支持和、查询超时的设置等。
*/
/**
* 根据用户的密码模糊查询:查询以0开头的文件
* query 的参数,采用Java的entity 实体类
* @param likePassword
* @return
*/
@Query("select u from User u where u.userName like ?1")
List findByLikePassword(String likePassword);
}
```
#### 3.10.2 编写service方法
```java
// >>>>>>>>>>>>>>>>>>>>>>>>>>>> UserComplexService.java <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.service;
import com.test.springboot.jpa.pojo.User;
import org.springframework.data.domain.Page;
import java.util.List;
public interface UserComplexService {
/**
* 自定义分页查询
* @param pageCode 当前页数
* @param pageSize 每一页显示记录数
* @param sortName 排序的字段名
* @return
*/
Page selectUserByPage(int pageCode, int pageSize, String sortName);
/**
* 通过
* @param likePassword 模糊查询的密码
* @return
*/
List selectUsersByLikePassword(String likePassword);
}
```
```java
// >>>>>>>>>>>>>>>>>>>>> UserComplexServiceImpl.java <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.service.impl;
import com.test.springboot.jpa.dao.UserDao;
import com.test.springboot.jpa.pojo.User;
import com.test.springboot.jpa.service.UserComplexService;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Service;
import java.util.List;
@Slf4j
@Service
public class UserComplexServiceImpl implements UserComplexService {
@Autowired
private UserDao userDao;
@Override
public Page selectUserByPage(int pageCode, int pageSize, String sortName) {
Sort sort = new Sort(Sort.Direction.ASC, sortName);
Pageable pageable = new PageRequest(pageCode, pageSize, sort);
return userDao.findAll(pageable);
}
@Override
public List selectUsersByLikePassword(String likePassword) {
return userDao.findByLikePassword(likePassword);
}
}
```
#### 3.10.3 编写test方法
```java
// >>>>>>>>>>>>>>>>>>>>>>>>>> UserComplexServiceImplTest.java <<<<<<<<<<<<<<<<<<<<<<<<<<<
package com.test.springboot.jpa.service.impl;
import com.test.springboot.jpa.JpaApplicationTests;
import com.test.springboot.jpa.pojo.User;
import com.test.springboot.jpa.service.UserComplexService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import java.util.List;
import static org.junit.Assert.*;
@Slf4j
public class UserComplexServiceImplTest extends JpaApplicationTests {
@Autowired
private UserComplexService userComplexService;
@Test
public void selectUserByPage() {
int pageCode = 0;
int pageSize = 20;
// 使用Entity 中的字段
String sortName = "userId";
Page userPage = userComplexService.selectUserByPage(pageCode, pageSize, sortName);
log.info("总记录数 ===>>> {}",userPage.getTotalElements());
log.info("总页数 ===>>> {}",userPage.getTotalPages());
// log.info("记录 ===>>> {}",userPage.getContent());
userPage.forEach(user -> {
log.info("记录 ===>>> {}",user);
});
}
@Test
public void selectUsersByLikePassword(){
String likePassword = "0";
List userList = userComplexService.selectUsersByLikePassword(likePassword);
userList.forEach(user -> log.info("select user by like password log ===>>> {}",user));
}
}
```
### 3.11 多表查询
实在想吐槽,JPA这样对多表查询的支持太差了,**多表查询**就必须手动写SQL,然后放在`@Query`注解中,这样SQL真的很难看,而且需要额外遵循JPA的规范。
## 4 常见问题
### 4.1自定义简单查询,delete方法报错
> 问题描述
**问题代码**
```java
// ==== UserDao.java ====
/**
* 根据密码删除记录
* @param userPassword 用户密码
* @return 删除的数目
*/
void deleteByUserPassword(String userPassword);
// ==== UserEasyServiceImpl.java ====
@Override
public void deleteByUserPassword(String userPassword) {
userDao.deleteByUserPassword(userPassword);
}
// ==== UserEasyServiceImplTest.java ====
@Test
public void deleteByUserPassword() {
String userPassword = "I2VYDC";
userEasyService.deleteByUserPassword(userPassword);
// List userList = userEasyService.findByUserPassword(userPassword);
}
```
**运行错误信息**
```bash
org.springframework.dao.InvalidDataAccessApiUsageException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call
```
> 问题分析
除了query外的modiy和delete如果各层的方法中没有进行事务管理的话(没加@Transactional)话会报错
在对应的Service上添加@Transactional注解即可。
> 参考网站
1. [org.springframework.dao.InvalidDataAccessApiUsageException: No EntityManager with actual transaction_1](http://t.csdn.cn/C8n0M)
2. [org.springframework.dao.InvalidDataAccessApiUsageException: No EntityManager with actual transaction_2](http://t.csdn.cn/kbMaE)
## 5. 联系地址
gitee:[gitee.com/salute83/springboot-jpa](https://gitee.com/salute83/springboot-jpa.git)
docsift:[docsify/83_start_docks/springbot-jpa](https://83start.github.io/83_start_docs/#/./Springboot/Springboot_Jpa)