Spring 加强版 ORM 框架 spring-data-jpa 入门与实践

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
简介: 前言伴随着 Java 诞生与发展,目前 Java 界涌现出了五花八门的数据访问技术,有一些名词甚至达到了耳熟能详的程度,包括 JDBC、JTA、JPA、ORM、MyBatis 等等,这篇介绍的是 Spring Data 项目中的 spring-data-jpa 框架,了解其他相关技术可以查阅我前面的文章。

前言


伴随着 Java 诞生与发展,目前 Java 界涌现出了五花八门的数据访问技术,有一些名词甚至达到了耳熟能详的程度,包括 JDBC、JTA、JPA、ORM、MyBatis 等等,这篇介绍的是 Spring Data 项目中的 spring-data-jpa 框架,了解其他相关技术可以查阅我前面的文章。


由于这个框架目前在国内使用较少,本人也没有在实际项目中使用过,因此本篇更多的是带领大家对它有一个基本的认识,至于源码部分这里也不再进行分析,有感兴趣的小伙伴可以留言讨论交流。


背景与发展


JDBC 与 JTA


在 Java 中,一门技术的诞生往往是规范先行。为了在 Java 中以统一的方式访问不同的关系型数据库,sun 公司制定了 JDBC 规范,由各数据库厂商提供具体的实现。


JDBC 定义了对单个数据库的事务的使用方式,如果有多个数据库想同时加入事务,JDBC 就力不从心了,因此有时候我们还会听到 JTA 的声音,JTA 通过两阶段提交支持多个数据库加入一个事务。


ORM 框架


由于关系型数据库和面向对象编程的不同,加之 JDBC 规范的复杂性,在实际使用中会出现大量的样板式代码,包括创建连接、创建语句、查询数据库、操作结果转换为 Java 对象、关闭结果集、关闭语句、关闭连接。


为了应对使用 JDBC 的复杂性,诞生出了不少 ORM 框架,ORM 框架将这些通用的操作封装在内部,而将必须由用户定义的部分暴露出来,例如映射关系、一些复杂的 SQL 等等。


比较热门的一个 ORM 框架是 Hibernate,仅仅定义映射关系就足够了,Hibernate 将 JDBC 封装在内部,可以只使用框架提供的 API 操作数据库,一行 SQL 都不用手工书写。


另一种比较热门的 ORM 框架是 MyBatis,由于每个查询都需要单独提供映射关系,MyBatis 也被称为半自动化 ORM 框架,MyBatis 的 SQL 是手动定义的,因此相对 Hibernate 更灵活一些,能适用更复杂的场景,目前比较流行。


JPA


由于不同的 ORM 框架使用方法有所不同,为了统一 ORM 框架的使用,sun 公司又设计了一套 ORM 规范,这就是我们常听到的 JPA。JPA 的设计参考了 Hibernate,Hibernate 后来又反向实现了 JPA 规范,Hibernate 也成了目前最常用的 JPA 实现。


spring-data-jpa


Spring 框架作为 Java 事实上的标准,也对 JPA 进行了整合,最初在 spring-framework 框架中的 spring-orm 模块进行了整合,不过这里只是将 JPA 加入到 Spring 的事务管理中。


除了 spring-orm,Spring 对 JPA 整合的另一个模块是 spring-data-jpa,也就是今天的主题,关于上面提到的这些技术,可以用如下的图示来表示。


image.png

认识 spring-data-jpa


spring-data-jpa 其实只是 Spring Data 项目中的其中一个模块,Spring Data 项目旨在以相同或相似的方式操作不同的持久化实现,包括各种关系型数据库、非关系型数据库等。


其中 spring-data-commons 是其他模块依赖的公共模块,保证了不同持久化实现的使用方式统一,Spring Data 各模块通用的使用方式可参考《Spring 加强版 ORM 框架 Spring Data 入门》,这篇主要介绍 spring-data-jpa 特有的一些使用方式。


image.png


由于 spring-orm 已经实现了 JPA 与 Spring 事务的整合,spring-data-jpa 在底层直接复用了 spring-orm,只是在使用方式上又包装了一层,以适配 Spring Data 项目。如果你对 spring-orm 不了解,可以参考 《Spring 项目快速整合 JPA》,本篇同样参照了其中的示例。


快速上手


下面通过案例的形式演示如何在项目中使用 spring-data-jpa。


依赖引入


使用 spring-data-jpa 首先需要引入依赖,先来看下在 spring-framework 项目中的使用方式。


由于 Spring Data 项目各模块的发布时间有所不同,各模块以及底层依赖的 spring-framework 项目模块的版本号无法做到统一,Spring Data 官方提供了一个 bom 来维护版本号,将其添加到 maven pom 文件,然后就可以省略 Spring Data 各模块的版本号了。


<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-bom</artifactId>
            <version>2021.0.7</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>


除了这个 bom ,我们还需要添加其他的依赖,具体如下。


<!--JPA  依赖-->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
</dependency>
<!--JPA 实现-->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.6.9.Final</version>
</dependency>
<!--数据库驱动-->
<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>


其中 spring-data-jpa 无需指定版本号,Hibernate 作为 JPA 的实现,除此之外还需要引入具体数据库的驱动,这里使用的是 MySQL,然后还需要引入获取 Connection 的数据源,这里使用的是 HikariCP。


映射定义


测试使用的数据库表如下。


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;
}


启用 JPA


每个 ORM 框架都有一个操作数据库的核心类,例如对于 MyBatis 来说是 SqlSession,对于 Hibernate 来说是 Session,对于 JPA 来说是 EntityManager,对于 Spring Data 来说则是 Repository。


不过 Spring Data 的 Repository 比较特殊,它是一个由用户定义的接口,用户可以提供自定义的实现,也可以由 Spring Data 具体模块创建接口的代理作为 bean,通过解析方法来查找具体实现。


为了创建 Repository 接口的实现 bean,可以通过 @EnableJpaRepository 注解来开启,示例代码如下。


@Configuration
@EnableJpaRepositories(basePackages = "com.zzuhkp.jpa",
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager")
@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 entityManagerFactory() {
        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-data-jpa 依赖 spring-orm,因此有一些配置是和在 spring-orm 中使用 JPA 相同的,包括数据源、EntityManagerFactory、事务管理器。


@EnableJpaRepositories 注解的 basePackages 属性可以指定为哪些包中的 Repository 创建代理 bean。

entityManagerFactoryRef 可以指定底层依赖的 EntityManagerFactory,默认值为 entityManagerFactory。

transactionManagerRef 可以指定底层依赖的事务管理器,默认值为 transactionManager。


数据库操作


数据操作最重要的是 Repository 的定义,我们一般会定义一个继承 spring-data-jpa 模块提供的 JpaRepository 接口的 Repository 接口,这种方式可以大大简化一些常用操作方法的重复定义,这有些类似 MyBatis-Plus 框架提供的 BaseMapper。示例如下。


public interface UserRepository extends JpaRepository<User, Long> {
}


根据前面 @EnableJpaRepository 注解的配置,spring-data-jpa 会自动为这个接口提供实现并注册为 bean。


基本操作


对于单表的一些简单操作,使用 JpaRepository 接口提供的方法就可以了,JpaRepository 还继承了一些其他接口,使用非常方便,接口定义如下。


public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
}


PagingAndSortingRepository 是所有 Spring Data 模块都支持的接口,提供了基本的增删改查方法,QueryByExampleExecutor 是 spring-data-jpa 模块特有的接口,用于复杂查询,先看下 JpaRepository 提供的一些方法。


1. 添加/修改


public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    <S extends T> List<S> saveAll(Iterable<S> entities);
    <S extends T> S saveAndFlush(S entity);
    <S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
}

与其他 Spring Data 模块一样,spring-data-jpa 对于添加和修改操作,使用的是相同的方法,根据是否为新记录决定进行何种操作。


默认情况先根据版本号字段和标识符字段判断是否为新实体,如果实体中的标识符字段值是手动设置的,可以选择将实体实现接口 Persistable 自定义判断逻辑。


2. 删除


JpaRepository 包含的删除方法如下。


public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    void deleteAllInBatch(Iterable<T> entities);
    void deleteAllByIdInBatch(Iterable<ID> ids);
    void deleteAllInBatch();
}


3. 查询


JpaRepository 包含的简单查询方法如下。


public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();
    List<T> findAll(Sort sort);
    List<T> findAllById(Iterable<ID> ids);
}


Example 查询


Example 能解决一些比 CrudRepository 更复杂的问题,主要用于属性匹配,需要 Repository 继承接口 QueryByExampleExecutor 才可以,示例如下。


public User login(String username, String password) {
    User user = new User();
    user.setUsername(username);
    user.setPassword(password);
    Optional<User> one = userRepository.findOne(Example.of(user));
    return one.get();
}

等价于如下 SQL:

select id,username,password from user where username = ? and password = ?


Specification 查询


spring-data-jpa 提供了对 JPA CriteriaQuery 的支持,将其封装在了 Specification 接口内部,通过 Repository 继承接口 JpaSpecificationExecutor 就可以使用了,示例如下。


public User login(String username, String password) {
    Optional<User> one = userRepository.findOne(new Specification<User>() {
        @Override
        public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
            return criteriaBuilder.equal(root.get("username"), username);
        }
    }.and(new Specification<User>() {
        @Override
        public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
            return criteriaBuilder.equal(root.get("password"), password);
        }
    }));
    return one.get();
}


上面的查询等价于如下的 SQL:

select * from user where username = ? and password = ?


可以看到,这种使用方式实在太复杂了,强烈不建议使用。


方法解析查询


方法解析策略


对于一些复杂方法的查询,主要依靠在 Repository 自定义查询方法实现的,那么 spring-data-jpa 是怎样将方法签名解析为具体的 SQL 执行的呢?


可以通过 @EnableJpaRepository 注解的 queryLookupStrategy 属性配置,各取值含义如下。


取值

含义

CREATE 从方法名中解析 SQL
USE_DECLARED_QUERY 从声明的查询中解析 SQL
CREATE_IF_NOT_FOUND 优先从声明的查询中解析 SQL,如果解析不到则使用方法名解析,这个是默认的查找策略


方法名解析 SQL


根据方法名解析 SQL 需要方法名遵循特定的结构,以用户登录查询用户信息为例,示例如下。


public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsernameAndPassword(String username, String password);
}


这将转换为如下的 SQL:

select id,username,password from user where username = ? and password = ?

如果你使用的 IDE 是 idea,还会有非常智能的代码提示。


image.png


更多查询关键字,可以参考 spring-data-jpa 官网 附录 C:存储库查询关键字 一节。


声明查询解析 SQL


1. 命名查询


spring-data-jpa 对 JPA 提供的命名查询提供了支持,可以将命名查询的注解用在实体类上。


@NamedQuery(name = "User.login", query = "select u from User u where u.username = ?1 and u.password = ?2")
public class User {
}

然后在 Repository 中将命名查询的名字作为方法名即可。

public interface UserRepository extends JpaRepository<User, Long> {
    User login(String username, String password);
}


2. @Query 查询


spring-data-jpa 还提供了一个 Query 注解,同时支持 JPQL 和 SQL,命名查询和非命名查询,将这个注解添加在 Repository 方法上即可,与上述 @NamedQuery 等价的 @Query 注解如下。


public interface UserRepository extends JpaRepository<User, Long> {
    @Query("select u from User where u.username = ?1 and u.password = ?2")
    User login(String username, String password);
}

如果想使用 SQL 查询,可以修改如下。

public interface UserRepository extends JpaRepository<User, Long> {
    @Query(value = "select * from user where username = ?1 and password = ?2",
            nativeQuery = true)
    User login(String username, String password);
}


如果想使用命名查询,可以将 JPQL 或 SQL 配置在 META-INF/jpa-named-queries.properties 文件中。


User.login = select u from User u where u.username = ?1 and u.password = ?2


然后在 @Query 注解中指定名称即可。


public interface UserRepository extends JpaRepository<User, Long> {
    @Query(name = "User.login")
    User login(String username, String password);
}


另外这个命名查询文件的位置还可以在 @EnableJpaRepository 的 namedQueriesLocation 属性中配置。


分页与排序


如果需要分页或者排序,可以将 Pageable 或 Sort 参数附加在方法参数后面,同时支持 @NamedQuery 与 @Query 注解,示例如下。


public interface UserRepository extends JpaRepository<User, Long> {
    @Query("select u from User u where u.username = ?1")
    Page<User> page(String username, Pageable pageable);
    @Query("select u from User u where u.username = ?1")
    List<User> sort(String username, Sort sort);
}


命名参数


默认情况,spring-data-jpa 采用基于位置的参数绑定,在重构代码时很容易出错,为了解决这个问题,spring-data-jpa 添加了对命名参数的支持,使用示例如下。

public interface UserRepository extends JpaRepository<User, Long> {
    @Query("select u from User u where u.username = :user and u.password = :pwd")
    User login(@Param("user") String username, @Param("pwd") String password);
}


JPQL 或 SQL 中的参数使用 :参数名 的形式表示,然后在方法参数前使用 @Param 指定对应的 JPQL 或 SQL 参数。


修改查询


spring-data-jpa 同样支持复杂的修改 SQL,在方法上添加 @Modifying 注解即可,示例如下。

public interface UserRepository extends JpaRepository<User, Long> {
    @Modifying
    @Query("delete from User u where u.username = :user and u.password = :pwd")
    int delete(@Param("user") String username, @Param("pwd") String password);
}


事务


spring-data-jpa 默认支持 Spring 的事务,对于查询操作 readyOnly 将设置为 true,也可以在 Repository 方法上手动修改事务配置。

public interface UserRepository extends JpaRepository<User, Long> {
    @Transactional(timeout = 20)
    User findByUsernameAndPassword(String username, String password);
}


审计


审计用于记录创建或修改的人以及时间,spring-data-jpa 提供了独有的支持。

public class User {
    @CreatedDate
    private Date createTime;
    @CreatedBy
    private String createBy;
    @LastModifiedDate
    private Date updateTime;
    @LastModifiedBy
    private String updateBy;
}


在实体类上添加上面的注解即可,spring-data-jpa 可以取当前时间作为创建或修改时间,对于操作人则需要手动告诉 spring-data-jpa


@EnableJpaAuditing
public class JpaConfig {
    @Bean
    public AuditorAware<String> auditorAware() {
        return new AuditorAware<String>() {
            @Override
            public Optional<String> getCurrentAuditor() {
                return Optional.of("currentUser");
            }
        };
    }
}    


AuditorAware bean 用来指定当前操作人,@EnableJpaAuditing 注解则用于开启审计功能。


如果不想使用注解,还可以将实体类实现 Auditable 接口,这里不再赘述。


spring-boot 整合


spring-boot 对 spring-data-jpa 的支持主要在于自动化配置,添加一个 starter 即可。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

这个 starter 包含了 Hibernate、HikariCP 的依赖,Spring 会自动配置 DataSource、EntityManagerFactory、TransactionManager,以及启用 JPA Repository bean 注册与查找的功能,当然了,必须的数据库驱动还是需要用户手动提供的。


总结

spring-data-jpa 对 JPA 进行了封装与简化,如果需要使用 JPA,建议直接引入 spring-boot-starter-data-jpa。


目录
相关文章
|
5月前
|
存储 人工智能 Java
AI 超级智能体全栈项目阶段四:学术分析 AI 项目 RAG 落地指南:基于 Spring AI 的本地与阿里云知识库实践
本文介绍RAG(检索增强生成)技术,结合Spring AI与本地及云知识库实现学术分析AI应用,利用阿里云Qwen-Plus模型提升回答准确性与可信度。
1678 90
AI 超级智能体全栈项目阶段四:学术分析 AI 项目 RAG 落地指南:基于 Spring AI 的本地与阿里云知识库实践
|
6月前
|
安全 Java Ruby
我尝试了所有后端框架 — — 这就是为什么只有 Spring Boot 幸存下来
作者回顾后端开发历程,指出多数框架在生产环境中难堪重负。相比之下,Spring Boot凭借内置安全、稳定扩展、完善生态和企业级支持,成为构建高可用系统的首选,真正经受住了时间与规模的考验。
452 2
|
5月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
5月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
356 8
|
5月前
|
人工智能 监控 Java
Spring AI Alibaba实践|后台定时Agent
基于Spring AI Alibaba框架,可构建自主运行的AI Agent,突破传统Chat模式限制,支持定时任务、事件响应与人工协同,实现数据采集、分析到决策的自动化闭环,提升企业智能化效率。
Spring AI Alibaba实践|后台定时Agent
|
5月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
592 2
|
SQL 关系型数据库 Java
【spring源码学习】spring集成orm数据框架
【一】简易的数据源配置 (1)配置文件 jdbc:mysql://localhost:3306/mobile_thinks root shangxiaofei ...
1277 0
|
8月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1203 0
|
9月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
985 0
|
5月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
491 4