芋道 Spring Boot JPA 入门

点击上方“Java基基”,选择“设为星标”

做积极的人,而不是积极废人!

源码精品专栏

 

摘要: 原创出处 http://www.iocoder.cn/Spring-Boot/JPA/ 「芋道源码」欢迎转载,保留摘要,谢谢!

  • 1. 概述

  • 2. 快速入门

  • 3. 分页操作

  • 4. 基于方法名查询

  • 5. 基于注解查询

  • 666. 彩蛋


本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-13 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

1. 概述

我们,咱们来学习下 Spring Data JPA 。

相信不少胖友之前有了解过 JPA、Hibernate ,那么 JPA、Hibernate、Spring Data JPA 这三者是什么关系呢?我们来一起理一理。

JPA ,全称 Java Persistence API ,是由 Java 定义的 Java ORM 以及实体操作 API 的标准。正如最早学习 JDBC 规范,Java 自身并未提供相关的实现,而是 MySQL 提供 MySQL mysql-connector-java 驱动,Oracle 提供 oracle-jdbc 驱动。而实现 JPA 规范的有:

  • Hibernate ORM

  • Oracle TopLink

  • Apache OpenJPA

Spring Data JPA ,是 Spring Data 提供的一套简化的 JPA 开发的框架。

  • 内置 CRUD、分页、排序等功能的操作。

  • 根据约定好的方法名规则,自动生成对应的查询操作。

  • 使用 @Query 注解,自定义 SQL 。

所以,绝大多数情况下,我们无需编写代码,直接调用 JPA 的 API 。也因此,在我们使用的 Spring Data JPA 的项目中,如果想要替换底层使用的 JPA 实现框架,在未使用到相关 JPA 实现框架的特殊特性的情况下,可以透明替换。

关于这一点,我们在 《芋道 Spring Boot Redis 入门》 中,已经看到 Spring Data Redis 也是已经看到这样的好处。

总的来说,就是如下一张图:

FROM 《spring data jpa hibernate jpa 三者之间的关系》

当然,绝大多数情况下,我们使用的 JPA 实现框架是 Hibernate ORM 。所以整个调用过程是:

应用程序 => Repository => Spring Data JPA => Hibernate

2. 快速入门

示例代码对应仓库:lab-13-jpa 。

本小节,我们会使用 spring-boot-starter-data-jpa 自动化配置 Spring Data JPA 。同时,演示 Spring Data JPA 的 CRUD 的操作。

2.1 引入依赖

pom.xml 文件中,引入相关依赖。

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-13-jpa</artifactId>

    <dependencies>
        <!-- 实现对数据库连接池的自动化配置 -->
        <!-- 实际上 spring-boot-starter-data-jpa 已经包括 spring-boot-starter-jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency> <!-- 本示例,我们使用 MySQL -->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

        <!-- 实现对 Spring Data JPA 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- 方便等会写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

具体每个依赖的作用,胖友自己认真看下艿艿添加的所有注释噢。

另外,在 spring-boot-starter-data-jpa 中,已经默认引入了 Hibernate 的依赖。

2.2 Application

创建 Application.java 类,配置 @SpringBootApplication 注解即可。代码如下:

// Application.java

@SpringBootApplication
public class Application {
}

2.3 配置文件

application.yml 中,添加 JPA 配置,如下:

spring:
  # datasource 数据源配置内容
  datasource:
    url: jdbc:mysql://47.112.193.81:3306/testb5f4?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.jdbc.Driver
    username: testb5f4
    password: F4df4db0ed86@11
  # JPA 配置内容,对应 JpaProperties 类
  jpa:
    show-sql: true # 打印 SQL 。生产环境,建议关闭
    # Hibernate 配置内容,对应 HibernateProperties 类
    hibernate:
      ddl-auto: none
  • datasource 配置项,配置 datasource 数据源配置内容。

  • jpa 配置项,配置 Spring Data JPA 配置内容,对应 org.springframework.boot.autoconfigure.orm.jpa.JpaProperties.java 类。

  • hibernate 配置项,配置 Hibernate 配置内容,对应 org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties.java 类。

    • create :每次加载 hibernate 时都会删除上一次的生成的表,然后根据你的 model 类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。

    • create-drop :每次加载 hibernate 时根据 model 类生成表,但是 sessionFactory 一关闭,表就自动删除。

    • update :最常用的属性,第一次加载 hibernate 时根据 model 类会自动建立起表的结构(前提是先建立好数据库),以后加载 hibernate 时根据 model 类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。

    • validate :每次加载 hibernate 时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

    • 建议,生产环境下,建议配置 none ,不使用 Hibernate Auto DDL 功能。???? 启动个项目,就自动变更数据库表结构,多危险啊~

    • 实际项目无需配置 hibernate 配置项,这里仅仅是演示,让胖友知道这回事。

    • ddl-auto 配置项,设置 Hibernate DDL 处理策略。一共有 nonecreatecreate-dropupdatevalidate 五个选项。

      FROM 《jpa 的 hibernate.ddl-auto 的几个属性值区别》

2.4 UserDO

cn.iocoder.springboot.lab13.jpa.dataobject 包路径下,创建 UserDO.java 类,用户 DO 。代码如下:

// UserDO.java

@Entity
@Table(name = "users")
public class UserDO {

    /**
     * 用户编号
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY,  // strategy 设置使用数据库主键自增策略;
            generator = "JDBC") // generator 设置插入完成后,查询最后生成的 ID 填充到该属性中。
    private Integer id;
    /**
     * 账号
     */
    @Column(nullable = false)
    private String username;
    /**
     * 密码(明文)
     *
     * ps:生产环境下,千万不要明文噢
     */
    @Column(nullable = false)
    private String password;
    /**
     * 创建时间
     */
    @Column(name = "create_time", nullable = false)
    private Date createTime;

    // ... 省略 setting/getting 方法

}

关于 JPA 的注解的详细说明,胖友后面再看看 《Spring Data JPA 中常用的注解详解》 文章。我们,继续往下看。

对应的创建表的 SQL 如下:

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',
  `username` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号',
  `password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

2.5 UserRepository01

cn.iocoder.springboot.lab13.mybatis.repository 包路径下,创建 UserRepository01 接口。代码如下:

// UserRepository01.java

public interface UserRepository01 extends CrudRepository<UserDO, Integer> {

}
  • 继承 org.springframework.data.repository.CrudRepository 接口,第一个泛型设置对应的实体是 UserDO ,第二个泛型设置对应的主键类型是 Integer 。

  • 因为实现了 CrudRepository 接口,Spring Data JPA 会自动生成对应的 CRUD 的代码。具体 CrudRepository 提供了哪些操作,胖友点击 CrudRepository.java 查看。

2.6 简单测试

创建 UserRepository01Test 测试类,我们来测试一下简单的 UserRepository01 的每个操作。代码如下:

// UserRepository01.java

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testInsert() {
        UserDO user = new UserDO().setUsername(UUID.randomUUID().toString())
                .setPassword("nicai").setCreateTime(new Date());
        userMapper.insert(user);
    }

    @Test
    public void testUpdateById() {
        UserDO updateUser = new UserDO().setId(1)
                .setPassword("wobucai");
        userMapper.updateById(updateUser);
    }

    @Test
    public void testDeleteById() {
        userMapper.deleteById(2);
    }

    @Test
    public void testSelectById() {
        userMapper.selectById(1);
    }

    @Test
    public void testSelectByUsername() {
        userMapper.selectByUsername("yunai");
    }

    @Test
    public void testSelectByIds() {
        List<UserDO> users = userMapper.selectByIds(Arrays.asList(1, 3));
        System.out.println("users:" + users.size());
    }

}

具体的,胖友可以自己跑跑,妥妥的。

3. 分页操作

示例代码对应仓库:lab-13-jpa 。

Spring Data 提供 org.springframework.data.repository.PagingAndSortingRepository 接口,继承 CrudRepository 接口,在 CRUD 操作的基础上,额外提供分页和排序的操作。代码如下:

// PagingAndSortingRepository.java

public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

 Iterable<T> findAll(Sort sort); // 排序操作

 Page<T> findAll(Pageable pageable); // 分页操作

}

3.1 UserRepository02

cn.iocoder.springboot.lab13.mybatis.repository 包路径下,创建 UserRepository02 接口。代码如下:

// UserRepository02.java

public interface UserRepository02 extends PagingAndSortingRepository<UserDO, Integer> {

}
  • 实现 PagingAndSortingRepository 接口,第一个泛型设置对应的实体是 UserDO ,第二个泛型设置对应的主键类型是 Integer 。

3.2 简单测试

创建 UserRepository02Test 测试类,我们来测试一下简单的 UserRepository02 的每个操作。代码如下:

// UserRepository02Test.java

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepository02Test {

    @Autowired
    private UserRepository02 userRepository;

    @Test // 排序
    public void testFindAll() {
        // 创建排序条件
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        // 执行排序操作
        Iterable<UserDO> iterable = userRepository.findAll(sort);
        // 打印
        iterable.forEach(System.out::println);
    }

    @Test // 分页
    public void testFindPage() {
        // 创建排序条件
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        // 创建分页条件
        Pageable pageable = PageRequest.of(1, 10, sort);
        // 执行分页操作
        Page<UserDO> page = userRepository.findAll(pageable);
        // 打印
        System.out.println(page.getTotalElements());
        System.out.println(page.getTotalPages());
    }

}

具体的,胖友可以自己跑跑,妥妥的。

4. 基于方法名查询

示例代码对应仓库:lab-13-jpa 。

在 Spring Data 中,支持根据方法名作生成对应的查询(WHERE)条件,进一步进化我们使用 JPA ,具体是方法名以 findByexistsBycountBydeleteBy 开头,后面跟具体的条件。具体的规则,在 《Spring Data JPA —— Query Creation》 文档中,已经详细提供。如下:

关键字方法示例JPQL snippet
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is, EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNull, NullfindByAge(Is)Null… where x.age is null
IsNotNull, NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)
  • 注意,如果我们有排序需求,可以使用 OrderBy 关键字。

下面,我们来编写一个简单的示例。

艿艿:IDEA 牛逼,提供的插件已经能够自动提示上述关键字。太强了~

4.1 UserRepository03

cn.iocoder.springboot.lab13.mybatis.repository 包路径下,创建 UserRepository03 接口。代码如下:

// UserRepository03.java

public interface UserRepository03 extends PagingAndSortingRepository<UserDO, Integer> {

    UserDO findByUsername(String username);

    Page<UserDO> findByCreateTimeAfter(Date createTime, Pageable pageable);

}
  • 对于分页操作,需要使用到 Pageable 参数,需要作为方法的最后一个参数。

4.2 简单测试

创建 UserRepository03Test 测试类,我们来测试一下简单的 UserRepository03 的每个操作。代码如下:

// UserRepository03.java

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepository03Test {

    @Autowired
    private UserRepository03 userRepository;

    @Test
    public void testFindByUsername() {
        UserDO user = userRepository.findByUsername("yunai");
        System.out.println(user);
    }

    @Test
    public void testFindByCreateTimeAfter() {
        // 创建分页条件
        Pageable pageable = PageRequest.of(1, 10);
        // 执行分页操作
        Date createTime = new Date(2018 - 1990, Calendar.FEBRUARY, 24); // 临时 Demo ,实际不建议这么写
        Page<UserDO> page = userRepository.findByCreateTimeAfter(createTime, pageable);
        // 打印
        System.out.println(page.getTotalElements());
        System.out.println(page.getTotalPages());
    }

}

具体的,胖友可以自己跑跑,妥妥的。

5. 基于注解查询

虽然 Spring Data JPA 提供了非常强大的功能,可以满足绝大多数业务场景下的 CRUD 操作,但是可能部分情况下,我们可以使用在方法上添加 org.springframework.data.jpa.repository.@Query 注解,实现自定义的 SQL 操作。

如果是更新或删除的 SQL 操作,需要额外在方法上添加 org.springframework.data.jpa.repository.@Modifying 注解。

下面,我们来编写一个简单的示例。

5.1 UserRepository04

cn.iocoder.springboot.lab13.mybatis.repository 包路径下,创建 UserRepository04 接口。代码如下:

// UserRepository04.java

public interface UserRepository04 extends PagingAndSortingRepository<UserDO, Integer> {

    @Query("SELECT u FROM UserDO u WHERE u.username = ?1")
    UserDO findByUsername01(String username); // <1>

    @Query("SELECT u FROM UserDO u WHERE u.username = :username")
    UserDO findByUsername02(@Param("username") String username); // <2>

    @Query(value = "SELECT * FROM users u WHERE u.username = :username", nativeQuery = true)
    UserDO findByUsername03(@Param("username") String username); // <3>

    @Query("UPDATE UserDO  u SET u.username = :username WHERE u.id = :id")
    @Modifying
    int updateUsernameById(Integer id, String username); // <4>

}
  • <1> 处,使用 @Query 自定义了一个 SQL 操作,并且参数使用占位符(?) + 参数位置的形式。

  • <2> 处,和 <1> 类似,差异在于使用占位符(:) + **参数名字(需要使用 @Param 声明)**的形式。

  • <3> 处,和 <2> 类似,差别在于我们增加了 nativeQuery = true ,表示在 @Query 自定义的是原生 SQL,而非在 <1><2> 自定义的是 JPQL 。进一步的说:

    • <1><2> 处,FROM UserDO ,使用的是实体名。

    • <3> 处,使用的是表名。

    • 对 JPQL 不是很了解的胖友,可以看看 《JPQL 的学习》 文章。

  • <4> 处,定义了更新操作,需要加上 @Modifying  注解。???? 另外,我们发可以现,使用参数名时,可以不用配合 @Param 注解。

5.2 简单测试

创建 UserRepository04Test 测试类,我们来测试一下简单的 UserRepository04 的每个操作。代码如下:

// UserRepository04Test.java

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepository04Test {

    @Autowired
    private UserRepository04 userRepository;

    @Test
    public void testFindIdByUsername01() {
        UserDO user = userRepository.findByUsername01("yunai");
        System.out.println(user);
    }

    @Test
    public void testFindIdByUsername02() {
        UserDO user = userRepository.findByUsername02("yunai");
        System.out.println(user);
    }

    @Test
    public void testFindIdByUsername03() {
        UserDO user = userRepository.findByUsername03("yunai");
        System.out.println(user);
    }

    @Test
    // 更新操作,需要在事务中。
    // 在单元测试中,事务默认回滚,所以胖友可能怎么测试,事务都不更新。
    @Transactional
    public void testUpdateUsernameById() {
        userRepository.updateUsernameById(5, "yudaoyuanma");
    }

}

具体的,胖友可以自己跑跑,妥妥的。

对于分页操作,需要在 @Query 编写查询分页列表和记录总数两条 SQL 。示例如下:

public interface UserRepository extends JpaRepository<User, Long> {

  @Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1", // value 属性,编写查询分页列表的 SQL 。
    countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1", // countQuery 属性,编写记录总数的 SQL 。
    nativeQuery = true)
  Page<User> findByLastname(String lastname, Pageable pageable);

}

666. 彩蛋

???? 本文仅仅是 Spring Data JPA 的简单入门,还有部分内容,胖友可以自己在去学习下:

  • 《Using JPA Named Queries》 ,可以使用 XML 自定义 SQL 操作。

  • 《Spring Data JPA 实现逻辑删除》 ,绝大多数业务场景下,我们不会使用 DELETE 物理删除,而是通过标志位进行逻辑删除。

  • 多表查询

    • 方式一:《JPA 多表查询的解决办法》

    • 方式二:《JPA 多表关联查询》

  • 《Spring Data JPA 使用 Example 快速实现动态查询》

    艿艿,如果在这种情况下,Repository 需要继承 JpaRepository 接口。

如果胖友想找一个完整的,使用 JPA 的项目,可以看看 Apollo 。它是携程开源的配置中心,目前最好用的配置中心,基本没有之一,嘿嘿。

推荐阅读:

  • 《性能测试 —— MySQL 基准测试》

  • 《芋道 Spring Boot 多数据源(读写分离)入门》 对应 lab-17 。

  • 《芋道 Spring Boot 分库分表入门》 对应 lab-18 。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值