Skip to content

Spring Data 集成

本文档介绍如何在 LCGYL Framework 中集成 Spring Data,实现数据访问层的开发。

快速开始

添加依赖

gradle
dependencies {
    implementation 'com.lcgyl:lcgyl-spring-data:${version}'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
xml
<dependencies>
    <dependency>
        <groupId>com.lcgyl</groupId>
        <artifactId>lcgyl-spring-data</artifactId>
        <version>${version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
</dependencies>

配置数据源

yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: secret
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

JPA 集成

实体定义

java
@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 50)
    private String name;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    @Enumerated(EnumType.STRING)
    private UserStatus status;
    
    @CreatedDate
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    private LocalDateTime updatedAt;
    
    // Getters and Setters
}

Repository 接口

java
public interface UserRepository extends JpaRepository<User, Long> {
    
    // 方法名查询
    List<User> findByName(String name);
    
    Optional<User> findByEmail(String email);
    
    List<User> findByStatus(UserStatus status);
    
    List<User> findByNameContaining(String keyword);
    
    // 分页查询
    Page<User> findByStatus(UserStatus status, Pageable pageable);
    
    // 排序查询
    List<User> findByStatusOrderByCreatedAtDesc(UserStatus status);
    
    // 计数
    long countByStatus(UserStatus status);
    
    // 存在性检查
    boolean existsByEmail(String email);
    
    // 删除
    void deleteByStatus(UserStatus status);
}

JPQL 查询

java
public interface UserRepository extends JpaRepository<User, Long> {
    
    @Query("SELECT u FROM User u WHERE u.name LIKE %:keyword% OR u.email LIKE %:keyword%")
    List<User> search(@Param("keyword") String keyword);
    
    @Query("SELECT u FROM User u WHERE u.createdAt BETWEEN :start AND :end")
    List<User> findByCreatedAtBetween(
        @Param("start") LocalDateTime start,
        @Param("end") LocalDateTime end
    );
    
    @Query("SELECT COUNT(u) FROM User u WHERE u.status = :status")
    long countByStatusQuery(@Param("status") UserStatus status);
    
    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
    int updateStatus(@Param("id") Long id, @Param("status") UserStatus status);
    
    @Modifying
    @Query("DELETE FROM User u WHERE u.status = :status")
    int deleteByStatusQuery(@Param("status") UserStatus status);
}

原生 SQL 查询

java
public interface UserRepository extends JpaRepository<User, Long> {
    
    @Query(value = "SELECT * FROM users WHERE name = ?1", nativeQuery = true)
    List<User> findByNameNative(String name);
    
    @Query(value = "SELECT * FROM users WHERE status = :status LIMIT :limit", 
           nativeQuery = true)
    List<User> findTopByStatus(
        @Param("status") String status, 
        @Param("limit") int limit
    );
}

动态查询

Specification

java
public class UserSpecifications {
    
    public static Specification<User> hasName(String name) {
        return (root, query, cb) -> 
            name == null ? null : cb.equal(root.get("name"), name);
    }
    
    public static Specification<User> hasStatus(UserStatus status) {
        return (root, query, cb) -> 
            status == null ? null : cb.equal(root.get("status"), status);
    }
    
    public static Specification<User> nameLike(String keyword) {
        return (root, query, cb) -> 
            keyword == null ? null : cb.like(root.get("name"), "%" + keyword + "%");
    }
    
    public static Specification<User> createdAfter(LocalDateTime date) {
        return (root, query, cb) -> 
            date == null ? null : cb.greaterThan(root.get("createdAt"), date);
    }
}

// 使用
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public List<User> search(UserSearchCriteria criteria) {
        Specification<User> spec = Specification
            .where(UserSpecifications.hasName(criteria.getName()))
            .and(UserSpecifications.hasStatus(criteria.getStatus()))
            .and(UserSpecifications.nameLike(criteria.getKeyword()))
            .and(UserSpecifications.createdAfter(criteria.getCreatedAfter()));
        
        return userRepository.findAll(spec);
    }
}

QueryDSL

java
// 添加依赖
// implementation 'com.querydsl:querydsl-jpa:5.0.0'

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

// 使用
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public List<User> search(UserSearchCriteria criteria) {
        QUser user = QUser.user;
        
        BooleanBuilder builder = new BooleanBuilder();
        
        if (criteria.getName() != null) {
            builder.and(user.name.eq(criteria.getName()));
        }
        if (criteria.getStatus() != null) {
            builder.and(user.status.eq(criteria.getStatus()));
        }
        if (criteria.getKeyword() != null) {
            builder.and(user.name.contains(criteria.getKeyword())
                .or(user.email.contains(criteria.getKeyword())));
        }
        
        return (List<User>) userRepository.findAll(builder);
    }
}

分页和排序

分页查询

java
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public Page<User> findAll(int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findAll(pageable);
    }
    
    public Page<User> findByStatus(UserStatus status, int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        return userRepository.findByStatus(status, pageable);
    }
}

排序

java
@Service
public class UserService {
    
    public List<User> findAllSorted() {
        // 单字段排序
        Sort sort = Sort.by("name").ascending();
        
        // 多字段排序
        Sort multiSort = Sort.by(
            Sort.Order.desc("status"),
            Sort.Order.asc("name")
        );
        
        return userRepository.findAll(multiSort);
    }
}

Slice 查询

java
public interface UserRepository extends JpaRepository<User, Long> {
    
    Slice<User> findByStatus(UserStatus status, Pageable pageable);
}

// Slice 不查询总数,性能更好
@Service
public class UserService {
    
    public Slice<User> findByStatus(UserStatus status, int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findByStatus(status, pageable);
    }
}

审计

启用审计

java
@Configuration
@EnableJpaAuditing
public class JpaConfig {
    
    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> Optional.ofNullable(SecurityContext.getCurrentUser())
            .map(User::getUsername);
    }
}

审计实体

java
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    private LocalDateTime updatedAt;
    
    @CreatedBy
    @Column(updatable = false)
    private String createdBy;
    
    @LastModifiedBy
    private String updatedBy;
    
    // Getters and Setters
}

@Entity
public class User extends BaseEntity {
    private String name;
    private String email;
    // ...
}

事务管理

声明式事务

java
@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public Order createOrder(Order order) {
        // 验证用户
        User user = userRepository.findById(order.getUserId())
            .orElseThrow(() -> new UserNotFoundException(order.getUserId()));
        
        // 保存订单
        order.setStatus(OrderStatus.PENDING);
        return orderRepository.save(order);
    }
    
    @Transactional(readOnly = true)
    public Order findById(Long id) {
        return orderRepository.findById(id)
            .orElseThrow(() -> new OrderNotFoundException(id));
    }
    
    @Transactional(rollbackFor = BusinessException.class)
    public void processOrder(Long orderId) {
        // 业务异常会触发回滚
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void auditLog(String action) {
        // 在新事务中执行
    }
}

编程式事务

java
@Service
public class OrderService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public Order createOrder(Order order) {
        return transactionTemplate.execute(status -> {
            try {
                return orderRepository.save(order);
            } catch (Exception e) {
                status.setRollbackOnly();
                throw e;
            }
        });
    }
}

多数据源

配置多数据源

yaml
spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/primary
      username: root
      password: secret
    secondary:
      url: jdbc:mysql://localhost:3306/secondary
      username: root
      password: secret

数据源配置类

java
@Configuration
public class DataSourceConfig {
    
    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties("spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}

@Configuration
@EnableJpaRepositories(
    basePackages = "com.example.repository.primary",
    entityManagerFactoryRef = "primaryEntityManagerFactory",
    transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryJpaConfig {
    
    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
            @Qualifier("primaryDataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.example.entity.primary");
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        return em;
    }
    
    @Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager(
            @Qualifier("primaryEntityManagerFactory") EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}

缓存集成

启用缓存

java
@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users", "orders");
    }
}

使用缓存

java
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Cacheable(value = "users", key = "#id")
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    @CachePut(value = "users", key = "#user.id")
    public User save(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(value = "users", key = "#id")
    public void delete(Long id) {
        userRepository.deleteById(id);
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void clearCache() {
    }
}

最佳实践

1. 使用投影

java
// 投影接口
public interface UserSummary {
    Long getId();
    String getName();
    String getEmail();
}

public interface UserRepository extends JpaRepository<User, Long> {
    List<UserSummary> findByStatus(UserStatus status);
}

2. 批量操作

java
@Service
public class UserService {
    
    @Transactional
    public void batchSave(List<User> users) {
        int batchSize = 50;
        for (int i = 0; i < users.size(); i++) {
            userRepository.save(users.get(i));
            if (i % batchSize == 0) {
                entityManager.flush();
                entityManager.clear();
            }
        }
    }
}

3. N+1 问题

java
// 使用 JOIN FETCH 解决 N+1 问题
public interface OrderRepository extends JpaRepository<Order, Long> {
    
    @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.userId = :userId")
    List<Order> findByUserIdWithItems(@Param("userId") Long userId);
}

4. 只读事务

java
@Transactional(readOnly = true)
public List<User> findAll() {
    return userRepository.findAll();
}

常见问题

Q: 如何处理懒加载异常?

A:

  1. 使用 @Transactional 保持会话
  2. 使用 JOIN FETCH 预加载
  3. 使用 DTO 投影

Q: 如何优化大数据量查询?

A:

  1. 使用分页
  2. 使用 Slice 代替 Page
  3. 使用流式查询
java
@Query("SELECT u FROM User u")
Stream<User> findAllAsStream();

Q: 如何处理乐观锁?

A: 使用 @Version 注解:

java
@Entity
public class User {
    @Version
    private Long version;
}

下一步

Released under the Apache License 2.0