摘要:本文主要介绍了SpringBoot如何整合Jpa、Jpa关键字定义查询方法、Jpa自定义查询、Jpa自定义数据源以及Jpa整合多数据源等。
Jpa
Jpa是什么
- Java Persistence API:用于对象持久化的 API
- Java EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层
Jpa与Hibernate的关系
- JPA 是 Hibernate 的一个抽象(就像JDBC和JDBC驱动的关系);
- JPA 是规范:JPA 本质上就是一种 ORM 规范,不是ORM 框架,这是因为 JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现;
- Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现
- 从功能上来说, JPA 是 Hibernate 功能的一个子集
Jpa的优势
- 标准化: 提供相同的 API,这保证了基于JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。
- 简单易用,集成方便: JPA 的主要目标之一就是提供更加简单的编程模型,在 JPA 框架下创建实体和创建 Java 类一样简单,只需要使用 javax.persistence.Entity 进行注解;JPA 的框架和接口也都非常简单。
- 可媲美JDBC的查询能力: JPA的查询语言是面向对象的,JPA定义了独特的JPQL,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
- 支持面向对象的高级特性: 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
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;
@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; } }
|
创建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;
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;
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:
- 先判断 bookDepUuid (根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
- 从右往左截取第一个大写字母开头的字符串(此处为Uuid),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 book 为查询实体的一个属性;
- 接着处理剩下部分(DepUuid),先判断 book 所对应的类型是否有depUuid属性,如果有,则表示该方法最终是根据 “ Doc.book.depUuid” 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 “Doc.book.dep.uuid” 的值进行查询。
- 可能会存在一种特殊情况,比如 Doc包含一个 book 的属性,也有一个 bookDep 属性,此时会存在混淆。可以明确在属性之间加上 “_” 以显式表达意图,比如
findByBook_DepUuid()
或者 findByBookDep_uuid()
- 还有一些特殊的参数:例如分页或排序的参数
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开始,查询中 ”?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);
|
- 命名参数:这种方式可以定义好参数名,赋值时采用@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);
|
注意:
- 可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
- 方法的返回值应该是 int,表示更新语句所影响的行数
- 在调用的地方必须加事务,没有事务不能正常执行
- 默认情况下, Spring Data 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作
SpringData中的事务问题
- Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。
- 对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上添加 @Transactional 注解。
- 进行多个 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;
@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;
@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;
@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;
@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;
@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;
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;
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()); } }
|