Spring Boot 2 实战:Spring Data Jpa 集成以及使用
本文于 1501 天之前发表,文中内容可能已经过时。
1. 前言
JPA 即 Java Persistence API,是一个基于O/R映射的标准规范。Hibernate 是该规范的实现,Hibernate 是非常流行的对象关系映射框架(ORM
),是SSH
组合开发框架重要组件。Spring Data JPA 是 Spring Data 的一个子项目,它提供了基于 JPA 的 Repository 减少了大量的数据访问操作的代码。JpaRepository 接口提供了通用的数据访问方法。始终建议去spring.io看官方文档
2. JDBC自动配置
Spring Boot 自动配置依赖于 spring-boot-autoconfigure-2.1.9.RELEASE.jar
包,默认支持的自动配置都在org.springframework.boot.autoconfigure
路径下。
2.1 自动配置
spring-boot-starter-data-jpa
依赖于spring-boot-starter-jdbc
, Spring Boot 为 JDBC 做了些自动配置,并自动开启了注解事务的支持。JDBC 自动配置源码在 org.springframework.boot.autoconfigure.jdbc
路径下。
- 自动配置文件:
DataSourceAutoConfiguration.class
自动配置类会被注册为Bean
并执行里面的配置。 - 配置属性文件:
DataSourceProperties.class
,可以通过spring.datasource
为前辍的属性自动配置数据源。 - 自动开启注解事务文件,
DataSourceTransactionManagerAutoConfiguration.class
自动配置事务管理,并配置了一个JdbcTemplate
。
2.2表结构初始化
Spring Boot 提供了初始化数据的功能:放置在类路径(/src/main/resources)下的schema.sql
文件会自动用来初始化表结构;放置在类路径下的data.sql
文件会自动用来填充表数据。
实际项目中几乎不会使用,也不建议使用;实际开发是先根据业务逻辑设计好数据库,再定实体类,再进行业务逻辑代码开发;数据库设计先行能更好更深入的理解业务逻辑和关联关系。
3. JPA自动配置分析
- Spring Boot 对 JPA 的自动配置在
org.springframework.boot.autoconfigure.orm.jpa
下,从该包下的HibernateJpaAutoConfiguration
可以看出,Spring Boot 默认支持的 JPA 实现是Hibernate,HibernateJpaAutoConfiguration
依赖于JDBC
的自动配置类DataSourceAutoConfiguration
。 - 从
JpaProperties
属性配置类可以看到,配置 JPA 可以使用spring.jpa
为前缀的属性在application.properties
中配置。 JpaBaseConfiguration
类配置了transactionManager
,jpaVendorAdapter
,entityManagerFactory
等 Bean。还提供了getPackagesToScan()
方法用于自动扫描注解@Entity
的实体类。
3.1 Spring Data JPA自动配置
- 对 Spring Data JPA 的自动配置放在
org.springframework.boot.autoconfigure.data.jpa
包下。 - 从
JpaRepositoriesAutoConfiguration
配置类中可以看到该自动配置依赖于HibernateJpaAutoConfiguration
配置。该配置类还引入了JpaRepositoriesAutoConfigureRegistrar
注册类,该注册启用了注解@EnableJpaRepositories
,所以在 Spring Boot 项目中配置类就无须重复声明@EnableJpaRepositories
。
3.2 Spring Boot JPA配置
在项目中添加spring-boot-starter-data-jpa
依赖,会自动添加jdbc
依赖,然后只需定义DataSource
、实体类和数据访问层,并在需要使用数据访问的地方注入数据访问层的 Bean 即可,无须额外的配置。
4. Spring Boot JPA使用示例
接下来我们编写一个具体的实例来使用Spring Data Jpa。
4.1 新建Spring Boot项目,引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
4.2 在application.properties
配置数据源。
#-----------data source-----------------
server.port=80
spring.profiles.active=dev
logging.config=classpath:log4j2.xml
#-----------data source-----------------
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.driverClassName = net.sf.log4jdbc.DriverSpy
#spring.datasource.url=jdbc:mysql://localhost:3306/mytest?characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true
spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/mytest?characterEncoding=utf-8
spring.datasource.username=admin
spring.datasource.password=123456
#spring.datasource.tomcat.max-active=20
#spring.datasource.tomcat.test-while-idle=true
#spring.datasource.tomcat.validation-query=select 1
#spring.datasource.tomcat.default-auto-commit=false
#spring.datasource.tomcat.min-idle=15
#spring.datasource.tomcat.initial-size=15
#spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jackson.serialization.indent-output=true
4.3 定义实体类
@Entity //和数据库表映射的实体类
public class Actor {
@Id //映射为数据库主键
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long actorId;
// @Column(name = "realName") //字段名映射
private String firstName;
private String lastName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date lastUpdate;
//...........set/get方法................
}
4.4 Controller层代码
import com.springboot.jpa.entity.Actor;
import com.springboot.jpa.service.ActorService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping(value = "/actor")
public class ActorController {
private static final Logger logger = LogManager.getLogger(ActorController.class);
@Autowired
private ActorService actorService;
/**
* 添加
* @param actor
* @return
*/
@RequestMapping(value = "/addActor")
public Actor addActor(Actor actor){
actor.setLastUpdate(new Date());
return actorService.addActor(actor);
}
/**
* 删除
* @param actorId
* @return
*/
@RequestMapping(value = "/deleteByActorId")
public void deleteByActorId(Long actorId){
actorService.deleteByActorId(actorId);
}
/**
* 改
* @param actorId
* @param firstName
* @return
*/
@RequestMapping(value = "/updateFirstName")
public int updateFirstName(Long actorId, String firstName){
return actorService.updateFirstName(actorId, firstName);
}
/**
* 查所有
* @return
*/
@RequestMapping("/queryAll")
public List<Actor> queryAll(){
return actorService.queryAll();
}
/**
* 主键条件查询
* @param actorId
* @return
*/
@RequestMapping("/queryByActorId")
public Actor queryByActorId(Long actorId){
return actorService.queryByActorId(actorId);
}
/**
* 非主键条件查询
* @param firstName
* @return
*/
@RequestMapping("/queryByFirstName")
public List<Actor> queryByFirstName(String firstName){
return actorService.queryByFirstName(firstName);
}
/**
* HQL语句查询
* @param lastName
* @return
*/
@RequestMapping("/queryByLastName")
public List<Actor> queryByLastName(String lastName){
return actorService.queryByLastName(lastName);
}
/**
* 条件查询,自定匹配规则
* @param firstName
* @param lastName
* @return
*/
@RequestMapping("/queryByFirstNameAndLastName")
public List<Actor> queryByFirstNameAndLastName(String firstName, String lastName){
return actorService.queryByFirstNameAndLastName(firstName,lastName);
}
/**
* 排序查询
* @return
*/
@RequestMapping("/queryByFirstNameWithSortDesc")
public List<Actor> queryByFirstNameWithSortDesc(String firstName){
// Sort sort = new Sort(Sort.Direction.DESC, "firstName");
Sort sort = new Sort(Sort.Direction.ASC, "firstName");
return actorService.queryByFirstNameWithSortDesc(sort);
}
/**
* 分页查询-sql:从第(page * size) + 1条开始查,查 size 条
* @param page 页码
* @param size 每页显示条数
* @return
*/
@RequestMapping("/queryByPage")
public Page<Actor> queryByPage(int page, int size){
PageRequest pageRequest = PageRequest.of(page, size);
return actorService.queryByPage(pageRequest);
}
/**
* 统计
* @return
*/
@RequestMapping("/count")
public Long countActor(){
return actorService.countActor();
}
/**
* 条件统计
* @param actor
* @return
*/
@RequestMapping("/countBy")
public Long countBy(Actor actor){
return actorService.countBy(actor);
}
}
4.5 业务接口
import com.springboot.jpa.entity.Actor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import java.util.List;
public interface ActorService {
Actor addActor(Actor actor);
void deleteByActorId(Long actorId);
List<Actor> queryAll();
Actor queryByActorId(Long actorId);
Long countActor();
Long countBy(Actor actor);
List<Actor> queryByFirstName(String firstName);
int updateFirstName(Long actorId, String firstName);
List<Actor> queryByLastName(String lastName);
List<Actor> queryByFirstNameAndLastName(String firstName, String lastName);
List<Actor> queryByFirstNameWithSortDesc(Sort sort);
Page<Actor> queryByPage(PageRequest pageRequest);
}
4.6 业务接口实现类代码
import com.springboot.jpa.entity.Actor;
import com.springboot.jpa.repository.ActorRepository;
import com.springboot.jpa.service.ActorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ActorServiceImpl implements ActorService {
@Autowired
private ActorRepository actorRepository;
/**
* 增
*
* @param actor
* @return
*/
@Override
public Actor addActor(Actor actor) {
return actorRepository.save(actor);
}
/**
* 删: deleteById()
*
* @param actorId
* @return
*/
@Override
public void deleteByActorId(Long actorId) {
actorRepository.deleteById(actorId);
}
/**
* 改
*
* @param firstName
* @return
*/
@Override
public int updateFirstName(Long actorId, String firstName) {
return actorRepository.updateFirstName(actorId, firstName);
}
/**
* 查: 所有, findAll()
*
* @return
*/
@Override
public List<Actor> queryAll() {
return actorRepository.findAll();
}
/**
* 查: 条件-主键, findById(primary key)
*
* @param actorId
* @return
*/
@Override
public Actor queryByActorId(Long actorId) {
//springboot 1.5.3.Release
// return actorRepository.findOne(actorId);
//springboot 2.0.0.Release及以上
return actorRepository.findById(actorId).get();
}
/**
* 查: 条件-非主键字段
* 非主键查询都是匹配对象属性查询
*
* @param firstName
* @return
*/
@Override
public List<Actor> queryByFirstName(String firstName) {
Example<Actor> example = Example.of(new Actor().setFirstName(firstName));
return actorRepository.findAll(example);
}
/**
* 查: 定制查询条件和匹配规则
* @param firstName
* @param lastName
* @return
*/
@Override
public List<Actor> queryByFirstNameAndLastName(String firstName, String lastName) {
Actor actor = new Actor().setFirstName(firstName).setLastName(lastName);
Example<Actor> example = new Example<Actor>() {
//构造参与查询的参数
@Override
public Actor getProbe() {
return actor;
}
//设置查询匹配规则
@Override
public ExampleMatcher getMatcher() {
return ExampleMatcher.matchingAny();
}
};
// return actorRepository.findAll(example);
return actorRepository.findAll(Example.of(actor, ExampleMatcher.matchingAll()));
}
/**
* 排序查询
* @param sort
* @return
*/
@Override
public List<Actor> queryByFirstNameWithSortDesc(Sort sort) {
return actorRepository.findAll(sort);
}
/**
* 分页查询
* @param pageRequest
* @return
*/
@Override
public Page<Actor> queryByPage(PageRequest pageRequest) {
return actorRepository.findAll(pageRequest);
}
/**
* 使用HQL查询语句
* @param lastName
* @return
*/
@Override
public List<Actor> queryByLastName(String lastName) {
return actorRepository.queryByLastName(lastName);
}
/**
* 统计: 所有, count()
*
* @return
*/
@Override
public Long countActor() {
return actorRepository.count();
}
/**
* 统计: 条件,全匹配实体类属性,count(example)
*
* @param actor
* @return
*/
@Override
public Long countBy(Actor actor) {
// Actor actor = new Actor().setFirstName(firstName);
// Example<Actor> example = Example.of(actor);
return actorRepository.count(Example.of(actor));
}
}
4.7 定义数据访问接口
继承JpaRepository
接口,指定映射的实体类和主键类型。
import com.springboot.jpa.entity.Actor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Repository
public interface ActorRepository extends JpaRepository<Actor, Long> {
/**
* @Query注解的SQL语句是HQL,是对对象的查询
* 更新和删除操作必需开启事务,否则会报:
* nested exception is javax.persistence.TransactionRequiredException
* @param actorId
* @param firstName
* @return
*/
@Modifying
@Transactional(timeout = 1000)
@Query("update Actor set firstName = ?2 where actorId = ?1")
int updateFirstName(Long actorId, String firstName);
/**
* @Query注解传参有两种方式
* 1. 第一种是按参数的序号传参。
* 2. 第二种是绑定参数名传参,方法参数必须使用@Param注解来与HQL入参绑定
* @param lastName
* @return
*/
// @Query(value = "select a from Actor a where a.lastName = ?1")
@Query(value = "select a from Actor a where a.lastName = :lastName")
List<Actor> queryByLastName(@Param("lastName") String lastName);
}
5. JPA常用注解
@Entity
:表示这是一个与数据库映射的实体类(作用在类上)。@Table
:表示该实体类映射到的表名(作用在类上)。@Id
:表示该属性映射为数据库表的主键(作用在属性上)。@GeneratedValue
:指示主键生成策略(作用在属性上)。GenerationType.TABLE
GenerationType.SEQUENCE
GenerationType.IDENTITY
GenerationType.AUTO
@Column
:表示该属性映射到表的字段名(作用在属性上)。@Temporal
:Date类型属性映射到表字段的具体日期时间类型(作用在属性上),可设置如下值:TemporalType.DATE
TemporalType.TIME
TemporalType.TIMESTAMP
@Inheritance
:指定继承(父子)关系的实体类映射到表的策略(作用在类上)。InheritanceType.SINGLE_TABLE
InheritanceType.TABLE_PER_CLASS
InheritanceType.JOINED
评论系统未开启,无法评论!