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: trueJPA 集成
实体定义
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:
- 使用
@Transactional保持会话 - 使用
JOIN FETCH预加载 - 使用 DTO 投影
Q: 如何优化大数据量查询?
A:
- 使用分页
- 使用 Slice 代替 Page
- 使用流式查询
java
@Query("SELECT u FROM User u")
Stream<User> findAllAsStream();Q: 如何处理乐观锁?
A: 使用 @Version 注解:
java
@Entity
public class User {
@Version
private Long version;
}下一步
- Spring Security 集成 - 安全认证
- Spring Cloud 集成 - 微服务
- 数据访问 - 更多数据访问