一、前言:從玩具項目到生產系統
經過前四天的學習,我們已經能夠開發基礎功能了。但要讓應用真正具備生產價值,還需要掌握數據庫高級操作、事務控制、緩存優化等企業級開發技能。今天就來攻克這些關鍵知識點!
二、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統一日志處理
思考題:在電商系統中,下單操作需要扣減庫存、生成訂單、扣減優惠券等多個步驟,該如何設計事務邊界?歡迎評論區分享你的設計方案!
如果覺得這篇日記有幫助,請點贊收藏支持~完整代碼示例可通過私信獲取。在實際開發中遇到問題也歡迎留言討論!