本文源码:Gitee·点这里
参考
引入包
引入jpa
的包,同时引入mysql
包和test
包
<dependencys>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencys>
配置文件
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot-familay?charset=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
hikari:
maximum-pool-size: 32
minimum-idle: 8
jpa:
show-sql: true
hibernate:
ddl-auto: update
-
spring.jpa.show-sql=true
配置是否打印执行的SQL语句 -
spring.jpa.hibernate.ddl-auto=update
配置程序启动时自动创建数据库的类型(注意:此配置很危险,禁止在生产中使用)-
create
程序重启时先删除表,再创建表 -
create-drop
程序重启时与create
一样;程序结束时删除表 -
update
程序启动时若没有表格会创建,表内有数据不会清空,只会更新 -
validate
程序重启时会校验实体类与数据库中的字段是否相同,不同则报错
-
实体类
代码省略了get/set方法
@Entity
@Table(name = "AUTH_USER")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 32)
private String name;
@Column(length = 32, unique = true)
private String account;
@Column(length = 63)
private String pwd;
@Column
private Date createTime;
}
-
@Entity
声明此类为实体类 -
@Table
声明该实体类对应数据库中的表信息-
name
指定表名,若不指定使用类名
-
-
@Id
声明实体属性为表主键 -
@GeneratedValue
主键生成策略,通过strategy
属性指定-
TABLE
使用数据库表来模拟序列产生主键 -
SEQUENCE
根据底层数据库的序列来生成主键,通过@SequenceGenerator
注解指定序列名,条件是数据库支持序列 -
IDENTITY
使用数据库ID自增主键,条件是数据库支持 -
AUTO
默认,JPA根据数据库类型自动选择适合的策略
-
-
@Column
声明实体属性的表字段定义-
name
指定字段名,不指定则使用该实体属性名 -
unique
唯一索引,默认false
-
nullable
可为空,默认true
-
length
长度,默认255 -
precision
精度,默认0 -
columnDefinition
该字段的SQL片段,示例:@Column(name = "account",columnDefinition = "varchar(32) not null default'' unique")
-
以上创建好后,程序启动后会自动在数据库中创建表信息
持久层
定义持久层接口
JPA中实现持久层非常简单,创建一个接口,并继承 org.springframework.data.jpa.repository.JpaRepository<T, ID>
,T
为实体类,ID
为实体类主键类型;最后在接口上增加 @Repository
注解
@Repository
public interface UserDao extends JpaRepository<User, Long> {
}
JpaRepository
继承了CrudRepository
,提供了基本的CRUD
功能
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity); //(1)
Optional<T> findById(ID primaryKey); //(2)
Iterable<T> findAll(); //(3)
long count(); //(4)
void delete(T entity); //(5)
boolean existsById(ID primaryKey); //(6)
// … more functionality omitted.
}
- (1) 保存给定的实体,若已存在则更新。
- (2) 返回由给定 ID 标识的实体。
- (3) 返回所有实体。
- (4) 返回实体数。
- (5) 删除给定的实体。
- (6) 指示是否存在具有给定 ID 的实体。
JpaRepository
继承了PagingAndSortingRepository
,提供分页和排序功能
public interface PagingAndSortingRepository<T, ID extends Serializable>
extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
定义查询方法
JPA可以通过方法名称派生查询
- 该机制从方法中剥离前缀
find…By
、read…By
、query…By
、count…By
和get…By
,并解析剩余部分 - 可以包含其他表达式,如
Distinct
- 可以使用
And
和Or
进行串联
官方示例:
interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
方法名称中支持的关键字
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 |
findByFirstname , 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 (参数附加了% ) |
EndingWith |
findByFirstnameEndingWith |
… where x.firstname like ?1 (参数与前缀% 绑定) |
Containing |
findByFirstnameContaining |
… where x.firstname like ?1 (参数绑定在% 中) |
OrderBy |
findByAgeOrderByLastnameDesc |
… where x.age = ?1 order by x.lastname desc |
Not |
findByLastnameNot |
… where x.lastname <> ?1 |
In |
findByAgeIn(Collection<Age> ages) |
… where x.age in ?1 |
NotIn |
findByAgeNotIn(Collection<Age> ages) |
… 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) |
查询返回类型
Return type | Description |
---|---|
void |
表示没有返回值。 |
Primitives | Java primitives. |
Wrapper types | Java 包装器类型。 |
T |
唯一实体。期望查询方法最多返回一个结果。如果未找到结果,则返回null 。一个以上的结果触发IncorrectResultSizeDataAccessException 。 |
Iterator<T> |
一个Iterator 。 |
Collection<T> |
A Collection 。 |
List<T> |
A List 。 |
Optional<T> |
Java 8 或 Guava Optional 。期望查询方法最多返回一个结果。如果未找到结果,则返回Optional.empty() 或Optional.absent() 。多个结果触发IncorrectResultSizeDataAccessException 。 |
Option<T> |
Scala 或 Javaslang Option 类型。语义上与前面描述的 Java 8 的Optional 相同。 |
Stream<T> |
Java 8 Stream 。 |
Future<T> |
A Future 。期望使用@Async Comments 方法,并且需要启用 Spring 的异步方法执行功能。 |
CompletableFuture<T> |
Java 8 CompletableFuture 。期望使用@Async Comments 方法,并且需要启用 Spring 的异步方法执行功能。 |
ListenableFuture |
A org.springframework.util.concurrent.ListenableFuture 。期望使用@Async Comments 方法,并且需要启用 Spring 的异步方法执行功能。 |
Slice |
一定大小的数据块,用于指示是否有更多可用数据。需要Pageable 方法参数。 |
Page<T> |
Slice 以及其他信息,例如结果总数。需要Pageable 方法参数。 |
GeoResult<T> |
具有附加信息(例如到参考位置的距离)的结果条目。 |
GeoResults<T> |
GeoResult<T> 列表以及其他信息,例如到参考位置的平均距离。 |
GeoPage<T> |
Page 和GeoResult<T> ,例如到参考位置的平均距离。 |
Mono<T> |
使用 Reactive 存储库的 Project Reactor Mono 发出零或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回Mono.empty() 。多个结果触发IncorrectResultSizeDataAccessException 。 |
Flux<T> |
Project Reactor Flux 使用 Reactive 存储库发出零,一个或多个元素。返回Flux 的查询也可以发出无限数量的元素。 |
Single<T> |
使用 Reactive 存储库发出单个元素的 RxJava Single 。期望查询方法最多返回一个结果。如果未找到结果,则返回Mono.empty() 。多个结果触发IncorrectResultSizeDataAccessException 。 |
Maybe<T> |
使用 Reactive 存储库的 RxJava Maybe 发出零或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回Mono.empty() 。多个结果触发IncorrectResultSizeDataAccessException 。 |
Flowable<T> |
RxJava Flowable 使用 Reactive 存储库发出零个,一个或多个元素。返回Flowable 的查询也可以发出无限数量的元素。 |
异步查询结果
我们在这里重点讲一下异步查询结果。
JPA查询结果支持异步,这意味着该方法在调用时立即返回,而实际查询的执行已提交给Spring的TaskExecutor
的任务中。
官方示例:
interface PersonRepository extends Repository<User, Long> {
@Async
Future<User> findByFirstname(String firstname); //(1)
@Async
CompletableFuture<User> findOneByFirstname(String firstname); //(2)
@Async
ListenableFuture<User> findOneByLastname(String lastname); //(3)
}
- (1) 使用
java.util.concurrent.Future
作为返回类型。 - (2) 使用 Java 8
java.util.concurrent.CompletableFuture
作为返回类型。 - (3) 使用
org.springframework.util.concurrent.ListenableFuture
作为返回类型。
使用SQL语句查询
JPA自定义查询语句非常简单,使用@Query
注解即可
public interface UserDao extends JpaRepository<User, Long> {
@Query("select u from User u where u.account = ?1")
User selectByAccount(String account);
@Transactional
@Modifying
@Query("update User u set u.pwd = ?2 where u.id = ?1")
int updatePassword(Long id, String password);
}
- SQL语句中使用的是实体类名和实体属性,而不是表名和表字段
- 如果使用update/delete,需要加上
@Transactional
和@Modifying
实体关系映射
JPA 实体关系映射:@OneToOne 一对一关系、@OneToMany @ManyToOne 一对多和多对一关系、@ManyToMany 多对多关系。
OneToOne
一对一关系,即两个表中的数据是一一对应关系,这种主要应用到对象的扩展信息中。
用户实体类:
@Entity
@Table(name = "AUTH_USER")
public class User {
/*
其余代码省略
*/
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user")
private UserDetail userDetail;
}
用户详情实体类:
@Entity
@Table(name = "AUTH_USER_DETAIL")
public class UserDetail {
/*
其余代码省略
*/
@OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
private User user;
}
-
mappedBy
加在了User
实体为的userDetail
属性上,并指向user
表示:在UserDetail
实体类映射的表中创建关系字段(user_id),并设为外键,指向User
实体类映射的表中主键 -
UserDetail
实体类的user
属性的CascadeType
为PERSIST
表示:删除用户详情时,不级联删除用户信息
OneToMany/ManyToOne
一对多关系即一个表中的一行数据关联另外一个表中的多行数据,多对一与之相反。
用户实体类:
@Entity
@Table(name = "AUTH_USER")
public class User {
/*
其余代码省略
*/
@ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
private Company company;
}
公司实体类:
@Entity
@Table(name = "AUTH_COMPANY")
public class Company {
/*
其余代码省略
*/
@OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY, mappedBy = "company")
private Set<User> users = new HashSet<>();
}
-
@mappedBy
只能出现在@OneToMany
注解中 - 会在
User
实体类对应映射表中创建一个外键(company_id),指向Company
实体类对应映射表的主键 - 两边最好不要做级联删除,
cascade
设为PERSIST
ManyToMany
多对多关系即其中一个表中的一行,与另一表中的多行关联,比如本示例中用户和角色的关系。
用户实体类:
@Entity
@Table(name = "AUTH_USER")
public class User {
/*
其余代码省略
*/
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Role> roles = new HashSet<>();
}
角色实体类:
@Entity
@Table(name = "AUTH_ROLE")
public class Role {
/*
其余代码省略
*/
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "roles")
private Set<User> users = new HashSet<>();
}
- 会建立一个中间表,包含两个实体类的主键关系
-
mappedBy
指向哪一个都可以,中途不要修改
网友评论