一、前言:从玩具项目到生产系统
经过前四天的学习,我们已经能够开发基础功能了。但要让应用真正具备生产价值,还需要掌握数据库高级操作、事务控制、缓存优化等企业级开发技能。今天就来攻克这些关键知识点!
二、JPA进阶:让数据库操作更高效
1. 复杂查询的三种实现方式
方式一:方法名派生查询
public interface UserRepository extends JpaRepository<User, Long> {// 根据姓名模糊查询+年龄范围List<User> findByUsernameContainingAndAgeBetween(String name, Integer minAge, Integer maxAge);// 统计大于某年龄的用户数Long countByAgeGreaterThan(Integer age);
}
方式二:@Query注解(JPQL)
@Query("SELECT u FROM User u WHERE u.dept.id = :deptId AND u.status = :status")
List<User> findUsersByDeptAndStatus(@Param("deptId") Long deptId, @Param("status") Integer status);
方式三:原生SQL查询
@Query(value = "SELECT * FROM users WHERE reg_time > :date", nativeQuery = true)
List<User> findRecentUsers(@Param("date") Date date);
2. 分页查询最佳实践
@GetMapping("/users")
public PageResult<User> getUsers(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "10") Integer size,@RequestParam(required = false) String name) {Pageable pageable = PageRequest.of(page - 1, size, Sort.by("createTime").descending());Page<User> userPage;if (StringUtils.isEmpty(name)) {userPage = userRepository.findAll(pageable);} else {userPage = userRepository.findByUsernameContaining(name, pageable);}return PageResult.success(userPage);
}
3. 关联关系实战(用户-部门示例)
实体类配置:
@Entity
@Data
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;@ManyToOne@JoinColumn(name = "dept_id")private Department department;
}@Entity
@Data
public class Department {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;@OneToMany(mappedBy = "department")private List<User> users;
}
查询优化建议:
1. 使用@EntityGraph解决N+1查询问题:
@EntityGraph(attributePaths = {"department"})
List<User> findAllWithDepartment();
2. 延迟加载时注意事务范围:
// 在Service层方法上添加事务注解
@Transactional(readOnly = true)
public User getUserDetail(Long id) {User user = userRepository.findById(id).orElseThrow();// 此时可以安全访问延迟加载的关联对象System.out.println(user.getDepartment().getName());return user;
}
三、事务管理:数据一致性的守护者
1. 声明式事务基础
@Service
@RequiredArgsConstructor
public class OrderService {private final OrderRepository orderRepository;private final UserRepository userRepository;@Transactionalpublic void createOrder(OrderDTO dto) {// 扣减用户余额User user = userRepository.findById(dto.getUserId()).orElseThrow(() -> new BusinessException("用户不存在"));user.setBalance(user.getBalance() - dto.getAmount());userRepository.save(user);// 创建订单Order order = new Order();// 设置订单属性...orderRepository.save(order);// 如果这里抛出异常,上面所有操作都会回滚}
}
2. 事务传播行为实验
传播行为 | 说明 | 适用场景 |
---|---|---|
REQUIRED(默认) | 当前有事务则加入,没有则新建 | 大多数业务方法 |
REQUIRES_NEW | 总是新建事务,挂起当前事务 | 日志记录等独立操作 |
NESTED | 在当前事务嵌套子事务 | 部分需要独立回滚的子流程 |
测试用例:
@Transactional
public void parentMethod() {// 操作1...childMethod(); // 测试不同传播行为// 操作2...
}@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {// 子方法操作...
}
四、Redis缓存:性能加速器
1. 集成Redis三步走
第一步:添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
第二步:配置连接
spring:redis:host: localhostport: 6379password: database: 0
第三步:启用缓存
@SpringBootApplication
@EnableCaching
public class MyApp {public static void main(String[] args) {SpringApplication.run(MyApp.class, args);}
}
2. 缓存注解实战
@Service
public class UserService {// 缓存查询结果@Cacheable(value = "user", key = "#id")public User getUserById(Long id) {return userRepository.findById(id).orElseThrow();}// 更新时清除缓存@CacheEvict(value = "user", key = "#user.id")public User updateUser(User user) {return userRepository.save(user);}// 条件缓存@Cacheable(value = "user", key = "#name", unless = "#result == null")public User getUserByName(String name) {return userRepository.findByUsername(name);}
}
五、文件操作:用户头像上传实战
1. 配置文件上传
spring:servlet:multipart:max-file-size: 2MBmax-request-size: 10MB
2. 实现上传接口
@PostMapping("/avatar/upload")
public Result<String> uploadAvatar(@RequestParam("file") MultipartFile file) {if (file.isEmpty()) {throw new BusinessException("请选择文件");}// 生成唯一文件名String fileName = UUID.randomUUID() + "." + FileUtil.getExtension(file.getOriginalFilename());// 保存文件Path path = Paths.get("uploads/avatars", fileName);try {Files.createDirectories(path.getParent());file.transferTo(path);} catch (IOException e) {throw new BusinessException("文件上传失败");}return Result.success("/avatars/" + fileName);
}
3. 文件下载实现
@GetMapping("/avatar/download/{filename:.+}")
public void downloadAvatar(@PathVariable String filename, HttpServletResponse response) throws IOException {Path path = Paths.get("uploads/avatars", filename);if (!Files.exists(path)) {response.sendError(404, "文件不存在");return;}response.setContentType("image/jpeg");Files.copy(path, response.getOutputStream());
}
六、今日成果:用户管理系统升级版
1. 数据库层:
- 实现多表关联查询
- 支持分页排序
- 完善事务管理
2. 缓存层:
- 高频查询结果缓存
- 自动更新缓存策略
3. 文件操作:
- 头像上传下载功能
- 文件大小限制处理
4. API增强:
// 分页查询示例
GET /users?page=1&size=10&name=张&sort=age,desc// 头像上传
POST /avatar/upload// 带缓存的用户查询
GET /users/{id}
七、避坑指南
1. N+1查询问题:
- 使用@EntityGraph或JOIN FETCH优化
- 测试时开启SQL日志观察查询次数
2. 事务失效场景:
- 方法必须是public
- 自调用问题(AOP失效)
- 异常类型默认只回滚RuntimeException
3. 缓存一致性:
- 更新数据库后及时清除缓存
- 考虑使用@CachePut更新缓存
4. 文件存储安全:
- 校验文件类型(不要仅靠扩展名)
- 限制上传目录权限
- 考虑使用云存储服务
八、明日计划
1. 学习SpringBoot定时任务
2. 集成邮件发送功能
3. 实现系统监控端点
4. 探索AOP统一日志处理
思考题:在电商系统中,下单操作需要扣减库存、生成订单、扣减优惠券等多个步骤,该如何设计事务边界?欢迎评论区分享你的设计方案!
如果觉得这篇日记有帮助,请点赞收藏支持~完整代码示例可通过私信获取。在实际开发中遇到问题也欢迎留言讨论!