avatar

SpringBoot整合Jpa

摘要:本文主要介绍了SpringBoot如何整合Jpa、Jpa关键字定义查询方法、Jpa自定义查询、Jpa自定义数据源以及Jpa整合多数据源等。

Jpa

Jpa是什么

  1. Java Persistence API:用于对象持久化的 API
  2. Java EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层

Jpa与Hibernate的关系

  1. JPA 是 Hibernate 的一个抽象(就像JDBC和JDBC驱动的关系);
  2. JPA 是规范:JPA 本质上就是一种 ORM 规范,不是ORM 框架,这是因为 JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现;
  3. Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现
  4. 从功能上来说, JPA 是 Hibernate 功能的一个子集

Jpa的优势

  1. 标准化: 提供相同的 API,这保证了基于JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。
  2. 简单易用,集成方便: JPA 的主要目标之一就是提供更加简单的编程模型,在 JPA 框架下创建实体和创建 Java 类一样简单,只需要使用 javax.persistence.Entity 进行注解;JPA 的框架和接口也都非常简单。
  3. 可媲美JDBC的查询能力: JPA的查询语言是面向对象的,JPA定义了独特的JPQL,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
  4. 支持面向对象的高级特性: JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,最大限度的使用面向对象的模型

SpringBoot整合Jpa

在application.properties中配置

1
2
3
4
5
6
7
8
9
10
11
12
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.bookname=root
spring.datasource.password=11215858
spring.datasource.url=jdbc:mysql://localhost:3306/javaboy?serverTimezone=UTC

# 程序运行的时候,在控制台中打印SQL语句
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.database-platform=mysql
# 该属性表示项目启动时,若实体类对应的表不存在,则自动创建出来,若存在则更新表的数据
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

创建Book实体类

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.example.jpa.bean;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
* @author: WJZheng
* @date: 2020/3/18 21:10
* @description:
*/

//需要添加@Entity注解来告诉Jpa这是一个实体类
//name属性对应数据库里表的名字
@Entity(name = "t_book")
public class Book {
//每一个表都需要一个主键,因此我们需要添加@Id注解来告诉Jpa该属性对应表中的主键
//同时我们希望该主键自增长,我们可以通过@GeneratedValue注解来实现
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String auther;

@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", auther='" + auther + '\'' +
'}';
}

@Id
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getAuther() {
return auther;
}

public void setAuther(String auther) {
this.auther = auther;
}
}

创建BookDao接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.jpa.dao;

import com.example.jpa.bean.Book;
import org.springframework.data.jpa.repository.JpaRepository;

/**
* @author: WJZheng
* @date: 2020/3/18 21:44
* @description:
*/

//dao接口是用于操作数据的,同时需要继承JpaRepository<T,ID>,其中T是对应的实体类,ID是该实体类的Id属性
public interface BookDao extends JpaRepository<Book,Integer> {
}

在Test中测试

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.example.jpa;

import com.example.jpa.bean.Book;
import com.example.jpa.dao.BookDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Optional;

@SpringBootTest
class JpaApplicationTests {

@Autowired
BookDao bookDao;

@Test
void contextLoads() {
Book book = new Book();
book.setId(1);
book.setName("Three Body");
book.setAuther("Liu");
bookDao.save(book);
}

@Test
void update() {
Book book = new Book();
book.setId(1);
book.setName("Ball Lightning");
bookDao.saveAndFlush(book);
}

@Test
void delete() {
Book book = new Book();
book.setId(1);
bookDao.delete(book);
}

@Test
void findById() {
Optional<Book> byId = bookDao.findById(1);
System.out.println(byId.get());
List<Book> all = bookDao.findAll();
System.out.println(all);
}

@Test
void findPageable() {
Pageable pageable = PageRequest.of(0, 2);
Page<Book> bookPage = bookDao.findAll(pageable);
System.out.println("总记录数:" + bookPage.getTotalElements());
System.out.println("当前页记录数:" + bookPage.getNumberOfElements());
System.out.println("每页记录数:" + bookPage.getSize());
System.out.println("总页数:" + bookPage.getTotalPages());
System.out.println("当前页数:" + bookPage.getNumber());
System.out.println("查询结果:" + bookPage.getContent());
System.out.println("是否为首页:" + bookPage.isFirst());
System.out.println("是否为尾页:" + bookPage.isLast());
}
}

Jpa关键字定义查询方法


方法定义规范

  • 按照 Spring Data 的规范,查询方法以 find | read | get 开头
  • 涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写
  • 支持属性的级联查询。若当前类有符合条件的属性,则优先使用,而不使用级联属性。若需要使用级联属性,则属性之间使用 _ 进行连接。

支持的关键字

代码示例

在dao中定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.jpa.dao;

import com.example.jpa.bean.Book;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

/**
* @author: WJZheng
* @date: 2020/3/18 21:44
* @description:
*/
public interface BookDao extends JpaRepository<Book,Integer> {
Book findBookById(Integer id);

List<Book> findBookByIdGreaterThan(Integer id);

List<Book> findBookByNameContaining(String name);
}

在Test中测试

1
2
3
4
5
6
7
8
9
10
@Test
void test(){
System.out.println(bookDao.findBookById(1));
}

@Test
void test2(){
System.out.println(bookDao.findBookByIdGreaterThan(3));
System.out.println(bookDao.findBookByNameContaining("Three"));
}

查询方法流程解析

为什么写上方法名,JPA就知道你想干嘛了呢?假如创建如下的查询:findByBookDepUuid(),框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc:

  1. 先判断 bookDepUuid (根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
  2. 从右往左截取第一个大写字母开头的字符串(此处为Uuid),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 book 为查询实体的一个属性;
  3. 接着处理剩下部分(DepUuid),先判断 book 所对应的类型是否有depUuid属性,如果有,则表示该方法最终是根据 “ Doc.book.depUuid” 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 “Doc.book.dep.uuid” 的值进行查询。
  4. 可能会存在一种特殊情况,比如 Doc包含一个 book 的属性,也有一个 bookDep 属性,此时会存在混淆。可以明确在属性之间加上 “_” 以显式表达意图,比如 findByBook_DepUuid() 或者 findByBookDep_uuid()
  5. 还有一些特殊的参数:例如分页或排序的参数
1
2
Page<BookModel> findByName(String name, Pageable pageable);  
List<BookModel> findByName(String name, Sort sort);

Jpa自定义SQL

@Query注解

有的时候,这里提供的查询关键字并不能满足我们的查询需求,这个时候就可以使用 @Query 关键字,来自定义查询 SQL,例如查询Id最大的Book:

1
2
@Query(value = "select * from t_book where id = (select max(id) from t_book)", nativeQuery = true)
Book findMaxIdBook();

如果查询有参数的话,参数有两种不同的传递方式:

  1. 利用下标索引传参:索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致
1
2
3
4
@Modifying
@Transactional
@Query(value = "insert into t_book (id, name , author) values (?1,?2,?3)", nativeQuery = true)
int addBook2(Integer id, String name, String author);
  1. 命名参数:这种方式可以定义好参数名,赋值时采用@Param(“参数名”),而不用管顺序
1
2
3
4
@Modifying
@Transactional
@Query(value = "insert into t_book(id, name, author) values(:id, :name, :author)", nativeQuery = true)
int addBook(@Param("id") Integer id, @Param("name") String name, @Param("author") String author);

还可以使用原生SQL语句

1
2
@Query(value = "select * from t_book",nativeQuery = true)
List<Book> selectAll();

@Modifying注解

涉及到数据修改操作,可以使用 @Modifying 注解,@Query 与 @Modifying 这两个 annotation一起声明,可定义个性化更新操作,例如涉及某些字段更新时最为常用

1
2
3
4
@Modifying
@Transactional
@Query("update t_book set age=:age where id>:id")
int updatebookById(@Param("age") Long age, @Param("id") Long id);

注意:

  1. 可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
  2. 方法的返回值应该是 int,表示更新语句所影响的行数
  3. 在调用的地方必须加事务,没有事务不能正常执行
  4. 默认情况下, Spring Data 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作

SpringData中的事务问题

  1. Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。
  2. 对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上添加 @Transactional 注解。
  3. 进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。

Jpa多数据源


在application.properties中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.username=root
spring.datasource.one.password=11215858
spring.datasource.one.url=jdbc:mysql://localhost:3306/javaboy?serverTimezone=UTC

spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.username=root
spring.datasource.two.password=11215858
spring.datasource.two.url=jdbc:mysql://localhost:3306/javaboy2?serverTimezone=UTC

spring.jpa.properties.show-sql=true
spring.jpa.properties.database=mysql
spring.jpa.properties.database-platform=mysql
spring.jpa.properties.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

配置数据源

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
32
package com.example.jpa2.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
* @author: WJZheng
* @date: 2020/3/24 15:14
* @description:
*/

@Configuration
public class DataSourceConfig {

@Bean
@Primary //该注解表示如果数据源冲突了,优先选择该数据源
@ConfigurationProperties(prefix = "spring.datasource.one")
DataSource dataSourceOne(){
return DruidDataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties(prefix = "spring.datasource.two")
DataSource dataSourceTwo(){
return DruidDataSourceBuilder.create().build();
}
}

配置Jpa

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.example.jpa2.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
* @author: WJZheng
* @date: 2020/3/24 15:16
* @description:
*/

@Configuration
@EnableJpaRepositories(basePackages = "com.example.jpa2.dao1",entityManagerFactoryRef =
"localContainerEntityManagerFactoryBeanOne",transactionManagerRef =
"platformTransactionManagerOne")
public class JpaConfigOne {

@Autowired
@Qualifier("dataSourceOne")
DataSource dataSourceOne;

@Autowired
JpaProperties jpaProperties;

@Bean
@Primary
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanOne(EntityManagerFactoryBuilder builder){
return builder.dataSource(dataSourceOne)
.properties(jpaProperties.getProperties())
.persistenceUnit("pu1")
.packages("com.example.jpa2")
.build();
}

@Bean
PlatformTransactionManager platformTransactionManagerOne(EntityManagerFactoryBuilder builder){
return new JpaTransactionManager(localContainerEntityManagerFactoryBeanOne(builder).getObject());
}
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.example.jpa2.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
* @author: WJZheng
* @date: 2020/3/24 15:16
* @description:
*/

@Configuration
@EnableJpaRepositories(basePackages = "com.example.jpa2.dao2",entityManagerFactoryRef =
"localContainerEntityManagerFactoryBeanTwo",transactionManagerRef =
"platformTransactionManagerTwo")
public class JpaConfigTwo {

@Autowired
@Qualifier("dataSourceTwo")
DataSource dataSourceTwo;

@Autowired
JpaProperties jpaProperties;

@Bean
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanTwo(EntityManagerFactoryBuilder builder){
return builder.dataSource(dataSourceTwo)
.properties(jpaProperties.getProperties())
.persistenceUnit("pu2")
.packages("com.example.jpa2")
.build();
}

@Bean
PlatformTransactionManager platformTransactionManagerTwo(EntityManagerFactoryBuilder builder){
return new JpaTransactionManager(localContainerEntityManagerFactoryBeanTwo(builder).getObject());
}
}

创建实体类

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.example.jpa2.bean;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
* @author: WJZheng
* @date: 2020/3/18 21:10
* @description:
*/

@Entity(name = "t_book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String auther;

@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", auther='" + auther + '\'' +
'}';
}

@Id
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getAuther() {
return auther;
}

public void setAuther(String auther) {
this.auther = auther;
}
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.example.jpa2.bean;

import org.hibernate.annotations.GeneratorType;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
* @author: WJZheng
* @date: 2020/3/24 16:03
* @description:
*/

@Entity(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String address;

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}
}

创建dao接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.jpa2.dao1;

import com.example.jpa2.bean.Book;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Id;

/**
* @author: WJZheng
* @date: 2020/3/24 16:11
* @description:
*/
public interface BookDao extends JpaRepository<Book, Integer> {
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.example.jpa2.dao2;

import com.example.jpa2.bean.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
* @author: WJZheng
* @date: 2020/3/24 16:12
* @description:
*/
public interface UserDao extends JpaRepository<User, Integer> {
}

在Test中测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.jpa2;

import com.example.jpa2.dao1.BookDao;
import com.example.jpa2.dao2.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Jpa2ApplicationTests {

@Autowired
BookDao bookDao;

@Autowired
UserDao userDao;

@Test
void contextLoads() {
System.out.println(bookDao.findAll());
System.out.println(userDao.findAll());
}
}

image-20200324162535777

Author: WJZheng
Link: https://wellenzheng.github.io/2020/03/23/SpringBoot%E6%95%B4%E5%90%88Jpa/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment