重学SpringBoot3-Spring Data JPA

重学SpringBoot3-Spring Data JPA

CoderJia 22 2024-10-14

在现代 Java 应用开发中,数据持久化层是非常重要的组成部分。Spring Boot 3 和 Spring Data JPA 的结合为我们提供了一个简洁、高效的解决方案,可以快速实现数据访问层逻辑,减少冗余代码。本文将详细介绍如何在 Spring Boot 3 中集成 Spring Data JPA,并展示一些常见的用法和最佳实践。

1. 什么是 Spring Data JPA?

Spring Data JPA 是 Spring 提供的一个模块,基于 Java Persistence API (JPA) 规范,简化了数据访问层的开发。通过它,我们可以通过少量的配置和代码完成大多数的数据持久化操作。它帮助我们避免了繁琐的 DAO 层代码,实现了一种声明式的数据访问方式。

Spring Data JPA 能够自动生成常见的增删改查方法,并提供了强大的查询生成机制,允许根据方法名自动生成 SQL 语句。

2. Spring Data JPA 的核心概念

2.1. 实体(Entity)

JPA 中的实体是与数据库表对应的 Java 类。每个实体对象都代表数据库表中的一行记录,实体中的每个字段对应表中的一列。

package com.coderjia.boot315shardingsphere.bean;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;

@Data
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;
}

2.2. Repository(仓库)

Spring Data JPA 中的 Repository 是一个接口层,它通过接口方法帮助我们简化与数据库的交互。JpaRepository 是 Spring Data JPA 提供的最重要的接口之一,它包含了常用的 CRUD 操作,并且可以通过命名规则实现动态查询。

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

JpaRepository 继承了哪些接口?

  • CrudRepository:提供基础的增删改查方法(save, findById, delete 等)。
  • PagingAndSortingRepository:提供分页和排序功能。
  • QueryByExampleExecutor:提供查询范例(Query by Example)的能力。

2.3. 查询生成策略

Spring Data JPA 根据方法名称自动生成查询。比如 findByUsername 将自动生成类似于 SELECT * FROM users WHERE username = ? 的 SQL 语句。开发者只需定义方法名称,无需手动编写 SQL。

3. Spring Boot 3 集成 Spring Data JPA

3.1. 引入依赖

首先,在 Spring Boot 3 项目中,需要引入 spring-boot-starter-data-jpa 依赖,同时配置数据库驱动依赖。例如,使用 MySQL 的话,可以这样配置:

<dependencies>
    <!-- Spring Data JPA 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- MySQL 数据库驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

3.2. 数据源配置

application.yml 中配置数据库连接:

JPA 可用配置项见 org.springframework.boot.autoconfigure.orm.jpa.JpaProperties

spring:
  application:
    name: spring-boot3-15-shardingsphere
  datasource:
    url: jdbc:mysql://localhost:3306/master_db?useSSL=false&serverTimezone=UTC
    username: root
    password: root123
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    database-platform: org.hibernate.dialect.MySQL8Dialect
    generateDdl: true   # 自动更新数据库表结构
    show-sql: true        # 是否显示 SQL 语句

3.3. 创建实体类

接下来,我们创建一个简单的 User 实体:

package com.coderjia.boot315shardingsphere.bean;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;

/**
 * @author CoderJia
 * @create 2024/10/14 下午 07:52
 * @Description
 **/
@Data
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;
}

3.4. 创建 Repository 接口

接着,我们为 User 实体创建一个 UserRepository 接口,继承 JpaRepository,它会自动为我们生成常见的增删改查方法。

package com.coderjia.boot315shardingsphere.dao;

import com.coderjia.boot315shardingsphere.bean.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

/**
 * @author CoderJia
 * @create 2024/10/14 下午 07:54
 * @Description
 **/
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

3.5. 编写服务层

服务层封装了业务逻辑,依赖于 UserRepository 实现用户相关的操作。

package com.coderjia.boot315shardingsphere.service;

import com.coderjia.boot315shardingsphere.bean.User;
import com.coderjia.boot315shardingsphere.dao.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
 * @author CoderJia
 * @create 2024/10/14 下午 07:56
 * @Description
 **/
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User createUser(User user) {
        return userRepository.save(user);
    }

    public Optional<User> getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
    
}

3.6. 编写控制器

控制器层暴露了 REST API 接口,用户可以通过这些接口与应用进行交互。

package com.coderjia.boot315shardingsphere.controller;

import com.coderjia.boot315shardingsphere.bean.User;
import com.coderjia.boot315shardingsphere.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author CoderJia
 * @create 2024/10/14 下午 07:57
 * @Description
 **/
@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }

    @GetMapping("/{username}")
    public ResponseEntity<User> getUserByUsername(@PathVariable("username") String username) {
        return userService.getUserByUsername(username)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable("id") Long id) {
        userService.deleteUser(id);
    }
}

3.7. 运行应用

至此,我们已经完成了 Spring Boot 3 集成 Spring Data JPA 的基本配置和功能实现。运行应用后,Spring Data JPA 会自动创建数据库表,并处理数据库的 CRUD 操作。

自动创建数据库表

3.8. 测试

3.8.1 新增用户

新增

3.8.2 查询用户

查询

3.8.3 查询所有

查询所有

3.8.4 删除用户

删除

4. 高级功能

除了基本的增删改查功能,Spring Data JPA 还提供了许多高级功能,例如分页、排序和自定义查询。

4.1. 分页与排序

Spring Data JPA 提供了分页与排序的功能,利用 Pageable 接口,我们可以轻松实现数据的分页查询。

    public Page<User> getUsersPaged(Pageable pageable) {
        return userRepository.findAll(pageable);
    }

在控制器中使用分页查询时,可以通过请求参数传递分页信息:

    @GetMapping("/paged")
    public Page<User> getUsersPaged(@RequestParam("page") int page, @RequestParam("size") int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userService.getUsersPaged(pageable);
    }

分页

4.2. 自定义查询

除了通过方法名称生成查询,Spring Data JPA 还允许我们使用 @Query 注解编写自定义的 JPQL 或原生 SQL 查询。

public interface UserRepository extends JpaRepository<User, Long> {

    @Query("SELECT u FROM User u WHERE u.username = ?1")
    Optional<User> findUserByUsername(String username);
}

4.3. 乐观锁与悲观锁

通过 Spring Data JPA,可以使用 JPA 提供的乐观锁和悲观锁机制,确保在并发环境下的数据一致性。

@Data
@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private Integer stock;

    @Version
    private Integer version;  // 乐观锁版本字段
}

创建 ProductRepository

import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}

创建 ProductService

import jakarta.persistence.OptimisticLockException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    public void updateProductStock(Long productId, int quantity) {
        try {
            Product product = productRepository.findById(productId).orElseThrow();
            product.setStock(product.getStock() - quantity);
            productRepository.save(product);  // 触发乐观锁机制
        } catch (OptimisticLockException e) {
            System.out.println("OptimisticLockException: Data has been modified by another transaction.");
        }
    }
}

测试乐观锁

@SpringBootTest
class OptimisticLockTest {

    @Autowired
    private ProductService productService;

    @Test
    void testOptimisticLocking() throws InterruptedException {
        // 启动两个线程同时修改同一条记录
        Thread thread1 = new Thread(() -> productService.updateProductStock(1L, 1));
        Thread thread2 = new Thread(() -> productService.updateProductStock(1L, 1));

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
    }
}

测试乐观锁

5. 总结

Spring Boot 3 + Spring Data JPA 提供了一个强大且易用的数据访问层解决方案。它大大减少了数据访问层的样板代码,使我们能够专注于业务逻辑的开发。通过 JPA 的实体映射、动态查询、分页排序、乐观锁等特性,我们可以非常高效地开发应用程序。

集成 Spring Data JPA 后,我们可以快速上手,构建面向数据库的应用程序,同时保留灵活的扩展能力和强大的功能支持。