一、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 | 数据库连接URL | jdbc:mysql://主机:端口/数据库名?参数 |
spring.jpa.show-sql | 是否显示执行的SQL | true/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条件 |
---|---|---|
And | findByUsernameAndPassword | where username=? and password=? |
Or | findByUsernameOrEmail | where username=? or email=? |
Is,Equals | findByIdIs | where id=? |
Between | findByCreateTimeBetween | where createTime between ? and ? |
LessThan | findByAgeLessThan | where age < ? |
Like | findByUsernameLike | where username like ? |
OrderBy | findByUserTypeOrderByCreateTimeDesc | where 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:
- 定义自定义接口:
public interface CustomUserRepository {
List<User> findActiveUsers();
int updateUserStatus(Long userId, boolean active);
}
- 实现自定义接口:
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();
}
}
- 扩展主Repository接口:
public interface UserRepository extends JpaRepository<User, Long>, CustomUserRepository {
// ...其他方法
}
五、性能优化与最佳实践
5.1 JPA性能优化
5.1.1 N+1查询问题解决方案
问题描述:当查询主实体并访问其关联实体时,会触发额外的查询。
解决方案:
- 使用JOIN FETCH:
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :userId")
User findUserWithOrders(@Param("userId") Long userId);
- 使用@EntityGraph:
@EntityGraph(attributePaths = {"orders"})
@Query("SELECT u FROM User u WHERE u.id = :userId")
User findUserWithOrders(@Param("userId") Long userId);
- 在实体上定义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 批量操作优化
- 批量插入:
@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
- 批量更新:
@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 二级缓存配置
- 添加依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
- 配置:
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
- 在实体上启用缓存:
@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注入防护
- 始终使用参数化查询:
// 正确做法 - 使用参数
@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);
-
使用JPA方法命名规则自动生成的查询也是安全的。
-
如果需要原生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状态码 |
JpaSystemException | JPA系统异常 | 记录日志,返回500状态码 |
OptimisticLockingFailureException | 乐观锁冲突 | 返回409状态码,提示重试 |
TransactionSystemException | 事务系统异常 | 记录日志,返回500状态码 |
七、监控与调试
7.1 监控配置
- 添加Spring Boot Actuator依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 配置数据库监控端点:
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
- 自定义健康检查:
@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:
- 添加依赖:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
- 配置:
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.metrics.export.prometheus.enabled=true
- 自定义指标:
@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 JPA | MyBatis | JDBC |
---|---|---|---|
学习曲线 | 中等 | 中等 | 低 |
开发效率 | 高 | 中 | 低 |
灵活性 | 中 | 高 | 最高 |
性能 | 中 | 高 | 最高 |
SQL控制 | 自动生成 | 完全控制 | 完全控制 |
缓存支持 | 完善 | 有限 | 无 |
适合场景 | 快速开发标准CRUD | 复杂SQL项目 | 极致性能需求 |
8.2 多数据源配置
- 配置主数据源:
@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);
}
}
- 配置次数据源:
@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);
}
}
- 配置文件:
# 主数据源
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最佳实践
-
实体设计原则:
- 保持实体简洁,只包含业务相关属性
- 合理使用关联关系,避免过度设计
- 为常用查询添加适当的索引
-
Repository设计:
- 优先使用方法命名规则生成查询
- 复杂查询使用@Query明确表达意图
- 将相关查询方法组织在同一个Repository中
-
事务管理:
- 服务层方法通常应该是事务性的
- 合理设置事务传播行为和隔离级别
- 避免在事务中进行远程调用或耗时操作
-
性能优化:
- 注意N+1查询问题
- 批量操作使用适当的分批处理
- 合理使用二级缓存
-
代码组织:
- 遵循分层架构:Controller -> Service -> Repository
- 保持各层职责单一
- 使用DTO在不同层之间传输数据
10.2 常见问题解决方案
问题 | 现象 | 解决方案 |
---|---|---|
LazyInitializationException | 在事务外访问延迟加载的关联属性 | 1. 使用JOIN FETCH预先加载 2. 在事务内访问 3. 使用DTO投影 |
慢查询 | 响应时间长,数据库负载高 | 1. 添加适当索引 2. 优化SQL 3. 使用分页 |
并发修改冲突 | OptimisticLockingFailureException | 1. 实现重试机制 2. 使用乐观锁 @Version |
连接池耗尽 | 获取连接超时 | 1. 增加连接池大小 2. 优化事务范围 3. 检查连接泄漏 |
关注我?别别别,我怕你笑出腹肌找我赔钱。
喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!