logo头像

From zero to HERO

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 路径下。

  1. 自动配置文件:DataSourceAutoConfiguration.class自动配置类会被注册为Bean并执行里面的配置。
  2. 配置属性文件:DataSourceProperties.class,可以通过spring.datasource为前辍的属性自动配置数据源。
  3. 自动开启注解事务文件,DataSourceTransactionManagerAutoConfiguration.class自动配置事务管理,并配置了一个JdbcTemplate

2.2表结构初始化

Spring Boot 提供了初始化数据的功能:放置在类路径(/src/main/resources)下的schema.sql文件会自动用来初始化表结构;放置在类路径下的data.sql文件会自动用来填充表数据。

实际项目中几乎不会使用,也不建议使用;实际开发是先根据业务逻辑设计好数据库,再定实体类,再进行业务逻辑代码开发;数据库设计先行能更好更深入的理解业务逻辑和关联关系。

3. JPA自动配置分析

  1. Spring Boot 对 JPA 的自动配置在 org.springframework.boot.autoconfigure.orm.jpa 下,从该包下的HibernateJpaAutoConfiguration 可以看出,Spring Boot 默认支持的 JPA 实现是Hibernate, HibernateJpaAutoConfiguration 依赖于 JDBC的自动配置类DataSourceAutoConfiguration
  2. JpaProperties属性配置类可以看到,配置 JPA 可以使用 spring.jpa 为前缀的属性在 application.properties中配置。
  3. JpaBaseConfiguration 类配置了 transactionManagerjpaVendorAdapterentityManagerFactory 等 Bean。还提供了getPackagesToScan() 方法用于自动扫描注解 @Entity 的实体类。

3.1 Spring Data JPA自动配置

  1. 对 Spring Data JPA 的自动配置放在 org.springframework.boot.autoconfigure.data.jpa 包下。
  2. 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常用注解

  1. @Entity:表示这是一个与数据库映射的实体类(作用在类上)。
  2. @Table:表示该实体类映射到的表名(作用在类上)。
  3. @Id:表示该属性映射为数据库表的主键(作用在属性上)。
  4. @GeneratedValue:指示主键生成策略(作用在属性上)。
    • GenerationType.TABLE
    • GenerationType.SEQUENCE
    • GenerationType.IDENTITY
    • GenerationType.AUTO
  5. @Column:表示该属性映射到表的字段名(作用在属性上)。
  6. @Temporal:Date类型属性映射到表字段的具体日期时间类型(作用在属性上),可设置如下值:
    • TemporalType.DATE
    • TemporalType.TIME
    • TemporalType.TIMESTAMP
  7. @Inheritance:指定继承(父子)关系的实体类映射到表的策略(作用在类上)。
    • InheritanceType.SINGLE_TABLE
    • InheritanceType.TABLE_PER_CLASS
    • InheritanceType.JOINED

评论系统未开启,无法评论!