Skip to content
清晨的一缕阳光
返回

Spring Boot Spring Data JPA 实战

前言

Spring Data JPA 是基于 JPA 规范的数据访问框架,简化了数据库操作。它通过方法命名约定自动生成查询,让数据访问变得简单优雅。

快速开始

1. 添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

2. 配置数据库

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 10
  
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.MySQLDialect

3. 创建实体类

package com.example.demo.entity;

import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;

@Data
@Entity
@Table(name = "user")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 50)
    private String username;
    
    @Column(unique = true, length = 100)
    private String email;
    
    private Integer age;
    
    @CreationTimestamp
    @Column(updatable = false)
    private LocalDateTime createTime;
    
    @UpdateTimestamp
    private LocalDateTime updateTime;
}

4. 创建 Repository

package com.example.demo.repository;

import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    // 方法命名查询
    Optional<User> findByUsername(String username);
    
    Optional<User> findByEmail(String email);
    
    List<User> findByAgeGreaterThan(int age);
    
    List<User> findByUsernameContaining(String keyword);
}

5. 创建 Service

package com.example.demo.service;

import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    
    public User findById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> BusinessException.notFound("用户"));
    }
    
    public List<User> findAll() {
        return userRepository.findAll();
    }
    
    public User create(User user) {
        return userRepository.save(user);
    }
    
    public User update(Long id, User user) {
        User existing = findById(id);
        existing.setUsername(user.getUsername());
        existing.setEmail(user.getEmail());
        return userRepository.save(existing);
    }
    
    public void delete(Long id) {
        userRepository.deleteById(id);
    }
}

查询方法

1. 方法命名约定

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    // 等于
    User findByUsername(String username);
    
    // 不等于
    User findByUsernameNot(String username);
    
    // 大于
    List<User> findByAgeGreaterThan(int age);
    
    // 小于
    List<User> findByAgeLessThan(int age);
    
    // 包含
    List<User> findByUsernameContaining(String keyword);
    
    // 以...开头
    List<User> findByUsernameStartingWith(String prefix);
    
    // 以...结尾
    List<User> findByUsernameEndingWith(String suffix);
    
    // IN 查询
    List<User> findByUsernameIn(List<String> usernames);
    
    // 是否为空
    List<User> findByEmailIsNull();
    
    List<User> findByEmailIsNotNull();
    
    // 布尔值
    List<User> findByEnabledTrue();
    
    // 排序
    List<User> findByAgeOrderByAgeDesc();
    
    // 分页
    Page<User> findByAge(int age, Pageable pageable);
}

2. @Query 自定义查询

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    /**
     * JPQL 查询
     */
    @Query("SELECT u FROM User u WHERE u.username = :username")
    User findByUsernameJPQL(@Param("username") String username);
    
    /**
     * 原生 SQL 查询
     */
    @Query(value = "SELECT * FROM user WHERE username = :username", nativeQuery = true)
    User findByUsernameNative(@Param("username") String username);
    
    /**
     * 更新操作
     */
    @Modifying
    @Transactional
    @Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
    int updateEmail(@Param("id") Long id, @Param("email") String email);
    
    /**
     * 删除操作
     */
    @Modifying
    @Transactional
    @Query("DELETE FROM User u WHERE u.age < :age")
    int deleteByAgeLessThan(@Param("age") int age);
}

3. 复杂查询

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    /**
     * 多条件查询
     */
    List<User> findByUsernameAndAge(String username, int age);
    
    List<User> findByUsernameOrEmail(String username, String email);
    
    /**
     * 组合条件
     */
    List<User> findByAgeGreaterThanAndUsernameContaining(int age, String keyword);
    
    /**
     * 子查询
     */
    @Query("SELECT u FROM User u WHERE u.age > (SELECT AVG(u2.age) FROM User u2)")
    List<User> findAboveAverageAge();
}

分页排序

1. 分页查询

@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    
    public Page<User> pageQuery(int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findAll(pageable);
    }
    
    public Page<User> pageQueryCondition(String username, int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findByUsernameContaining(username, pageable);
    }
}

2. 排序

@Service
@RequiredArgsConstructor
public class UserService {
    
    /**
     * 简单排序
     */
    public List<User> sortByAge() {
        return userRepository.findAll(Sort.by(Sort.Direction.ASC, "age"));
    }
    
    /**
     * 多字段排序
     */
    public List<User> sortByMultiple() {
        return userRepository.findAll(
            Sort.by(Sort.Direction.DESC, "createTime")
                .and(Sort.by(Sort.Direction.ASC, "age"))
        );
    }
    
    /**
     * 分页 + 排序
     */
    public Page<User> pageWithSort(int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createTime").descending());
        return userRepository.findAll(pageable);
    }
}

关联映射

1. 一对多

// 用户(一方)
@Data
@Entity
@Table(name = "user")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();
}

// 订单(多方)
@Data
@Entity
@Table(name = "orders")
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String orderNo;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
}

2. 多对多

// 用户
@Data
@Entity
@Table(name = "user")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    @ManyToMany
    @JoinTable(
        name = "user_role",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private List<Role> roles = new ArrayList<>();
}

// 角色
@Data
@Entity
@Table(name = "role")
public class Role {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @ManyToMany(mappedBy = "roles")
    private List<User> users = new ArrayList<>();
}

3. 一对一

// 用户
@Data
@Entity
@Table(name = "user")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
    private UserProfile profile;
}

// 用户详情
@Data
@Entity
@Table(name = "user_profile")
public class UserProfile {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String phone;
    
    private String address;
    
    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}

审计功能

1. 启用审计

@Configuration
@EnableJpaAuditing
public class JpaConfig {
}

2. 实体类使用

@Data
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "user")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createTime;
    
    @LastModifiedDate
    private LocalDateTime updateTime;
    
    @CreatedBy
    private String createBy;
    
    @LastModifiedBy
    private String updateBy;
}

3. 配置审计提供者

@Configuration
@EnableJpaAuditing
public class JpaConfig implements AuditorAware<String> {
    
    @Override
    public Optional<String> getCurrentAuditor() {
        // 从安全上下文获取当前用户
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.empty();
        }
        return Optional.of(authentication.getName());
    }
}

事务管理

1. 声明式事务

@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    
    /**
     * 默认事务
     */
    @Transactional
    public User create(User user) {
        return userRepository.save(user);
    }
    
    /**
     * 只读事务
     */
    @Transactional(readOnly = true)
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    /**
     * 指定隔离级别
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // 转账逻辑
    }
    
    /**
     * 指定超时时间
     */
    @Transactional(timeout = 30)
    public void batchInsert(List<User> users) {
        for (User user : users) {
            userRepository.save(user);
        }
    }
}

2. 事务传播

@Service
public class OrderService {
    
    @Autowired
    private UserService userService;
    
    /**
     * REQUIRED - 默认,加入现有事务或创建新事务
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder() {
        // 创建订单
        userService.updateUser(); // 加入同一事务
    }
    
    /**
     * REQUIRES_NEW - 总是创建新事务
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOrder() {
        // 记录日志,独立事务
    }
}

最佳实践

1. 使用 DTO

// ✅ 推荐
@Service
public class UserService {
    
    public UserDTO findById(Long id) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> BusinessException.notFound("用户"));
        return convertToDTO(user);
    }
    
    private UserDTO convertToDTO(User user) {
        return new UserDTO(user.getId(), user.getUsername(), user.getEmail());
    }
}

// ❌ 不推荐
@Service
public class UserService {
    
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

2. 懒加载处理

// ✅ 推荐 - 使用@JsonIgnore
@Data
@Entity
public class User {
    
    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    @JsonIgnore
    private List<Order> orders;
}

// ✅ 推荐 - 使用 DTO
@Service
public class UserService {
    
    public UserDTO findById(Long id) {
        User user = userRepository.findById(id).orElse(null);
        return new UserDTO(user.getId(), user.getUsername());
    }
}

3. N+1 问题

// ❌ 不推荐 - N+1 问题
List<User> users = userRepository.findAll();
for (User user : users) {
    user.getOrders().size(); // 每次访问都会查询数据库
}

// ✅ 推荐 - JOIN FETCH
@Query("SELECT u FROM User u JOIN FETCH u.orders")
List<User> findAllWithOrders();

总结

Spring Data JPA 要点:

Spring Data JPA 让数据访问更加优雅简洁。


分享这篇文章到:

上一篇文章
MySQL 子查询与临时表优化
下一篇文章
微服务设计原则