Spring 项目快速整合 JPA

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: 前言JDBC、JTA、JPA 都是 Java 与数据库打交道的标准,由于这些规范分别定义了不同的事务接口,Spring 对这些规范进行了整合,提供了统一的事务 API。Spring 的事务管理需要对这些标准做一些适配操作,才能转换为同一的接口,这篇就来谈谈如何在 Spring 中整合 JPA。

前言


JDBC、JTA、JPA 都是 Java 与数据库打交道的标准,由于这些规范分别定义了不同的事务接口,Spring 对这些规范进行了整合,提供了统一的事务 API。Spring 的事务管理需要对这些标准做一些适配操作,才能转换为同一的接口,这篇就来谈谈如何在 Spring 中整合 JPA。


这篇不对 JPA 的基础知识做过多介绍,如果你想了解 JPA 的更多内容,可以参见《Java ORM 规范 JPA 入门》。


Spring Framework 项目快速整合 JPA

JPA 虽然是 Java 中 ORM 的规范,然而在 JPA 出现之前就已经有一个广为流行的 ORM 框架实现了,名字大家都听说过,就是 Hibernate,JPA 的规范的制定参照了 Hibernate,Hibernate 后来又实现了 JPA 规范。


目前 Spring 对 JPA 和 Hibernate 都进行了支持,由于这两者接口不同,因此在 Spring 中需要不同的配置。由于 Hibernate 是最常用的 JPA 实现,这里还以 Hibernate 为例。


依赖引入


首先引入依赖,这里引入的依赖与 Spring 整合 Hibernate 的依赖一致,就不再过多解释了。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>4.0.3</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.6.9.Final</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>


映射关系定义


Hibernate 多使用 xml 定义映射关系,由于 JPA 诞生时 Java 注解已经提出,因此在 JPA 中多使用注解定义数据库表和实体类之间的映射关系。

这里使用的数据库表如下。


create table user
(
    id          bigint unsigned auto_increment comment '主键'
        primary key,
    username    varchar(20)  not null comment '用户名',
    password    varchar(20)  not null comment '密码'
);

对应的实体类如下。

@Getter
@Setter
@Entity(name = "User")
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
}


Spring 配置


Spring 的配置如下所示。


@Configuration
@EnableTransactionManagement
public class JpaConfig {
    @Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClassName(Driver.class.getName());
        dataSource.setUsername("root");
        dataSource.setPassword("12345678");
        return dataSource;
    }
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
        Properties jpaProperties = new Properties();
        jpaProperties.put(AvailableSettings.SHOW_SQL, true);
        jpaProperties.put(AvailableSettings.FORMAT_SQL, true);
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        entityManagerFactoryBean.setJpaProperties(jpaProperties);
        entityManagerFactoryBean.setPackagesToScan("com.zzuhkp.jpa.entity");
        return entityManagerFactoryBean;
    }
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        transactionManager.setDataSource(dataSource());
        return transactionManager;
    }
}


Spring 整合 JPA 的配置与 Spring 整合 Hibernate 的配置很相似,都包含数据源、数据库操作 bean 以及事务管理器。


对于 Hibernate 来说,操作数据库的核心接口是 Session,因此 Spring 提供了一个对应的 LocalSessionFactoryBean 实现。


对于 JPA 来说,操作数据库的核心接口变成了 EntityManager,因此 Spring 也提供了对应的实现 LocalContainerEntityManagerFactoryBean,这个类型可以通过 API 的方式灵活进行 JPA 的配置,如果想使用传统的 persience.xml 配置文件可以改用 LocalEntityManagerFactoryBean。


最后就是事务管理器的配置了,Spring 针对 JPA 提供的事务管理器是 JpaTransactionManager,这个事务管理器会在事务开始时创建一个 EntityManager,事务结束后关闭 EntityManager。


数据库操作


对于 JPA 的数据库操作,Spring 提供了更灵活的获取 EntityManagerFactory 或 EntityManager 的方式。数据库操作示例如下。


@Repository
public class UserRepository {
    @Autowired
    private EntityManagerFactory entityManagerFactory;
    @Transactional(rollbackFor = Exception.class)
    public Long saveUser(User user) {
        EntityManager entityManager = entityManagerFactory.createEntityManager(SynchronizationType.SYNCHRONIZED);
        entityManager.persist(user);
        System.out.println(user.getId());
        throw new RuntimeException("test rollback");
    }
}


EntityManager 获取方式一


EntityManager entityManager = entityManagerFactory.createEntityManager(SynchronizationType.SYNCHRONIZED);


使用 EntityManagerFactory.createEntityManager 获取 EntityManager ,注意要指定 SynchronizationType.SYNCHRONIZED 参数,这样 Spring 才会把获取到的 EntityManager 加入到事务管理中。


不过这种方式也仅仅是把获取的 EntityManager 加入到 Spring 的事务管理中而已,每个 EntityManager 底层都是使用不同的 Connection,如果有 A、B、C 三个 EntityManager,Spring 在执行事务提交操作时,A 提交事务后,B 发生异常,Spring 不会回滚 A 的事务,并且由于异常抛出 C 的事务也不会提交。


因此这种方式只适合不需要进行事务传播的单个方法内单次获取 EntityManager。


EntityManager 获取方式二


EntityManagerHolder entityManagerHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(this.entityManagerFactory);
EntityManager entityManager = entityManagerHolder.getEntityManager();


Spring 在事务方法进入前会将 EntityManagerFactory 作为 key,EntityManagerHolder 作为 value 存入线程上下文中,利用这个特性就可以将上下文中的 EntityManager 获取到了。


这种方式在单个线程内,不管获取多少次获取的都是相同的 EntityManager,因此适合在单个事务的多个方法内使用。


EntityManager 获取方式三


@Repository
public class UserRepository {
    @PersistenceContext
    private EntityManager entityManager;
}


除了上面的两种方式,Spring 还提供了使用 JPA 注解 @PersistenceContext 的方式注入 EntityManager,这是通过 BeanPostProcessor 来实现的,感兴趣的小伙伴可参阅 PersistenceAnnotationBeanPostProcessor 源码。


这种方式获取到的是 EntityManager 的一个代理实现,当其方法调用时会委托给加入事务的 EntityManager。


这种使用比较简便,个人比较推荐。


Spring Boot 项目快速整合 JPA


对于 Spring Boot 整合 JPA 这个话题,网上很多文章多是介绍对 spring-data 项目中的 spring-data-jpa 模块的整合,这里介绍的是对 spring-framework 项目 spring-orm 模块 JPA 的整合。


Spring Boot 对于 spring-orm 模块中的 JPA 其实并没有太友好的支持,当然了,这里的不友好其实只体现在依赖引入方面。


Spring Boot 各特性的开启通常引入一个对应的 starter 就可以了,例如对 JDBC 的支持可以通过引入 spring-boot-starter-jdbc 来完成,然而却没有一个 starter 可以直接支持 spring-orm 模块的特性,可能这也是相关资料比较少的原因。


Spring Boot 其实已经天然支持了基于 Hibernate 实现的 JPA 的自动化配置,各 starter 的作用仅仅是通过引入依赖来开启各特性自动化配置,因此无需对应的 starter 也可以开启 JPA 特性。那么对于 spring-orm 模块的 JPA 相关配置,需要引入哪些依赖呢?


参照 Spring Boot 自动化配置类 HibernateJpaAutoConfiguration 可以得出如下的依赖列表。


javax.persistence-api:这个是 JPA 规范的依赖。

hibernate-core:这个是 Hibernate 的依赖。

spring-orm:这个是对 JPA 支持的 Spring 依赖。

除了上面的依赖,还需要在 Spring 中存在一个 DataSource 才可以,这个 DataSource 可以手动定义,也可以利用 Spring Boot 的自动化配置,如果想要自动化配置 DataSource,可以直接引入 spring-boot-starter-jdbc,也可以引入 spring-jdbc 和 HikariCP,这两者是等价的,Spring 在类路径下监测到这两个模块后就会开启 DataSource 的自动化配置了。


依赖引入


综上,Spring Boot 整合 spring-orm JPA 引入的依赖和 Spring Framework 整合 JPA 引入的依赖基本保持一致,使用自动化配置 DataSource,在 Spring Boot 中整合 JPA 引入的依赖列表如下。


<!--等同于 spring-boot-starter + spring-jdbc + HikariCP-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>2.2.7.RELEASE</version>
</dependency>
<!--spring 对 jpa 的整合依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
</dependency>
<!--JPA 实现,引入后开启自动化配置-->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.6.9.Final</version>
</dependency>
<!--MySQL 驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>


引入这些依赖后 Spring Boot 还会自动开启声明式事务,无需手动在配置类上指定 @EnableTransactionManagement 注解。


配置


依赖引入只是为自动配置提供基本的条件,还需要提供给 Spring Boot 一些配置信息,Spring Boot 才能知道如何进行自动化配置。


在 application.properties 文件中添加如下内容。


# 数据源配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# JPA 配置
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true


除了上述的配置,还需要告诉 Spring Boot 哪些包中的 Entity 由 JPA 管理。可以使用 @EntityScan 注解指定。


@Configuration
@EntityScan(basePackages = "com.zzuhkp.jpa.entity")
public class JPAConfig {
}


数据库操作


有了必要的依赖和配置后,Spring Boot 会自动化配置 JPA 所需的 bean,然后直接进行数据库操作即可,和 Spring Framework 项目使用 JPA 的方式保持一致。示例如下。


@Repository
public class UserRepository {
    @PersistenceContext
    private EntityManager entityManager;
    @Transactional(rollbackFor = Exception.class)
    public Long saveUser(User user) {
        entityManager.persist(user);
        System.out.println(user.getId());
        throw new RuntimeException("test rollback");
    }
}


总结

得益于 Spring 优秀的设计,可以以统一的方式在 Spring 中使用事务。关于在 Spring Boot 中整合 JPA,也许把 spring-boot-starter-jdbc、spring-orm、hibernate-core 整合到一个 starter 中是一个更好的选择,为什么 Spring Boot 没有这样做呢?有了解的小伙伴请留言告知。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
21天前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
4天前
|
XML JSON Java
使用IDEA+Maven搭建整合一个Struts2+Spring4+Hibernate4项目,混合使用传统Xml与@注解,返回JSP视图或JSON数据,快来给你的SSH老项目翻新一下吧
本文介绍了如何使用IntelliJ IDEA和Maven搭建一个整合了Struts2、Spring4、Hibernate4的J2EE项目,并配置了项目目录结构、web.xml、welcome.jsp以及多个JSP页面,用于刷新和学习传统的SSH框架。
14 0
使用IDEA+Maven搭建整合一个Struts2+Spring4+Hibernate4项目,混合使用传统Xml与@注解,返回JSP视图或JSON数据,快来给你的SSH老项目翻新一下吧
|
5天前
|
安全 Java 数据安全/隐私保护
基于SpringBoot+Spring Security+Jpa的校园图书管理系统
本文介绍了一个基于SpringBoot、Spring Security和JPA开发的校园图书管理系统,包括系统的核心控制器`LoginController`的代码实现,该控制器处理用户登录、注销、密码更新、角色管理等功能,并提供了系统初始化测试数据的方法。
10 0
基于SpringBoot+Spring Security+Jpa的校园图书管理系统
|
5天前
|
前端开发 JavaScript Java
spring boot+vue前后端项目的分离(我的第一个前后端分离项目)
该博客文章介绍了作者构建的第一个前后端分离项目,使用Spring Boot和Vue技术栈,详细说明了前端Vue项目的搭建、后端Spring Boot项目的构建过程,包括依赖配置、数据库连接、服务层、数据访问层以及解决跨域问题的配置,并展示了项目的测试结果。
spring boot+vue前后端项目的分离(我的第一个前后端分离项目)
|
7天前
|
Java 关系型数据库 MySQL
|
14天前
|
IDE Java Shell
如何快速搭建一个 Spring Boot 项目?
本指南介绍如何通过Spring Initializr创建一个基本的Spring Boot Web项目。首先访问`start.spring.io`,选择Maven项目、Java语言、Spring Boot版本3.1.0、Java 17,并勾选Spring Web依赖。点击“Generate”下载项目模板。解压后,IDE打开项目并修改`DemoApplication.java`,添加REST控制器以实现一个简单的“Hello World!”服务。通过`@RestController`和`@GetMapping`注解定义Web端点,使用`@RequestParam`获取URL参数。
|
19天前
|
IDE Java Shell
如何快速搭建一个 Spring Boot 项目?
Spring Boot 可以用最少的配置来快速创建一个独立的、生产级的 Spring 应用程序。 本文介绍如何快速搭建一个 Spring Boot「Hello World!」项目。
|
23天前
|
Java Spring
idea新建spring boot 项目右键无package及java类的选项
idea新建spring boot 项目右键无package及java类的选项
34 5
|
23天前
|
Java 数据库连接 Spring
搭建 spring boot + mybatis plus 项目框架并进行调试
搭建 spring boot + mybatis plus 项目框架并进行调试
49 4
|
4天前
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作