Spring Boot整合MySQL:从基础到高级实践

一、Spring Boot与MySQL概述

1.1 为什么选择Spring Boot整合MySQL

Spring Boot与MySQL的结合是现代Java Web开发的黄金组合,它们共同提供了:

特性Spring Boot优势MySQL优势结合后的好处
开发效率自动配置、起步依赖成熟的SQL支持快速构建数据驱动应用
性能嵌入式容器优化高性能关系型数据库整体高性能解决方案
可扩展性微服务友好集群支持易于水平扩展的系统
社区支持强大的Spring生态最流行的开源数据库丰富的学习资源和解决方案

1.2 核心概念解析

JPA (Java Persistence API): Java持久化API,定义了一套对象关系映射(ORM)的标准规范。

Hibernate: JPA的一种实现,提供了数据查询和检索服务。

Spring Data JPA: Spring对JPA的封装,进一步简化了数据库操作。

JDBC (Java Database Connectivity): Java数据库连接,最基础的数据库操作方式。

MyBatis: 半自动ORM框架,需要手动编写SQL但提供对象映射。

二、环境准备与基础配置

2.1 项目初始化

使用Spring Initializr创建项目,选择以下依赖:

  • Spring Web
  • Spring Data JPA
  • MySQL Driver

或者通过Maven添加依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

2.2 数据库配置

application.properties配置示例:

# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456

# JPA配置
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

配置项解析:

配置项说明常用值
spring.datasource.url数据库连接URLjdbc:mysql://主机:端口/数据库名?参数
spring.jpa.show-sql是否显示执行的SQLtrue/false
spring.jpa.hibernate.ddl-auto数据库初始化策略none/update/create/create-drop/validate
hibernate.dialect数据库方言根据MySQL版本选择

三、基础CRUD操作

3.1 实体类映射

以"用户"实体为例:

@Entity
@Table(name = "t_user")  // 指定表名
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username", nullable = false, length = 50)
    private String username;
    
    @Column(name = "password", nullable = false)
    private String password;
    
    @Column(name = "email")
    private String email;
    
    @Column(name = "create_time")
    private LocalDateTime createTime;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "user_type")
    private UserType userType;
    
    // 省略构造方法、getter和setter
}

public enum UserType {
    NORMAL, VIP, ADMIN
}

常用注解解析:

注解作用常用属性
@Entity声明为JPA实体-
@Table指定映射的表name(表名)
@Id标识主键-
@GeneratedValue主键生成策略strategy
@Column字段映射name, nullable, length等
@Enumerated枚举类型映射EnumType.STRING/ORDINAL

3.2 创建Repository接口

public interface UserRepository extends JpaRepository<User, Long> {
    
    // 按用户名查询
    User findByUsername(String username);
    
    // 按邮箱模糊查询
    List<User> findByEmailContaining(String email);
    
    // 按用户类型查询并按创建时间降序
    List<User> findByUserTypeOrderByCreateTimeDesc(UserType userType);
    
    // 使用@Query自定义JPQL查询
    @Query("SELECT u FROM User u WHERE u.createTime BETWEEN ?1 AND ?2")
    List<User> findUsersByCreateTimeBetween(LocalDateTime start, LocalDateTime end);
    
    // 使用原生SQL查询
    @Query(value = "SELECT * FROM t_user WHERE username LIKE %?1%", nativeQuery = true)
    List<User> findUsersByUsernameLike(String keyword);
}

方法命名规则解析:

关键字示例生成的SQL条件
AndfindByUsernameAndPasswordwhere username=? and password=?
OrfindByUsernameOrEmailwhere username=? or email=?
Is,EqualsfindByIdIswhere id=?
BetweenfindByCreateTimeBetweenwhere createTime between ? and ?
LessThanfindByAgeLessThanwhere age < ?
LikefindByUsernameLikewhere username like ?
OrderByfindByUserTypeOrderByCreateTimeDescwhere userType=? order by createTime desc

3.3 服务层实现

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 保存用户
     * @param user 用户实体
     * @return 保存后的用户
     */
    public User saveUser(User user) {
        // 设置创建时间
        if (user.getCreateTime() == null) {
            user.setCreateTime(LocalDateTime.now());
        }
        return userRepository.save(user);
    }
    
    /**
     * 根据ID查询用户
     * @param id 用户ID
     * @return 用户实体
     * @throws ResourceNotFoundException 如果用户不存在
     */
    public User getUserById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
    }
    
    /**
     * 查询所有用户
     * @return 用户列表
     */
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    /**
     * 删除用户
     * @param id 用户ID
     */
    public void deleteUser(Long id) {
        User user = getUserById(id);
        userRepository.delete(user);
    }
    
    /**
     * 更新用户信息
     * @param id 用户ID
     * @param userDetails 更新内容
     * @return 更新后的用户
     */
    public User updateUser(Long id, User userDetails) {
        User user = getUserById(id);
        user.setUsername(userDetails.getUsername());
        user.setEmail(userDetails.getEmail());
        // 其他字段更新...
        return userRepository.save(user);
    }
}

3.4 控制器层

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = userService.saveUser(user);
        return ResponseEntity.created(URI.create("/api/users/" + savedUser.getId()))
                .body(savedUser);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }
    
    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
        User updatedUser = userService.updateUser(id, userDetails);
        return ResponseEntity.ok(updatedUser);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

四、高级特性与进阶使用

4.1 关联关系映射

4.1.1 一对一关系

以用户和用户详情为例:

@Entity
@Table(name = "t_user")
public class User {
    // ...其他字段
    
    @OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
    private UserProfile userProfile;
}

@Entity
@Table(name = "t_user_profile")
public class UserProfile {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String realName;
    private Integer age;
    private String address;
    
    @OneToOne
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    private User user;
}
4.1.2 一对多关系

以用户和订单为例:

@Entity
@Table(name = "t_user")
public class User {
    // ...其他字段
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();
}

@Entity
@Table(name = "t_order")
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String orderNo;
    private BigDecimal amount;
    private LocalDateTime createTime;
    
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
4.1.3 多对多关系

以用户和角色为例:

@Entity
@Table(name = "t_user")
public class User {
    // ...其他字段
    
    @ManyToMany
    @JoinTable(name = "t_user_role",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new HashSet<>();
}

@Entity
@Table(name = "t_role")
public class Role {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();
}

关联关系对比表:

关系类型注解适用场景示例
一对一@OneToOne主表-从表关系用户-用户详情
一对多@OneToMany一对多关系用户-订单
多对一@ManyToOne多对一关系订单-用户
多对多@ManyToMany多对多关系用户-角色

4.2 事务管理

Spring提供了声明式事务管理:

@Service
public class OrderService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Transactional
    public Order createOrder(Long userId, Order order) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found"));
        
        order.setUser(user);
        order.setCreateTime(LocalDateTime.now());
        
        // 扣减用户余额
        user.setBalance(user.getBalance().subtract(order.getAmount()));
        userRepository.save(user);
        
        // 保存订单
        return orderRepository.save(order);
    }
    
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, 
            timeout = 30, rollbackFor = Exception.class)
    public void batchCreateOrders(Long userId, List<Order> orders) {
        for (Order order : orders) {
            createOrder(userId, order);
        }
    }
}

事务传播行为:

传播行为说明适用场景
REQUIRED支持当前事务,如果没有则新建大多数业务方法
SUPPORTS支持当前事务,如果没有则以非事务执行查询方法
MANDATORY必须在事务中调用,否则抛出异常必须由事务调用的方法
REQUIRES_NEW新建事务,挂起当前事务需要独立事务的操作
NOT_SUPPORTED以非事务方式执行,挂起当前事务不涉及数据修改的操作
NEVER必须在非事务中执行,否则抛出异常不应该在事务中调用的方法
NESTED如果当前存在事务,则在嵌套事务内执行复杂业务中的子操作

4.3 分页与排序

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserRepository userRepository;
    
    @GetMapping("/search")
    public ResponseEntity<Page<User>> searchUsers(
            @RequestParam(required = false) String keyword,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(defaultValue = "createTime,desc") String[] sort) {
        
        // 解析排序参数
        List<Sort.Order> orders = new ArrayList<>();
        for (String sortParam : sort) {
            String[] _sort = sortParam.split(",");
            orders.add(new Sort.Order(Sort.Direction.fromString(_sort[1]), _sort[0]));
        }
        
        // 创建分页请求
        Pageable pageable = PageRequest.of(page, size, Sort.by(orders));
        
        // 执行查询
        Page<User> userPage;
        if (keyword != null && !keyword.isEmpty()) {
            userPage = userRepository.findByUsernameContainingOrEmailContaining(keyword, keyword, pageable);
        } else {
            userPage = userRepository.findAll(pageable);
        }
        
        return ResponseEntity.ok(userPage);
    }
}

分页返回结果示例:

{
    "content": [
        {
            "id": 1,
            "username": "user1",
            "email": "user1@example.com"
        },
        {
            "id": 2,
            "username": "user2",
            "email": "user2@example.com"
        }
    ],
    "pageable": {
        "sort": {
            "sorted": true,
            "unsorted": false,
            "empty": false
        },
        "offset": 0,
        "pageSize": 10,
        "pageNumber": 0,
        "unpaged": false,
        "paged": true
    },
    "last": false,
    "totalPages": 5,
    "totalElements": 42,
    "size": 10,
    "number": 0,
    "sort": {
        "sorted": true,
        "unsorted": false,
        "empty": false
    },
    "first": true,
    "numberOfElements": 10,
    "empty": false
}

4.4 自定义Repository实现

有时需要实现一些复杂的查询或操作,可以自定义Repository:

  1. 定义自定义接口:
public interface CustomUserRepository {
    List<User> findActiveUsers();
    int updateUserStatus(Long userId, boolean active);
}
  1. 实现自定义接口:
public class CustomUserRepositoryImpl implements CustomUserRepository {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Override
    public List<User> findActiveUsers() {
        String jpql = "SELECT u FROM User u WHERE u.active = true";
        return entityManager.createQuery(jpql, User.class).getResultList();
    }
    
    @Override
    public int updateUserStatus(Long userId, boolean active) {
        String jpql = "UPDATE User u SET u.active = :active WHERE u.id = :userId";
        return entityManager.createQuery(jpql)
                .setParameter("active", active)
                .setParameter("userId", userId)
                .executeUpdate();
    }
}
  1. 扩展主Repository接口:
public interface UserRepository extends JpaRepository<User, Long>, CustomUserRepository {
    // ...其他方法
}

五、性能优化与最佳实践

5.1 JPA性能优化

5.1.1 N+1查询问题解决方案

问题描述:当查询主实体并访问其关联实体时,会触发额外的查询。

解决方案:

  1. 使用JOIN FETCH:
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :userId")
User findUserWithOrders(@Param("userId") Long userId);
  1. 使用@EntityGraph:
@EntityGraph(attributePaths = {"orders"})
@Query("SELECT u FROM User u WHERE u.id = :userId")
User findUserWithOrders(@Param("userId") Long userId);
  1. 在实体上定义NamedEntityGraph:
@NamedEntityGraph(name = "User.withOrders", 
    attributeNodes = @NamedAttributeNode("orders"))
@Entity
public class User {
    // ...
}

然后在Repository中使用:

@EntityGraph(value = "User.withOrders")
@Query("SELECT u FROM User u WHERE u.id = :userId")
User findUserWithOrders(@Param("userId") Long userId);
5.1.2 批量操作优化
  1. 批量插入:
@Transactional
public void batchInsertUsers(List<User> users) {
    for (int i = 0; i < users.size(); i++) {
        entityManager.persist(users.get(i));
        if (i % 50 == 0) { // 每50条flush一次
            entityManager.flush();
            entityManager.clear();
        }
    }
}

配置批量大小:

spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
  1. 批量更新:
@Transactional
@Modifying
@Query("UPDATE User u SET u.active = ?2 WHERE u.id IN ?1")
int batchUpdateUserStatus(List<Long> userIds, boolean active);

5.2 缓存策略

5.2.1 二级缓存配置
  1. 添加依赖:
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
  1. 配置:
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
spring.jpa.properties.javax.persistence.sharedCache.mode=ENABLE_SELECTIVE
  1. 在实体上启用缓存:
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    // ...
}
5.2.2 查询缓存
spring.jpa.properties.hibernate.cache.use_query_cache=true

然后在查询方法上添加注解:

@QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true"))
@Query("SELECT u FROM User u WHERE u.active = true")
List<User> findAllActiveUsers();

5.3 连接池配置

推荐使用HikariCP连接池:

# 连接池配置
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

连接池参数说明:

参数说明推荐值
connection-timeout连接超时时间(ms)30000
maximum-pool-size最大连接数根据系统负载调整
minimum-idle最小空闲连接数10
idle-timeout空闲连接超时时间(ms)600000
max-lifetime连接最大存活时间(ms)1800000

六、安全与异常处理

6.1 SQL注入防护

  1. 始终使用参数化查询:
// 正确做法 - 使用参数
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsername(@Param("username") String username);

// 错误做法 - 拼接SQL
@Query("SELECT u FROM User u WHERE u.username = '" + "#{username}" + "'")
User findByUsernameIncorrect(@Param("username") String username);
  1. 使用JPA方法命名规则自动生成的查询也是安全的。

  2. 如果需要原生SQL,必须使用参数绑定:

@Query(value = "SELECT * FROM t_user WHERE username = ?1", nativeQuery = true)
User findByUsernameNative(String username);

6.2 异常处理

全局异常处理示例:

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.NOT_FOUND.value(),
            ex.getMessage(),
            System.currentTimeMillis());
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }
    
    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<ErrorResponse> handleDataIntegrityViolation(DataIntegrityViolationException ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.CONFLICT.value(),
            "数据完整性冲突,可能是重复数据或违反约束",
            System.currentTimeMillis());
        return new ResponseEntity<>(error, HttpStatus.CONFLICT);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            "服务器内部错误: " + ex.getMessage(),
            System.currentTimeMillis());
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

@Data
@AllArgsConstructor
class ErrorResponse {
    private int status;
    private String message;
    private long timestamp;
}

常见JPA异常:

异常原因建议处理方式
EntityNotFoundException实体未找到返回404状态码
DataIntegrityViolationException数据完整性冲突返回409状态码
JpaSystemExceptionJPA系统异常记录日志,返回500状态码
OptimisticLockingFailureException乐观锁冲突返回409状态码,提示重试
TransactionSystemException事务系统异常记录日志,返回500状态码

七、监控与调试

7.1 监控配置

  1. 添加Spring Boot Actuator依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 配置数据库监控端点:
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
  1. 自定义健康检查:
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
    
    @Autowired
    private DataSource dataSource;
    
    @Override
    public Health health() {
        try (Connection connection = dataSource.getConnection()) {
            if (connection.isValid(1000)) {
                return Health.up()
                        .withDetail("database", "MySQL")
                        .build();
            }
            return Health.down()
                    .withDetail("database", "MySQL connection invalid")
                    .build();
        } catch (Exception e) {
            return Health.down()
                    .withDetail("database", "MySQL unavailable: " + e.getMessage())
                    .build();
        }
    }
}

7.2 日志配置

配置SQL日志输出:

# 显示SQL语句
spring.jpa.show-sql=true
# 格式化SQL
spring.jpa.properties.hibernate.format_sql=true
# 显示SQL参数
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# 显示事务信息
logging.level.org.springframework.orm.jpa=DEBUG
logging.level.org.springframework.transaction=DEBUG

7.3 性能监控

使用Micrometer集成Prometheus:

  1. 添加依赖:
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
  1. 配置:
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.metrics.export.prometheus.enabled=true
  1. 自定义指标:
@Service
public class UserService {
    
    private final Counter userCreationCounter;
    private final Timer userQueryTimer;
    
    public UserService(MeterRegistry registry) {
        this.userCreationCounter = Counter.builder("user.creation.count")
                .description("Number of users created")
                .register(registry);
        
        this.userQueryTimer = Timer.builder("user.query.time")
                .description("Time taken to query users")
                .register(registry);
    }
    
    public User createUser(User user) {
        userCreationCounter.increment();
        // ...创建用户逻辑
    }
    
    public User getUserById(Long id) {
        return userQueryTimer.record(() -> {
            // ...查询用户逻辑
        });
    }
}

八、替代方案与对比

8.1 JPA vs MyBatis vs JDBC

特性Spring Data JPAMyBatisJDBC
学习曲线中等中等
开发效率
灵活性最高
性能最高
SQL控制自动生成完全控制完全控制
缓存支持完善有限
适合场景快速开发标准CRUD复杂SQL项目极致性能需求

8.2 多数据源配置

  1. 配置主数据源:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = "com.example.primary.repository",
        entityManagerFactoryRef = "primaryEntityManagerFactory",
        transactionManagerRef = "primaryTransactionManager")
public class PrimaryDataSourceConfig {
    
    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(primaryDataSource())
                .packages("com.example.primary.model")
                .persistenceUnit("primaryPersistenceUnit")
                .build();
    }
    
    @Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager(
            @Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}
  1. 配置次数据源:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = "com.example.secondary.repository",
        entityManagerFactoryRef = "secondaryEntityManagerFactory",
        transactionManagerRef = "secondaryTransactionManager")
public class SecondaryDataSourceConfig {
    
    @Bean
    @ConfigurationProperties("spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(secondaryDataSource())
                .packages("com.example.secondary.model")
                .persistenceUnit("secondaryPersistenceUnit")
                .build();
    }
    
    @Bean
    public PlatformTransactionManager secondaryTransactionManager(
            @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}
  1. 配置文件:
# 主数据源
spring.datasource.primary.url=jdbc:mysql://localhost:3306/primary_db
spring.datasource.primary.username=root
spring.datasource.primary.password=123456
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

# 次数据源
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/secondary_db
spring.datasource.secondary.username=root
spring.datasource.secondary.password=123456
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

九、实战案例:电商用户订单系统

9.1 需求分析

实现一个简单的电商系统,包含以下功能:

  • 用户注册、登录、管理
  • 商品管理
  • 订单创建、查询、状态更新
  • 订单统计

9.2 领域模型设计

// 用户实体
@Entity
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String email;
    private BigDecimal balance;
    
    @OneToMany(mappedBy = "user")
    private List<Order> orders;
}

// 商品实体
@Entity
public class Product {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
    private Integer stock;
    
    @OneToMany(mappedBy = "product")
    private List<OrderItem> orderItems;
}

// 订单实体
@Entity
public class Order {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    private User user;
    
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> items;
    
    private BigDecimal totalAmount;
    private LocalDateTime createTime;
    private OrderStatus status;
}

// 订单项实体
@Entity
public class OrderItem {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    private Order order;
    
    @ManyToOne
    private Product product;
    
    private Integer quantity;
    private BigDecimal unitPrice;
    private BigDecimal subtotal;
}

// 订单状态枚举
public enum OrderStatus {
    CREATED, PAID, SHIPPED, DELIVERED, CANCELLED
}

9.3 复杂业务实现

9.3.1 创建订单服务
@Service
@Transactional
public class OrderService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    public Order createOrder(Long userId, Map<Long, Integer> productQuantities) {
        // 验证用户
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found"));
        
        // 创建订单
        Order order = new Order();
        order.setUser(user);
        order.setCreateTime(LocalDateTime.now());
        order.setStatus(OrderStatus.CREATED);
        
        // 处理订单项
        List<OrderItem> items = new ArrayList<>();
        BigDecimal totalAmount = BigDecimal.ZERO;
        
        for (Map.Entry<Long, Integer> entry : productQuantities.entrySet()) {
            Long productId = entry.getKey();
            Integer quantity = entry.getValue();
            
            // 获取商品并验证库存
            Product product = productRepository.findById(productId)
                    .orElseThrow(() -> new ResourceNotFoundException("Product not found: " + productId));
            
            if (product.getStock() < quantity) {
                throw new InsufficientStockException("Insufficient stock for product: " + product.getName());
            }
            
            // 创建订单项
            OrderItem item = new OrderItem();
            item.setOrder(order);
            item.setProduct(product);
            item.setQuantity(quantity);
            item.setUnitPrice(product.getPrice());
            item.setSubtotal(product.getPrice().multiply(BigDecimal.valueOf(quantity)));
            
            items.add(item);
            totalAmount = totalAmount.add(item.getSubtotal());
            
            // 扣减库存
            product.setStock(product.getStock() - quantity);
            productRepository.save(product);
        }
        
        // 验证用户余额
        if (user.getBalance().compareTo(totalAmount) < 0) {
            throw new InsufficientBalanceException("Insufficient balance");
        }
        
        // 扣减余额
        user.setBalance(user.getBalance().subtract(totalAmount));
        userRepository.save(user);
        
        // 保存订单
        order.setItems(items);
        order.setTotalAmount(totalAmount);
        return orderRepository.save(order);
    }
}
9.3.2 订单统计查询
public interface OrderRepository extends JpaRepository<Order, Long> {
    
    // 按状态统计订单数量
    @Query("SELECT o.status, COUNT(o) FROM Order o GROUP BY o.status")
    List<Object[]> countByStatus();
    
    // 按用户统计订单总金额
    @Query("SELECT u.username, SUM(o.totalAmount) FROM Order o JOIN o.user u GROUP BY u.username")
    List<Object[]> sumAmountByUser();
    
    // 按时间范围查询订单
    @Query("SELECT o FROM Order o WHERE o.createTime BETWEEN :start AND :end ORDER BY o.createTime DESC")
    List<Order> findByCreateTimeBetween(@Param("start") LocalDateTime start, 
                                      @Param("end") LocalDateTime end);
    
    // 查询用户最近订单
    @Query("SELECT o FROM Order o WHERE o.user.id = :userId ORDER BY o.createTime DESC")
    Page<Order> findLatestByUser(@Param("userId") Long userId, Pageable pageable);
}

9.4 基于MySQL版的唯一自增id生成器

详见:唯一id生成器

十、总结与最佳实践

10.1 Spring Boot整合MySQL最佳实践

  1. 实体设计原则

    • 保持实体简洁,只包含业务相关属性
    • 合理使用关联关系,避免过度设计
    • 为常用查询添加适当的索引
  2. Repository设计

    • 优先使用方法命名规则生成查询
    • 复杂查询使用@Query明确表达意图
    • 将相关查询方法组织在同一个Repository中
  3. 事务管理

    • 服务层方法通常应该是事务性的
    • 合理设置事务传播行为和隔离级别
    • 避免在事务中进行远程调用或耗时操作
  4. 性能优化

    • 注意N+1查询问题
    • 批量操作使用适当的分批处理
    • 合理使用二级缓存
  5. 代码组织

    • 遵循分层架构:Controller -> Service -> Repository
    • 保持各层职责单一
    • 使用DTO在不同层之间传输数据

10.2 常见问题解决方案

问题现象解决方案
LazyInitializationException在事务外访问延迟加载的关联属性1. 使用JOIN FETCH预先加载 2. 在事务内访问 3. 使用DTO投影
慢查询响应时间长,数据库负载高1. 添加适当索引 2. 优化SQL 3. 使用分页
并发修改冲突OptimisticLockingFailureException1. 实现重试机制 2. 使用乐观锁 @Version
连接池耗尽获取连接超时1. 增加连接池大小 2. 优化事务范围 3. 检查连接泄漏

关注我?别别别,我怕你笑出腹肌找我赔钱。

喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clf丶忆笙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值