Spring Data实战:数据访问层开发指南

Spring Data为数据访问提供了统一的编程模型,本文将详细介绍Spring Data的核心特性和最佳实践。

基础配置

Maven依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.1.0.RELEASE</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
</dependency>

数据源配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        format_sql: true

实体映射

基本映射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String username;
    
    @Column(nullable = false)
    private String email;
    
    @Column(name = "created_at")
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    @Version
    private Integer version;
}

关系映射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Entity
@Table(name = "orders")
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();
    
    public void addItem(OrderItem item) {
        items.add(item);
        item.setOrder(this);
    }
    
    public void removeItem(OrderItem item) {
        items.remove(item);
        item.setOrder(null);
    }
}

数据访问

Repository接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public interface UserRepository extends JpaRepository<User, Long> {
    
    Optional<User> findByUsername(String username);
    
    List<User> findByEmailEndingWith(String domain);
    
    @Query("SELECT u FROM User u WHERE u.createdAt > :date")
    List<User> findRecentUsers(@Param("date") LocalDateTime date);
    
    @Modifying
    @Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
    int updateEmail(@Param("id") Long id, @Param("email") String email);
}

分页和排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public Page<User> findUsers(int page, int size) {
        PageRequest pageRequest = PageRequest.of(page, size, 
            Sort.by("createdAt").descending());
        
        return userRepository.findAll(pageRequest);
    }
    
    public List<User> findUsersSorted() {
        Sort sort = Sort.by("username").ascending()
                       .and(Sort.by("email").descending());
                       
        return userRepository.findAll(sort);
    }
}

查询方法

命名查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public interface OrderRepository extends JpaRepository<Order, Long> {
    
    // 自动生成查询
    List<Order> findByUserAndStatus(User user, OrderStatus status);
    
    // 使用JPQL
    @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.user.id = :userId")
    List<Order> findByUserWithItems(@Param("userId") Long userId);
    
    // 使用原生SQL
    @Query(value = "SELECT * FROM orders WHERE total_amount > ?1", nativeQuery = true)
    List<Order> findExpensiveOrders(BigDecimal amount);
}

Specification查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Service
public class UserService {
    
    public List<User> findUsers(String username, String email) {
        return userRepository.findAll((root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            
            if (username != null) {
                predicates.add(cb.like(root.get("username"), "%" + username + "%"));
            }
            
            if (email != null) {
                predicates.add(cb.equal(root.get("email"), email));
            }
            
            return cb.and(predicates.toArray(new Predicate[0]));
        });
    }
}

事务管理

事务配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Service
@Transactional
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Transactional(readOnly = true)
    public Order findOrder(Long id) {
        return orderRepository.findById(id)
            .orElseThrow(() -> new OrderNotFoundException(id));
    }
    
    @Transactional(rollbackFor = OrderException.class)
    public Order createOrder(Order order) {
        validateOrder(order);
        return orderRepository.save(order);
    }
}

审计功能

审计配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Configuration
@EnableJpaAuditing
public class JpaConfig {
    
    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> Optional.ofNullable(SecurityContextHolder.getContext())
            .map(SecurityContext::getAuthentication)
            .map(Authentication::getName);
    }
}

审计实体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Data
public abstract class Auditable {
    
    @CreatedBy
    protected String createdBy;
    
    @CreatedDate
    protected LocalDateTime createdDate;
    
    @LastModifiedBy
    protected String lastModifiedBy;
    
    @LastModifiedDate
    protected LocalDateTime lastModifiedDate;
}

@Entity
public class Product extends Auditable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private BigDecimal price;
}

缓存支持

缓存配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
            new ConcurrentMapCache("users"),
            new ConcurrentMapCache("orders")
        ));
        return cacheManager;
    }
}

缓存使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#id")
    public User findUser(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
    
    @CacheEvict(value = "users", key = "#user.id")
    public void updateUser(User user) {
        userRepository.save(user);
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void clearCache() {
        // 清除缓存
    }
}

最佳实践

  1. 领域模型设计
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Entity
public class Order {
    // 使用充血模型
    public void addItem(Product product, int quantity) {
        OrderItem item = new OrderItem(this, product, quantity);
        items.add(item);
        recalculateTotal();
    }
    
    private void recalculateTotal() {
        this.total = items.stream()
            .map(OrderItem::getSubtotal)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}
  1. 性能优化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    // 使用投影
    interface OrderSummary {
        Long getId();
        BigDecimal getTotal();
        String getStatus();
    }
    
    // 避免N+1问题
    @EntityGraph(attributePaths = {"items", "user"})
    List<Order> findAllWithItemsAndUser();
}

总结

Spring Data提供了强大的数据访问功能,通过合理使用其特性,可以大大提高开发效率。

参考资料

  • Spring Data JPA参考文档
  • Java Persistence with Hibernate
  • Spring实战(第5版)
使用绝夜之城强力驱动