SpringBoot整合JPA使用

SpringBoot整合JPA使用

整合JPA

SpringBoot整合JPA十分方便,在Pom中添加如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 数据库配置,使用druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>

JPA基本配置

在application.properties 或者 application.yaml 文件中配置数据库链接和JPA的基本配置(本例使用yaml文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
druid:
initial-size: 5
max-active: 20
max-wait: 60000
stat-view-servlet:
login-password: admin
login-username: admin
password: 123456
url: jdbc:mysql://127.0.0.1:3306/web?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
jpa:
# 配置创建表使用的SQLDialect
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
# 自动更新数据库表
hibernate:
ddl-auto: update
properties:
hibernate:
# 使用@Query时SQL的dialect方言
dialect: org.hibernate.dialect.MySQL5InnoDBDialect

使用JPA

在SpringBoot中使用JPA十分简单,只要引用JPA的starter即可

实体类中属性

使用Spring Data JPA,与Mybatis不同,更多的是面向对象查询.所以构造对象实体类就非常重要,如果配置了自动更新表结构,就需要注意如何在实体中使用特定的注解完成对实体的配置.

1. @Entity

使用 @Entity 注解在类上,用来表示该类为一个实体类

2. @Table

使用 @Table 注解标注在类上, 可以指定使用 @Entity 注解标注的类所生成的数据库表信息,如表名称等.

1
2
3
4
5
@Entity // 指定这个类为实体类
@Table(name="user") // 指定@Entity标注的主表
public class User implements Serializable {
// ...
}

3. @Id,@Column

使用 @Id 注解标注在变量或者set方法上,用来标注该变量为主键ID.

使用 @Column 注解同样标注在变量上,能够设置生成表的各种详细信息,如字段名称,字段长度等等…

1
2
3
4
5
6
7
8
@Id
@Column(columnDefinition = "INT(11)")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;

@Column(name = "is_deleted",columnDefinition = "TINYINT(1)",nullable =
false)
private Short isDeleted=0;

4. @GeneratedValue

使用 @GeneratedValue 注解标注在 和 @Id 相同的位置,用来表示主键ID的生成策略.该注解有两个属性,第一个是常用到的 strategy 即生成策略,第二个是不常用到的generator指定生成器.

strategy

GenerationType 包含四种策略:

  • TABLE,使用特定的表来存储生成的主键,也就是说会自动生成一种记录主键的表,一般会包含两个字段,第一个字段是字段生成策略的名称,第二个是ID的最大序列值.通常会和 @TableGenerator 一起来使用,能够指定特定表来生成主键,如果不指定,会默认生成一个名称为 sequence 的表来记录.
  • SEQUENCE,在特定的数据库,如ORACLE,不支持自增主键,但是会提供一种叫做序列的方式生成主键,此时就需要指定 SEQUENCE 为生成主键的策略,和TABLE相似,通常会使用 @SequenceGenerator 一起使用
  • IDENTITY ,遇到向MYSQL能够让主键自增的数据库,就可以指定生成策略为IDENTITY,生成表后, Mysql会默认将该字段设置为 ‘auto_increment’,
  • AUTO ,使用 @GeneratedValue 默认的生成策略,把具体生成主键的规则交给持久化引擎来实现,也是我使用最多的一种方式.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用 GenerationType.TABLE 指定生成的记录表,其中name必须,其他可选
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "roleSeq")
@TableGenerator(name = "roleSeq", allocationSize = 1, table = "seq_table", pkColumnName = "seq_id", valueColumnName = "seq_count")
private Integer id;
// 使用 SEQUENCE
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "menuSeq")
@SequenceGenerator(name = "menuSeq", initialValue = 1, allocationSize = 1, sequenceName = "MENU_SEQUENCE")
private Integer id;
// 使用 IDENTITY 进行自增
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// 使用AUTO,或者不指定策略
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id
generator

通过上面对策略的描述,也就明白generator的作用,就是指定特定name的Generator

5. @Transient

使用 @Transient 注解,标注在字段上,在JPA生成表字段时能够忽略该字段,让字段不出现在数据库中.

6. @Embedded和@Embeddable

当实体A出现在实体B中,但实体A不需要单独生成一张表的使用,使用@@Embedded和@Embeddable标注在类上,能够防止实体A生成数据库表.

7. @Temporal

使用 @Temporal 注解特殊标记在需要持久化的字段上,并且字段类型为 java.util.Datejava.util.Calendar的时候,可指定 TemporalType 来生成对应数据库中的时间格式(java.sql.xxxx).

8. 一对一,一对多,多对多关系

在使用关系型数据库中,免不了上述关系的生成,下面就来说一下,如何针对一对一关系、一对多关系、多对多关系的通用设置,在分别实验前,先了解一下 @JoinColumn 的作用

我认为的JoinColumn是特殊的@Column,当需要使用到“关系”时,需要使用到JoinColumn来替代普通的Column注解

OneToOne

使用OneToOne注解,指定两方关系是一对一的.比如User和Status,Status是User的一个属性,在User表中只需记录Stauts表中的主键,所以需要在User表(主)中声明一个Status(从)即可:

1
2
3
@OneToOne()
@JoinColumn(columnDefinition = "INT(11)",name = "status_id")
private Status status;
OneToMany、ManyToOne

使用 @OneToMany 或 @ManyToOne 注解来表示一对多、多对一的关系.典型的如Project之于Task,一个Project包含多个Task,所以Project为One,Task为Many.

如果在Project(主)中声明Task(从)时,可以使用 OneToMany 表示一对多关系

1
2
3
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name="project_id") // task表指向project表的外键
Set<Task> tasks;

也可以在多的一方使用 ManyToOne 注解,表示多对一的关系

1
2
3
4
@ManyToOne
// 在 one 的一方生成 name为 project_id 的字段,指向task
@JoinColumn(name="project_id")
Project project;

以上是单独使用 @OneToMany 或 @ManyToOne 构造单向的关系,但是这样有缺点,注定有一方找不到另外一方(只建立了单向关系).所以稳妥的方式是构建双向关系,在One和Many双方,使用两个注解相互指定

1
2
3
4
5
6
7
8
// Project中,其中mappedBy是双向关系中必须要使用的属性
@OneToMany(cascade = CascadeType.ALL,mappedBy ="project_id")
Set<Task> tasks;

// Task中
@ManyToOne
@JoinColumn(name="project_id")
Project project
ManyToMany

使用 @ManyToMany 注解来标注多对多关系,比如用户和角色之间就可以理解为多对多的关系,双方需要互相拥有多个.多地多关系需要使用中间表来维护两方关系.建立关系表使用@JoinTable注解来指定实现.

1
2
3
4
5
6
7
8
9
10
11
12
// User表中
@JSONField(name = "roles")
@ManyToMany(mappedBy = "users",fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private Set<Role> roles;

// Role 表中
@JSONField(serialize = false)
@ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
@JoinTable(name = "r_user_role",
joinColumns = {@JoinColumn(name = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "user_id")})
private List<User> users;
关系使用时的坑
  1. 双向关系需要双方都进行维护,否则保存不上 :)
1
2
3
4
User user = one.get();
// 多对多关系需要双向关联保存才起作用
user.getRoles().add(role);
role.getUsers().add(user);
  1. 使用JSON时,如果出现循环引用导出溢出时,在一方加入设置防止序列化
1
2
3
4
5
6
7
// 我使用的是FastJosn,Jackson有不同的实现方式
@JSONField(serialize = false)
@ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
@JoinTable(name = "r_user_routemeta",
joinColumns = {@JoinColumn(name = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "meta_id")})
private Set<RouteMeta> routeMeta;

使用 Repository 查询

Repository 是 Spring Data JPA 中最重要的接口,使用实体类和实体类中的主键ID作为类型参数.其中CURDRepository为实体类提供了复杂增删改查的函数.

1. CrudRepository接口

1
public interface CrudRepository<T, ID> extends Repository<T, ID> {}

同样提供了特定的Repository,比如JPARepository、MongoRepository,这几个都是继承的 CrudRepository 接口,根据不同的技术特性暴露不同的接口方法.一般使用上直接继承CurdRepository就可以完成基本的工作.

2. PagingAndSortingRepository接口

如果使用分页或者排序需要继承 PagingAndSortingRepository 接口,这个接口中包含 Iterable<T> findAll(Sort sort);Page<T> findAll(Pageable pageable);,这样能够具有查询时分页的特性

3. 接口中的名称推导查询

  • 通过命名查询,具体可查看相关文档,写的非常全 create-query
关键字 方法命名 sql where字句
And findByNameAndPwd where name= ? and pwd =?
Or findByNameOrSex where name= ? or sex=?
Is,Equals findById,findByIdEquals where id= ?
Between findByIdBetween where id between ? and ?
LessThan findByIdLessThan where id < ?
LessThanEquals findByIdLessThanEquals where id <= ?
GreaterThan findByIdGreaterThan where id > ?
GreaterThanEquals findByIdGreaterThanEquals where id > = ?
After findByIdAfter where id > ?
Before findByIdBefore where id < ?
IsNull findByNameIsNull where name is null
isNotNull,NotNull findByNameNotNull where name is not null
Like findByNameLike where name like ?
NotLike findByNameNotLike where name not like ?
StartingWith findByNameStartingWith where name like ‘?%’
EndingWith findByNameEndingWith where name like ‘%?’
Containing findByNameContaining where name like ‘%?%’
OrderBy findByIdOrderByXDesc where id=? order by x desc
Not findByNameNot where name <> ?
In findByIdIn(Collection<?> c) where id in (?)
NotIn findByIdNotIn(Collection<?> c) where id not in (?)
True findByAaaTue where aaa = true
False findByAaaFalse where aaa = false
IgnoreCase findByNameIgnoreCase where UPPER(name)=UPPER(?)

4. 使用 Repository 查询实例

  1. 声明一个接口继承特定的Repository
1
2
3
4
5
6
/**
* JpaRepository 继承自 PagingAndSortingRepository,其中类型参数
* 第一个是操作实体类的类型
* 第二个是操作实体类的标识ID
**/
public interface UserRepository extends JpaRepository<User,Integer> {}
  1. (可选)在接口中定义方法
1
2
// 具体名称推导查询可以查看上面的表格
List<User> findByLastname(String lastname);
  1. 使用@AutoWire注解inject到特定的service中调用
1
2
3
4
5
6
@Autowired
UserRepository userRepository;
@Override
public User getUserFromUserName(String name) {
return userRepository.findUserByName(name);
}

到这里,增删改查基本都能够实现,复杂的在于分页、多条件复杂查询.

5. 使用 PagingAndSortingRepository 进行分页查询

前面说到 PagingAndSortingRepository 提供了两个方法,一个是返回Iterable的 findAll(Sort) 方法,另一个是返回Page的findAll方法

  1. 在没有其他查询条件的情况下,直接定义一个Pageable变量,然后使用特定方法即可:
1
2
3
4
5
@Override
public Page<Book> findBookNoCriteria(Integer page,Integer size) {
Pageable pageable = new PageRequest(page, size, Sort.Direction.ASC, "id");
return bookRepository.findAll(pageable);
}
  1. 在存在多样的查询条件的情况下,还需要接口继承 JpaSpecificationExecutor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Page<Book> findBookCriteria(Integer page, Integer size, final BookQuery bookQuery) {
Pageable pageable = new PageRequest(page, size, Sort.Direction.ASC, "id");
Page<Book> bookPage = bookRepository.findAll(new Specification<Book>(){
@Override
public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
Predicate p1 = criteriaBuilder.equal(root.get("name").as(String.class), bookQuery.getName());
Predicate p2 = criteriaBuilder.equal(root.get("isbn").as(String.class), bookQuery.getIsbn());
Predicate p3 = criteriaBuilder.equal(root.get("author").as(String.class), bookQuery.getAuthor());
query.where(criteriaBuilder.and(p1,p2,p3));
return query.getRestriction();
}
},pageable);
return bookPage;
}

使用@Query语句查询

尽管通过上面的方法能够非常方便快捷的使用查询,但是有时候包含特殊字段的查询或者使用名称组合出来的方法名又长有丑…而且担心效率低,这时候JPA提供了一种类似Hibernate的方法,就是使用@Query进行查询

1
2
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);

或者使用原生SQL查询,将Query的参数nativeQuery设置为true

1
2
@Query(nativeQuery = true,value = "select u.name from user u where u.id:id limit 1")
String getUserNameById(Integer id);

使用QueryDSL进行复杂查询

如果上面的查询方式仍然满足不了需求,那么可以尝试使用QueryDSL进行查询 QueryDSL

1. 过程

  1. 添加MAVEN依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
   <dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>

<!-- 添加APT PLUGIN -->

<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>

JPAAnnotationProcessor 会查找所有的 Entity 注解标注的实体类在目标路径下生成 Qxxx 的类.第一次需要使用 maven install 生成一下.

  1. 使用查询
1
2
3
4
5
6
7
8
9
/**
* 注入 jpaQueryFactory
* @param entityManager
* @return
*/
@Bean
public JPAQueryFactory jpaQueryFactory(EntityManager entityManager){
return new JPAQueryFactory(entityManager);
}

在配置文件中使用@Bean把JPAQueryFactory注入到容器中,然后使用JpaQueryFactory查询.

1
2
3
4
5
6
7
8
9
10
private List<User> doGetrolePerson(Integer role,boolean in,List<User> users){
QUser user = QUser.user;
Predicate predicate = user.isNotNull().or(user.isNull());
if(in){
predicate = role == null ? predicate : ExpressionUtils.and(predicate, user.roles.any().id.in(role));
}else{
predicate = role == null ? predicate : ExpressionUtils.and(predicate, user.notIn(users));
}
return jpaQueryFactory.selectFrom(user).where(predicate).fetch();
}

以上是使用基本的QueryDSL进行查询的方式,更多的增删改查的方法可以参考上面引用的文档,这里不在过多描述.

2. 结合查询

SpringDataJPA 对 QueryDSL 提供了一个通用的Repository – QuerydslPredicateExecutor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface QuerydslPredicateExecutor<T> {
Optional<T> findOne(Predicate var1);

Iterable<T> findAll(Predicate var1);

Iterable<T> findAll(Predicate var1, Sort var2);

Iterable<T> findAll(Predicate var1, OrderSpecifier<?>... var2);

Iterable<T> findAll(OrderSpecifier<?>... var1);

Page<T> findAll(Predicate var1, Pageable var2);

long count(Predicate var1);

boolean exists(Predicate var1);
}

使用需要让定义的Repository继承这个QuerydslPredicateExecutor,然后调用方法即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Page<User> findUsers(Integer offset, Integer limit, String order, String search, Integer status, Integer sex, Integer role) throws Exception {
Sort sort = null;
String propertie = order.substring(1);
if (order.startsWith("-")) {
sort = Sort.by(propertie).descending();
} else if (order.startsWith("+")) {
sort = Sort.by(propertie).ascending();
}
if(offset == null || limit == null){
offset = 1;
limit = Integer.MAX_VALUE;
}
PageRequest pageRequest = new PageRequest(offset - 1, limit, sort);
QUser user = QUser.user;
com.querydsl.core.types.Predicate predicate = user.isNotNull().or(user.isNull());
predicate = search == null ? predicate : ExpressionUtils.and(predicate, user.name.like("%" + search + "%"));
predicate = sex == null ? predicate : ExpressionUtils.and(predicate, user.sex.id.eq(sex));
predicate = status == null ? predicate : ExpressionUtils.and(predicate, user.status.id.eq(status));
predicate = role == null ? predicate : ExpressionUtils.and(predicate, user.roles.any().id.eq(role));
return userRepository.findAll(predicate, pageRequest);
}
-------------本文结束感谢您的阅读-------------

本文标题:SpringBoot整合JPA使用

文章作者:NanYin

发布时间:2020年03月05日 - 12:03

最后更新:2020年03月05日 - 21:03

原始链接:https://nanyiniu.github.io/2020/03/05/SpringBoot%E6%95%B4%E5%90%88JPA%E4%BD%BF%E7%94%A8/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。