Skip to content

Spring Boot 数据访问

1. 数据访问概述

1.1 Spring Data 简介

Spring Data 提供了一致的数据访问模型,支持多种数据存储:

  • Spring Data JPA:关系型数据库
  • Spring Data MongoDB:MongoDB
  • Spring Data Redis:Redis
  • Spring Data Elasticsearch:Elasticsearch

1.2 数据源配置

yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5
      idle-timeout: 300000
      connection-timeout: 20000
      pool-name: MyHikariCP

2. Spring Data JPA

2.1 添加依赖

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

2.2 实体类

java
@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true, length = 50)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    @Column(length = 100)
    private String email;
    
    @Enumerated(EnumType.STRING)
    private UserStatus status = UserStatus.ACTIVE;
    
    @CreatedDate
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Order> orders = new ArrayList<>();
    
    // getters and setters
}

2.3 Repository 接口

java
public interface UserRepository extends JpaRepository<User, Long>, 
                                          JpaSpecificationExecutor<User> {
    
    Optional<User> findByUsername(String username);
    
    Optional<User> findByEmail(String email);
    
    List<User> findByStatus(UserStatus status);
    
    List<User> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
    
    @Query("SELECT u FROM User u WHERE u.email LIKE %:domain")
    List<User> findByEmailDomain(@Param("domain") String domain);
    
    @Query(value = "SELECT * FROM users WHERE status = :status", nativeQuery = true)
    List<User> findByStatusNative(@Param("status") String status);
    
    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
    int updateStatus(@Param("id") Long id, @Param("status") UserStatus status);
    
    boolean existsByUsername(String username);
    
    boolean existsByEmail(String email);
    
    long countByStatus(UserStatus status);
    
    void deleteByUsername(String username);
}

2.4 分页查询

java
@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    public Page<User> findByPage(int page, int size, String sortBy) {
        Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy).ascending());
        return userRepository.findAll(pageable);
    }
    
    public Page<User> search(String keyword, int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        Specification<User> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            
            if (keyword != null && !keyword.isEmpty()) {
                predicates.add(cb.or(
                    cb.like(root.get("username"), "%" + keyword + "%"),
                    cb.like(root.get("email"), "%" + keyword + "%")
                ));
            }
            
            return cb.and(predicates.toArray(new Predicate[0]));
        };
        
        return userRepository.findAll(spec, pageable);
    }
}

2.5 审计功能

java
@Configuration
@EnableJpaAuditing
public class JpaConfig {
    
    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication == null || !authentication.isAuthenticated()) {
                return Optional.empty();
            }
            return Optional.of(authentication.getName());
        };
    }
}

@Entity
@EntityListeners(AuditingEntityListener.class)
public class User {
    
    @CreatedBy
    @Column(name = "created_by", updatable = false)
    private String createdBy;
    
    @LastModifiedBy
    @Column(name = "updated_by")
    private String updatedBy;
    
    // ...
}

3. Spring Data MongoDB

3.1 添加依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

3.2 配置连接

yaml
spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/mydb
      # 或分开配置
      host: localhost
      port: 27017
      database: mydb
      username: root
      password: password

3.3 文档实体

java
@Document(collection = "users")
public class User {
    
    @Id
    private String id;
    
    @Indexed(unique = true)
    private String username;
    
    private String email;
    
    private List<String> roles = new ArrayList<>();
    
    private Address address;
    
    @CreatedDate
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    private LocalDateTime updatedAt;
    
    // getters and setters
}

@Document(collection = "products")
public class Product {
    
    @Id
    private String id;
    
    private String name;
    
    private BigDecimal price;
    
    private String category;
    
    @TextIndexed
    private String description;
    
    // getters and setters
}

3.4 Repository 接口

java
public interface UserRepository extends MongoRepository<User, String> {
    
    Optional<User> findByUsername(String username);
    
    List<User> findByRolesIn(List<String> roles);
    
    List<User> findByAddressCity(String city);
    
    @Query("{ 'price' : { $gt: ?0 } }")
    List<Product> findByPriceGreaterThan(BigDecimal price);
    
    @Aggregation(pipeline = {
        "{ $match: { 'category': ?0 } }",
        "{ $group: { _id: null, avgPrice: { $avg: '$price' } } }"
    })
    Double findAveragePriceByCategory(String category);
}

4. Spring Data Redis

4.1 添加依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4.2 配置 Redis

yaml
spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: 
      database: 0
      lettuce:
        pool:
          max-active: 8
          max-idle: 8
          min-idle: 0

4.3 Redis 实体

java
@RedisHash(value = "user", timeToLive = 3600)
public class User {
    
    @Id
    private Long id;
    
    @Indexed
    private String username;
    
    private String email;
    
    // getters and setters
}

4.4 Repository 接口

java
public interface UserRepository extends CrudRepository<User, Long> {
    
    Optional<User> findByUsername(String username);
    
    List<User> findByEmail(String email);
}

4.5 RedisTemplate

java
@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        
        return template;
    }
}

@Service
public class CacheService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }
    
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    
    public void delete(String key) {
        redisTemplate.delete(key);
    }
    
    public boolean hasKey(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }
}

5. 事务管理

5.1 声明式事务

java
@Service
@Transactional
public class UserService {
    
    private final UserRepository userRepository;
    private final OrderRepository orderRepository;
    
    @Transactional(readOnly = true)
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    @Transactional(rollbackFor = Exception.class)
    public User create(User user) {
        return userRepository.save(user);
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void deleteUserWithOrders(Long userId) {
        orderRepository.deleteByUserId(userId);
        userRepository.deleteById(userId);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOperation(String operation) {
        // 新事务中记录日志
    }
}

5.2 事务传播行为

传播行为说明
REQUIRED有事务则加入,无则新建(默认)
REQUIRES_NEW总是新建事务
SUPPORTS有事务则加入,无则非事务运行
NOT_SUPPORTED非事务运行
MANDATORY必须在事务中运行
NEVER不能在事务中运行
NESTED嵌套事务

5.3 事务隔离级别

隔离级别说明
DEFAULT使用数据库默认隔离级别
READ_UNCOMMITTED读未提交
READ_COMMITTED读已提交
REPEATABLE_READ可重复读
SERIALIZABLE串行化

6. 多数据源

6.1 配置多数据源

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

6.2 主数据源配置

java
@Configuration
@EnableJpaRepositories(
    basePackages = "com.example.repository.primary",
    entityManagerFactoryRef = "primaryEntityManagerFactory",
    transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryDataSourceConfig {
    
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            DataSource primaryDataSource) {
        return builder
                .dataSource(primaryDataSource)
                .packages("com.example.entity.primary")
                .persistenceUnit("primary")
                .build();
    }
    
    @Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager(
            LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory) {
        return new JpaTransactionManager(
            primaryEntityManagerFactory.getObject()
        );
    }
}

6.3 从数据源配置

java
@Configuration
@EnableJpaRepositories(
    basePackages = "com.example.repository.secondary",
    entityManagerFactoryRef = "secondaryEntityManagerFactory",
    transactionManagerRef = "secondaryTransactionManager"
)
public class SecondaryDataSourceConfig {
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            DataSource secondaryDataSource) {
        return builder
                .dataSource(secondaryDataSource)
                .packages("com.example.entity.secondary")
                .persistenceUnit("secondary")
                .build();
    }
    
    @Bean
    public PlatformTransactionManager secondaryTransactionManager(
            LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory) {
        return new JpaTransactionManager(
            secondaryEntityManagerFactory.getObject()
        );
    }
}

7. 数据库迁移

7.1 Flyway

xml
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
</dependency>
yaml
spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: true
    validate-on-migrate: true
# db/migration/V1__Create_users_table.sql
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

# db/migration/V2__Create_orders_table.sql
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    order_no VARCHAR(50) NOT NULL,
    amount DECIMAL(10, 2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

7.2 Liquibase

xml
<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>
yaml
spring:
  liquibase:
    enabled: true
    change-log: classpath:db/changelog/db.changelog-master.yaml
yaml
# db/changelog/db.changelog-master.yaml
databaseChangeLog:
  - include:
      file: db/changelog/changes/001-create-users-table.yaml
  - include:
      file: db/changelog/changes/002-create-orders-table.yaml
yaml
# db/changelog/changes/001-create-users-table.yaml
databaseChangeLog:
  - changeSet:
      id: 1
      author: developer
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: BIGINT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
              - column:
                  name: username
                  type: VARCHAR(50)
                  constraints:
                    nullable: false
                    unique: true

8. 小结

本章学习了 Spring Boot 数据访问的核心内容:

内容要点
Spring Data JPA实体类、Repository、分页查询
Spring Data MongoDB文档实体、Repository
Spring Data RedisRedisTemplate、缓存实体
事务管理@Transactional、传播行为、隔离级别
多数据源配置类、EntityManagerFactory
数据库迁移Flyway、Liquibase

下一章将学习 Spring Boot 缓存机制。