前言 Spring Data JPA 是基于 Hibernate 的。
Hibernate 是一个广泛使用的 Java ORM(对象关系映射)框架,它提供了对关系型数据库的映射和操作功能,使开发者能够以面向对象的方式来处理数据库操作,而不用直接编写 SQL 语句。
与 MyBatis 比较
Spring Data JPA:优点是代码简单、易于维护,集成 Spring 框架更方便;
缺点是灵活性不如 MyBatis,性能也可能不如 MyBatis。
MyBatis:优点是灵活性强,可以执行复杂的 SQL 语句;
缺点是需要手动编写 SQL 语句,难以维护。
环境 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 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.6.3</version > <relativePath /> </parent > <groupId > cn.psvmc</groupId > <artifactId > z-api-jpa</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > z-api-jpa</name > <description > z-api-jpa</description > <properties > <java.version > 1.8</java.version > </properties > <dependencies > <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-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
添加依赖 在项目的 pom.xml 文件中添加如下 Spring Data JPA 相关依赖:
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency >
连接配置 application.properties配置文件中增加数据库参数,信息内容如下:
1 2 3 4 5 6 7 8 9 10 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zbi_source?useUnicode=true &characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.hikari.minimum-idle=10 spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.idle-timeout=500000 spring.datasource.hikari.max-lifetime=540000 spring.datasource.hikari.connection-timeout=60000 spring.datasource.hikari.connection-test-query=SELECT 1
实体类 在项目中创建实体类,用于映射数据库表和列。
表实体 实体类需要使用@Entity注解进行标记,并且需要指定主键和自动生成策略。
例如:
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 package cn.psvmc.zapijpa.entity;import javax.persistence.*;@Entity @Table(name = "t_user") public class UserEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name = "name", length = 20, nullable = false) private String name; @Column(name = "age") private Integer age; @Column(name = "sex") private Integer sex; public Long getId () { return id; } public void setId (Long id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } public Integer getSex () { return sex; } public void setSex (Integer sex) { this .sex = sex; } }
主键生成 1 2 3 4 5 @Id @GeneratedValue(generator="system-uuid") @GenericGenerator(name="system-uuid", strategy = "uuid") @Column(name = "id") private String id;
自动建表 默认JPA是不会自动建表的,但是如果想自动建表,可以添加配置。
1 spring.jpa.hibernate.ddl-auto=update
设置 spring.jpa.hibernate.ddl-auto 属性为 create 或 update。
create 表示每次启动应用时都会删除现有表并重新创建。
update 表示每次启动应用时会根据实体类的定义,更新已存在的表结构(增加或修改列),但不会删除数据。如果表不存在也会创建。
一般来说使用 update,如果不想自动建表可以设置为none。
关系映射 关系映射通常包括一对一、一对多和多对多等关系。
在 Spring Data JPA 中,可以使用 @OneToOne、@OneToMany 和 @ManyToMany 注解来标注关系映射。
这些注解通常与 @JoinColumn 注解一起使用,用于指定关联的外键列。
示例代码:
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 @Entity @Table(name = "t_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List<Address> addresses; } @Entity @Table(name = "t_address") public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "user_id") private User user; }
在上例中,User 和 Address 之间是一对多的关系,所以在 User 实体类中使用了 @OneToMany 注解,在 Address 实体类中使用了 @ManyToOne 注解。mappedBy 属性用于指定关联的属性名称,这里是 user,表示 Address 实体类中的 user 属性与 User 实体类中的 addresses 属性相对应。
cascade 属性表示级联操作,这里使用 CascadeType.ALL 表示在删除 User 实体时同时删除其关联的所有 Address 实体。
@JoinColumn 注解用于指定外键名称,这里是 user_id,表示 Address 表中的 user_id 列与 User 表中的主键相对应。
Repository UserRepository.java
1 2 3 4 5 6 7 8 9 10 package cn.psvmc.zapijpa.repository;import cn.psvmc.zapijpa.entity.UserEntity;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repository public interface UserRepository extends JpaRepository <UserEntity, Long> { UserEntity findByName (String name) ; }
内置方法 在继承 Repository 接口后,会默认提供基本的增删改查方法,无需额外的代码实现即可使用。
常用的方法如下:
方法名
描述
T save(T entity)
保存实体对象
Iterable saveAll(Iterable entities)
批量保存实体对象
Optional findById(ID id)
根据主键获取实体对象
boolean existsById(ID id)
判断是否存在特定主键的实体对象
Iterable findAll()
获取所有实体对象
Iterable findAllById(Iterable ids)
根据主键批量获取实体对象
long count()
获取实体对象的数量
void deleteById(ID id)
根据主键删除实体对象
void delete(T entity)
删除实体对象
void deleteAll(Iterable<? extends T> entities)
批量删除实体对象
方法名称查询 方法名称查询是 Spring Data JPA 中最简单的一种自定义查询方法,并且不需要额外的注解或 XML 配置。
它通过方法名来推断出查询的条件,
例如以 findBy 开头的方法表示按照某些条件查询,以 deleteBy 开头的方法表示按照某些条件删除数据。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface UserRepository extends Repository <UserEntity, Long> { UserEntity findByName (String name) ; List<UserEntity> findByAge (Integer age) ; UserEntity findByNameAndPassword (String name, String password) ; void deleteByIdAndUserName (Long id, String name) ; }
规则
关键字
方法命名
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
findByStateTue
where state= true
FALSE
findByStateFalse
where state= false
IgnoreCase
findByNameIgnoreCase
where UPPER(name)=UPPER(?)
查询参数设置 除了方法名称查询外,还可以使用参数设置方式进行自定义查询。
它通过在方法上使用 @Query 注解来指定查询语句,然后使用 @Param 注解来指定方法参数与查询语句中的参数对应关系。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 public interface UserRepository extends Repository <UserEntity, Long> { @Query("SELECT u FROM UserEntity u WHERE u.name = :name") UserEntity findByName (@Param("name") String name) ; @Query("SELECT u FROM UserEntity u WHERE u.name = :name AND u.password = :password") UserEntity findByUserNameAndPassword (@Param("name") String name, @Param("password") String password) ; @Query("SELECT u FROM UserEntity u WHERE u.name like %:name%") List<UserEntity> findByName (@Param("name") String name) ; }
使用 Native SQL 查询 在某些情况下,需要执行原生的 SQL 查询语句。
Spring Data JPA 提供了 @Query 注解来支持使用原生 SQL 查询数据。
在 @Query 注解中设置 nativeQuery=true 即可执行原生 SQL 语句。
以下示例代码演示了如何使用原生 SQL 查询 age 大于等于 18 的用户。
1 2 3 4 5 6 public interface UserRepository extends JpaRepository <UserEntity, Long> { @Query(value = "SELECT * FROM t_user WHERE age >= ?1", nativeQuery = true) List<UserEntity> findByAgeGreaterThanEqual (Integer age) ; }
使用
1 userRepository.findByAgeGreaterThanEqual(18 );
这两种是一样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import cn.psvmc.zapijpa.entity.UserEntity;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.Query;import org.springframework.data.repository.query.Param;import org.springframework.stereotype.Repository;import java.util.List;@Repository public interface UserRepository extends JpaRepository <UserEntity, Long> { @Query("SELECT u FROM UserEntity u WHERE u.name like %:name%") List<UserEntity> findByName (@Param("name") String name) ; @Query(value = "SELECT * FROM t_user WHERE name like %?1%", nativeQuery = true) List<UserEntity> findByName2 (@Param("name") String name) ; }
让人难受的是下面的写法是对的,但是IDEA工具会提示错误。
排序和分页 在查询数据时,经常需要对结果进行排序和分页操作。
Spring Data JPA 提供了 Sort 和 Pageable 两个类来实现排序和分页功能。
Sort 类表示排序规则,可以使用 Sort.by() 静态方法创建实例,并指定排序属性和排序方向。
常用方法如下:
方法名
描述
static Sort by(Sort.Order… orders)
根据排序规则创建 Sort 实例
static Sort.Order by(String property)
根据属性升序排序
static Sort.Order by(String property, Sort.Direction direction)
根据属性排序
示例代码:
1 2 3 4 public interface UserRepository extends Repository <UserEntity, Long> { List<UserEntity> findByOrderByAgeAsc () ; }
Pageable 类表示分页信息,可以使用 PageRequest.of() 静态方法创建实例,并指定页码、每页数据量和排序规则。
常用方法如下:
方法名
描述
static PageRequest of(int page, int size, Sort sort)
创建分页信息实例
static PageRequest of(int page, int size, Sort.Direction direction, String… properties)
创建分页信息实例
示例代码:
1 2 3 4 public interface UserRepository extends Repository <UserEntity, Long> { Page<UserEntity> findBy (Pageable pageable) ; }
使用
1 2 3 Pageable pageable = PageRequest.of(0 , 10 , Sort.by("age" ).descending());Page<UserEntity> page = userRepository.findBy(pageable); List<UserEntity> userList = page.getContent();
更新和删除 在 Spring Data JPA 中,使用 update 和 delete 语句需要使用 @Modifying 注解标注,并且需要添加 @Transactional 注解开启事务。
需要注意的是,@Modifying 注解只支持 DML 语句。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface UserRepository extends Repository <UserEntity, Long> { @Modifying @Transactional @Query("UPDATE UserEntity u SET u.password = :password WHERE u.id = :id") void updatePasswordById (@Param("id") Long id, @Param("password") String password) ; @Modifying @Transactional @Query("DELETE FROM UserEntity u WHERE u.age >= :age") void deleteByAgeGreaterThanEqual (@Param("age") Integer age) ; }
Service UserService.java
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 package cn.psvmc.zapijpa.service;import cn.psvmc.zapijpa.entity.UserEntity;import cn.psvmc.zapijpa.repository.UserRepository;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import javax.transaction.Transactional;import java.util.List;@Service @Transactional public class UserService { @Autowired private UserRepository userRepository; public List<UserEntity> userList () { return userRepository.findAll(); } public UserEntity addUser (UserEntity user) { return userRepository.save(user); } public void updateUser (UserEntity user) { userRepository.save(user); } public void deleteUser (Long id) { userRepository.deleteById(id); } }
Controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package cn.psvmc.zapijpa.controller;import cn.psvmc.zapijpa.entity.UserEntity;import cn.psvmc.zapijpa.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/list") public List<UserEntity> getUsers () { return userService.userList(); } }
访问
http://localhost:8080/user/list
多数据源 在实际应用中,有时需要使用多个数据源。
Spring Boot 提供了 @ConfigurationProperties、@Primary、@Qualifier 等注解来支持多数据源配置。
以下示例代码演示了如何在 Spring Boot 应用程序中配置多数据源。
在 application.properties 文件中配置两个数据源的连接信息
1 2 3 4 5 6 7 8 spring.datasource.one.url =jdbc:mysql://localhost:3306/test1 spring.datasource.one.username =root spring.datasource.one.password =123456 spring.datasource.two.url =jdbc:mysql://localhost:3306/test2 spring.datasource.two.username =root spring.datasource.two.password =123456
创建两个数据源的配置类
DataSourceOneConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration @ConfigurationProperties(prefix = "spring.datasource.one") public class DataSourceOneConfig { private String url; private String username; private String password; @Bean public DataSource dataSourceOne () { return DataSourceBuilder.create() .url(url) .username(username) .password(password) .build(); } }
DataSourceTwoConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration @ConfigurationProperties(prefix = "spring.datasource.two") public class DataSourceTwoConfig { private String url; private String username; private String password; @Bean public DataSource dataSourceTwo () { return DataSourceBuilder.create() .url(url) .username(username) .password(password) .build(); } }
在 Service 或 Repository 中指定要使用的数据源
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 @Service public class UserService { @Autowired @Qualifier("dataSourceOne") private DataSource dataSourceOne; @Autowired @Qualifier("dataSourceTwo") private DataSource dataSourceTwo; public void addUser (User user) { try (Connection connection = dataSourceOne.getConnection(); PreparedStatement statement = connection.prepareStatement( "INSERT INTO user (name, age) VALUES (?, ?)" ) ) { statement.setString(1 , user.getName()); statement.setInt(2 , user.getAge()); statement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException (e); } } }
在上述示例代码中,使用 @ConfigurationProperties 注解将数据源的连接信息和配置类绑定。
使用 @Qualifier 和 @Autowired 注解指定要使用的数据源。
在 Service 或 Repository 中通过 DataSource.getConnection() 获取连接,手动执行 SQL 语句。
开启批量操作 Mysql的话,开启批量操作需要在jdbc的url后面加上参数rewriteBatchedStatements=true,Oracle无需此操作。
默认批量操作是关闭的,要想开启设置如下参数
1 2 3 4 5 spring.jpa.properties.hibernate.jdbc.batch_size =2 spring.jpa.properties.hibernate.order_inserts =true spring.jpa.properties.hibernate.order_updates =true
当batch_size设置值等于1的时候也是不生效的,必须大于1。
这里是为了测试才设置为2。实际使用可以大一点
1 spring.jpa.properties.hibernate.jdbc.batch_size =100
为了方便验证我们可以添加打印配置
1 spring.jpa.properties.hibernate.generate_statistics =true
这样当没有批量处理的时候会看到
spent executing 0 JDBC batches;
有批量的时候值是大于0的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import cn.psvmc.zapijpa.entity.UserEntity;import cn.psvmc.zapijpa.repository.UserRepository;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import javax.transaction.Transactional;import java.util.List;@Service @Transactional public class UserService { @Autowired private UserRepository userRepository; @Transactional public List<UserEntity> addAll (List<UserEntity> users) { return userRepository.saveAll(users); } }
注意
批处理必须开启事务。
Controller中
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 import cn.psvmc.zapijpa.entity.UserEntity;import cn.psvmc.zapijpa.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Arrays;import java.util.List;@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/list") public List<UserEntity> getUsers () { return userService.userList(); } @RequestMapping("/add_users") public List<UserEntity> addUsers () { UserEntity u1 = new UserEntity ("张三" ,10 ); UserEntity u2 = new UserEntity ("李四" ,18 ); UserEntity u3 = new UserEntity ("王五" ,22 ); UserEntity u4 = new UserEntity ("赵六" ,16 ); List<UserEntity> userList = Arrays.asList(u1, u2, u3, u4); return userService.addAll(userList); } }
当我们调用以下地址的时候会插入4条数据
http://localhost:8080/user/add_users
开启批量设置为2的时候,没两条就进行一次批处理,就会看到显示2次批处理了。
spent executing 2 JDBC batches;
有人说是通过打印SQL查看
1 2 3 4 spring.jpa.properties.hibernate.show_sql =true spring.jpa.properties.hibernate.format_sql =false logging.level.org.hibernate.SQL =DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder =TRACE
但是实测发现就算是批处理,SQL也是一次一条,只不过是多条后批量提交。
打印执行的SQL 1 2 3 4 spring.jpa.properties.hibernate.show_sql =true spring.jpa.properties.hibernate.format_sql =false logging.level.org.hibernate.SQL =DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder =TRACE