Spring MVC的三層架構(gòu)使用及解析
一、Spring MVC 三層架構(gòu)概述
在傳統(tǒng)的 Java Web 開發(fā)中(如 Servlet+JSP),代碼往往混雜在一起:數(shù)據(jù)處理、頁面交互、業(yè)務(wù)邏輯全部寫在 Servlet 中,導(dǎo)致項目維護困難、擴展性差。
這種開發(fā)模式存在以下典型問題:
- 單個Servlet文件可能包含數(shù)百行代碼,混合處理業(yè)務(wù)邏輯和視圖渲染
- 數(shù)據(jù)庫操作直接嵌入業(yè)務(wù)邏輯中,難以更換數(shù)據(jù)源
- 代碼復(fù)用率低,相似功能需要重復(fù)實現(xiàn)
- 單元測試困難,各功能模塊耦合度過高
Spring MVC 的三層架構(gòu)正是為解決這些問題而生,通過職責拆分,將系統(tǒng)分為三個核心層級,每層專注于特定功能,既降低了耦合度,又提升了代碼的可復(fù)用性和可維護性。
這種分層架構(gòu)模式借鑒了企業(yè)級應(yīng)用開發(fā)的成熟經(jīng)驗,并針對Web應(yīng)用場景進行了優(yōu)化。
1.1 三層架構(gòu)的核心定義
Spring MVC 的三層架構(gòu)并非獨立存在,而是相互協(xié)作、自上而下的調(diào)用關(guān)系,具體包括:
表現(xiàn)層(Presentation Layer)
- 直接與用戶交互,負責接收請求、返回響應(yīng)
主要功能包括:
- 接收HTTP請求參數(shù)并進行基本校驗
- 調(diào)用Service層處理業(yè)務(wù)邏輯
- 返回響應(yīng)(頁面渲染或JSON數(shù)據(jù))
- 核心組件:Spring MVC的Controller
典型實現(xiàn)示例:
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
業(yè)務(wù)邏輯層(Business Logic Layer,簡稱Service層)
- 處理核心業(yè)務(wù)邏輯的中樞
主要職責包括:
- 復(fù)雜業(yè)務(wù)規(guī)則的實現(xiàn)
- 數(shù)據(jù)有效性校驗
- 事務(wù)管理(通過@Transactional注解)
- 異常處理
- 組合多個數(shù)據(jù)操作完成業(yè)務(wù)功能
典型實現(xiàn)示例:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Transactional
@Override
public User createUser(UserDto userDto) {
// 業(yè)務(wù)校驗
if(userMapper.existsByUsername(userDto.getUsername())) {
throw new BusinessException("用戶名已存在");
}
// 數(shù)據(jù)轉(zhuǎn)換
User user = new User();
BeanUtils.copyProperties(userDto, user);
// 持久化操作
userMapper.insert(user);
return user;
}
}
持久層(Persistence Layer)
- 專注于數(shù)據(jù)持久化操作
主要特點:
- 只關(guān)心"如何訪問數(shù)據(jù)",不關(guān)心業(yè)務(wù)邏輯
- 提供標準的CRUD操作方法
- 支持多種持久化技術(shù)(JDBC、MyBatis、JPA等)
核心組件:
- DAO(Data Access Object)或Repository
- MyBatis的Mapper接口
典型實現(xiàn)示例:
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectById(Long id);
@Insert("INSERT INTO users(username, password) VALUES(#{username}, #{password})")
void insert(User user);
}
1.2 三層架構(gòu)的調(diào)用流程
請求接收階段
- 用戶通過瀏覽器訪問
/users/123 - HTTP請求被Spring MVC的前端控制器DispatcherServlet攔截
- DispatcherServlet查找所有已注冊的HandlerMapping
請求路由階段
- HandlerMapping根據(jù)URL路徑
/users/123匹配到UserController的getUser方法 - 將路徑變量
123解析為方法參數(shù)id
業(yè)務(wù)處理階段
- Controller調(diào)用UserService的getUserById方法
Service層可能執(zhí)行以下操作:
- 參數(shù)校驗(如ID有效性)
- 業(yè)務(wù)規(guī)則判斷(如權(quán)限檢查)
- 調(diào)用持久層獲取數(shù)據(jù)
數(shù)據(jù)訪問階段
- Service調(diào)用UserMapper的selectById方法
- MyBatis執(zhí)行SQL:
SELECT * FROM users WHERE id = 123 - 將查詢結(jié)果映射為User對象返回
響應(yīng)返回階段
- 查詢結(jié)果沿調(diào)用鏈返回:Mapper → Service → Controller
- Controller將User對象轉(zhuǎn)換為JSON格式
- DispatcherServlet將響應(yīng)寫入HttpServletResponse
視圖渲染階段(可選)
如果返回的是視圖(如JSP):
- DispatcherServlet將ModelAndView交給ViewResolver
- ViewResolver解析視圖名稱,定位具體的JSP文件
- 視圖引擎渲染JSP,生成HTML響應(yīng)
三層架構(gòu)交互時序圖示例:
Client → DispatcherServlet → Controller → Service → Mapper → DB
↑ |
| ↓
Client ← DispatcherServlet ← Controller ← Service ← Mapper這種分層架構(gòu)使得系統(tǒng)各部分的職責更加清晰,便于團隊協(xié)作開發(fā)、單元測試和后期維護。例如:
- 前端開發(fā)人員只需要關(guān)注Controller層的接口定義
- 業(yè)務(wù)分析師可以基于Service層的代碼理解業(yè)務(wù)規(guī)則
- DBA可以優(yōu)化Mapper層的SQL語句而不影響業(yè)務(wù)邏輯
二、各層詳細拆解
2.1 表現(xiàn)層(Controller 層):用戶交互的 "入口"
表現(xiàn)層是 Spring MVC 框架中與用戶直接交互的層級,作為系統(tǒng)的"門面",負責處理 HTTP 請求和響應(yīng)。它的核心組件是 Controller 類,主要職責包括:
- 接收客戶端請求(解析請求參數(shù)、請求頭等)
- 進行基礎(chǔ)參數(shù)校驗
- 調(diào)用 Service 層處理業(yè)務(wù)邏輯
- 組裝響應(yīng)數(shù)據(jù)并返回給客戶端
- 處理異常情況并返回友好錯誤信息
2.1.1 核心組件與注解詳解
@Controller
- 作用:標識一個類作為 Spring MVC 的控制器
- 實現(xiàn)原理:Spring 會在啟動時掃描帶有該注解的類,并將其注冊為 Spring Bean
- 配套機制:配合組件掃描注解 @ComponentScan 使用
示例:
@Controller
public class HomeController {
// 控制器方法...
}
@RequestMapping
核心功能:
- 路徑映射:將 HTTP 請求映射到控制器方法
- 請求方法限定:通過 method 屬性指定處理的 HTTP 方法
- 參數(shù)匹配:通過 params 屬性匹配特定請求參數(shù)
- 頭部匹配:通過 headers 屬性匹配特定請求頭
高級用法:
- 通配符支持:如 "/user/*" 可匹配 "/user/123" 等路徑
- 類級別與方法級別組合:類級別定義基礎(chǔ)路徑,方法級別定義具體路徑
- 媒體類型限定:通過 produces/consumes 指定處理的內(nèi)容類型
示例:
@RequestMapping(value = "/products", method = RequestMethod.GET)
public String listProducts() {...}
@GetMapping/@PostMapping
優(yōu)點:
- 代碼更簡潔:相比 @RequestMapping(method = RequestMethod.GET) 更易讀
- 語義更明確:直接表明處理的 HTTP 方法
- 支持繼承:可以組合使用 @RequestMapping 的類級別注解
示例對比:
// 傳統(tǒng)方式
@RequestMapping(value = "/user", method = RequestMethod.GET)
// 簡化方式
@GetMapping("/user")
@RequestParam
主要參數(shù):
- name/value:指定綁定的請求參數(shù)名稱
- required:是否為必須參數(shù)(默認 true)
- defaultValue:參數(shù)默認值
使用場景:
- 處理查詢參數(shù):如 ?page=1&size=10
- 處理表單數(shù)據(jù):如 application/x-www-form-urlencoded
示例:
@GetMapping("/search")
public String search(@RequestParam(name = "keyword", required = false, defaultValue = "") String keyword) {...}
@PathVariable
特點:
- 用于 RESTful 風格的 URL 參數(shù)獲取
- 支持正則表達式匹配路徑變量
- 可配合 @RequestMapping 的通配符使用
示例:
@GetMapping("/users/{userId}/orders/{orderId}")
public String getOrder(@PathVariable Long userId, @PathVariable String orderId) {...}
@ResponseBody
工作機制:
- 通過 HttpMessageConverter 將返回值轉(zhuǎn)換為指定格式
- 常用轉(zhuǎn)換器:MappingJackson2HttpMessageConverter(JSON)
- 可自定義轉(zhuǎn)換器處理特殊格式
典型應(yīng)用:
- 前后端分離架構(gòu)中的 API 接口
- AJAX 請求響應(yīng)
- 移動端接口開發(fā)
示例:
@ResponseBody
@GetMapping("/api/user/{id}")
public User getUser(@PathVariable Long id) {...}
@RestController
組合優(yōu)勢:
- 減少樣板代碼:無需在每個方法上添加 @ResponseBody
- 語義更清晰:明確表示該類是純 API 控制器
- 自動配置:默認啟用 JSON 序列化
實現(xiàn)原理:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {...}
2.1.2 示例:一個完整的 Controller 實現(xiàn)
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Min;
import java.util.List;
@Controller
@RequestMapping("/user")
@Validated // 啟用方法參數(shù)驗證
public class UserController {
private final UserService userService;
// 推薦使用構(gòu)造器注入
public UserController(UserService userService) {
this.userService = userService;
}
/**
* 用戶列表頁面
* @param page 頁碼(從1開始)
* @param size 每頁條數(shù)
* @param model 視圖模型
* @return 視圖名稱
*/
@GetMapping("/list")
public String listUsers(
@RequestParam(defaultValue = "1") @Min(1) int page,
@RequestParam(defaultValue = "10") @Min(1) int size,
Model model) {
PageInfo<User> pageInfo = userService.getUsersByPage(page, size);
model.addAttribute("pageInfo", pageInfo);
return "user/list";
}
/**
* 獲取用戶詳情(REST API)
* @param userId 用戶ID
* @return 統(tǒng)一響應(yīng)結(jié)果
*/
@GetMapping("/{userId}")
@ResponseBody
public Result<User> getUserDetail(@PathVariable @Min(1) Long userId) {
User user = userService.getUserById(userId);
return Result.success(user);
}
/**
* 創(chuàng)建新用戶
* @param userDTO 用戶數(shù)據(jù)
* @return 創(chuàng)建結(jié)果
*/
@PostMapping
@ResponseBody
public Result<Long> createUser(@RequestBody @Valid UserDTO userDTO) {
Long userId = userService.createUser(userDTO);
return Result.success(userId);
}
}
2.1.3 最佳實踐與注意事項
職責分離原則
Controller 應(yīng)保持"瘦身",僅處理:
- 請求/響應(yīng)轉(zhuǎn)換
- 基礎(chǔ)參數(shù)驗證
- 異常捕獲
所有業(yè)務(wù)邏輯應(yīng)委托給 Service 層
參數(shù)校驗建議
使用 JSR-380 規(guī)范注解:
- @NotNull/@NotEmpty/@NotBlank
- @Size(min=, max=)
- @Pattern(regexp=)
- @Min/@Max
分組校驗:通過 groups 屬性實現(xiàn)不同場景的校驗規(guī)則
自定義校驗:實現(xiàn) ConstraintValidator 接口
統(tǒng)一響應(yīng)格式
推薦結(jié)構(gòu):
public class Result<T> {
private int code; // 狀態(tài)碼
private String msg; // 消息
private T data; // 數(shù)據(jù)
private long timestamp = System.currentTimeMillis();
// 構(gòu)造方法、靜態(tài)工廠方法等...
}
使用示例:
@GetMapping("/{id}")
public Result<User> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
return Result.success(user);
}
異常處理
推薦使用 @ControllerAdvice 統(tǒng)一處理異常
示例:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseBody
public Result<?> handleBusinessException(BusinessException e) {
return Result.fail(e.getErrorCode(), e.getMessage());
}
}
性能考慮
避免在 Controller 中進行:
- 復(fù)雜計算
- 數(shù)據(jù)庫操作
- 耗時 I/O 操作
使用異步處理:@Async、DeferredResult 等
安全建議
- 對敏感參數(shù)進行過濾
- 重要操作添加權(quán)限校驗
- 防止 CSRF 攻擊
- 輸入?yún)?shù)進行 XSS 防護
測試建議
- 使用 MockMvc 進行單元測試
測試用例應(yīng)覆蓋:
- 正常流程
- 參數(shù)校驗失敗情況
- 異常情況處理
- 邊界條件
2.2 業(yè)務(wù)邏輯層(Service 層):系統(tǒng)的 "大腦"
Service 層是整個系統(tǒng)的核心業(yè)務(wù)處理中樞,負責實現(xiàn)業(yè)務(wù)規(guī)則、處理事務(wù)、協(xié)調(diào)多個持久層操作,是表現(xiàn)層與持久層之間的 "橋梁"。
它相當于應(yīng)用系統(tǒng)的"大腦",負責處理復(fù)雜的業(yè)務(wù)邏輯,確保業(yè)務(wù)流程的正確性和數(shù)據(jù)一致性。
2.2.1 核心組件與注解
@Service 注解
- 作用:標記一個類為 Service 層組件,Spring 會自動掃描并注冊為 Bean,供 Controller 層注入
- 使用場景:所有業(yè)務(wù)邏輯處理類都應(yīng)該使用此注解
示例:
@Service
public class OrderServiceImpl implements OrderService {...}
@Transactional 注解
- 作用:聲明事務(wù)管理,可用于類或方法上,指定事務(wù)的各種屬性
常用參數(shù):
propagation:事務(wù)傳播行為(如REQUIRED, REQUIRES_NEW)isolation:事務(wù)隔離級別(如DEFAULT, READ_COMMITTED)rollbackFor:指定哪些異常需要回滾timeout:事務(wù)超時時間
示例:
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
timeout = 30,
rollbackFor = Exception.class)
接口與實現(xiàn)類設(shè)計
優(yōu)勢:
- 面向接口編程,便于后續(xù)擴展
- 方便進行單元測試(可mock接口)
- 實現(xiàn)多態(tài)特性
典型結(jié)構(gòu):
├── service │ ├── UserService.java // 接口 │ └── impl │ └── UserServiceImpl.java // 實現(xiàn)類
2.2.2 示例:Service 接口與實現(xiàn)類
1. Service接口設(shè)計
/**
* 用戶服務(wù)接口
* 定義業(yè)務(wù)契約,不包含具體實現(xiàn)
*/
public interface UserService {
/**
* 查詢所有用戶
* @return 用戶列表(可能為空列表)
*/
List<User> getAllUsers();
/**
* 根據(jù)ID查詢用戶
* @param userId 用戶ID
* @return 用戶實體
* @throws BusinessException 當ID無效時拋出
*/
User getUserById(Integer userId) throws BusinessException;
/**
* 新增用戶
* @param user 用戶實體
* @return 操作是否成功
* @throws BusinessException 當用戶名已存在等業(yè)務(wù)異常時拋出
*/
boolean addUser(User user) throws BusinessException;
/**
* 批量導(dǎo)入用戶
* @param users 用戶列表
* @return 成功導(dǎo)入的數(shù)量
*/
@Transactional
int batchImportUsers(List<User> users);
}
2. Service實現(xiàn)類詳解
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* 用戶服務(wù)實現(xiàn)類
* 實現(xiàn)具體的業(yè)務(wù)邏輯
*/
@Service // 標記為Service組件
public class UserServiceImpl implements UserService {
// 使用@Resource或@Autowired注入持久層組件
@Resource
private UserMapper userMapper;
@Resource
private RoleService roleService;
@Resource
private LogService logService;
// 簡單查詢操作通常不需要事務(wù)
@Override
public List<User> getAllUsers() {
// 直接調(diào)用Mapper層方法獲取數(shù)據(jù)
// 可添加緩存邏輯提升性能
return userMapper.selectAll();
}
@Override
public User getUserById(Integer userId) throws BusinessException {
// 業(yè)務(wù)校驗
if (userId == null || userId <= 0) {
throw new BusinessException(ErrorCode.INVALID_USER_ID, "用戶ID無效");
}
// 查詢用戶
User user = userMapper.selectById(userId);
// 業(yè)務(wù)處理:如果用戶不存在
if (user == null) {
throw new BusinessException(ErrorCode.USER_NOT_FOUND, "用戶不存在");
}
return user;
}
// 需要事務(wù)管理的業(yè)務(wù)方法
@Override
@Transactional(rollbackFor = Exception.class)
public boolean addUser(User user) throws BusinessException {
// 1. 參數(shù)校驗
if (user == null || StringUtils.isEmpty(user.getUsername())) {
throw new BusinessException(ErrorCode.INVALID_PARAM, "用戶信息不完整");
}
// 2. 業(yè)務(wù)校驗(用戶名唯一性檢查)
User existingUser = userMapper.selectByUsername(user.getUsername());
if (existingUser != null) {
throw new BusinessException(ErrorCode.USERNAME_EXISTS, "用戶名已存在");
}
// 3. 設(shè)置默認值等業(yè)務(wù)處理
user.setCreateTime(new Date());
user.setStatus(1); // 默認激活狀態(tài)
// 4. 調(diào)用Mapper層保存數(shù)據(jù)
int rows = userMapper.insert(user);
// 5. 關(guān)聯(lián)操作(如分配默認角色)
roleService.assignDefaultRole(user.getId());
// 6. 記錄操作日志(異步處理)
logService.asyncRecordLog("USER_ADD", "新增用戶:" + user.getUsername());
return rows > 0;
}
@Override
@Transactional
public int batchImportUsers(List<User> users) {
int successCount = 0;
for (User user : users) {
try {
if (addUser(user)) {
successCount++;
}
} catch (BusinessException e) {
// 記錄導(dǎo)入失敗的用戶
log.warn("導(dǎo)入用戶失敗: {}", user.getUsername(), e);
}
}
return successCount;
}
}
2.2.3 開發(fā)注意事項
職責劃分原則
- Service層應(yīng)專注于業(yè)務(wù)邏輯處理
- 所有數(shù)據(jù)庫操作必須通過Mapper/DAO層完成
- 避免在Service層直接編寫SQL語句
事務(wù)管理最佳實踐
- 事務(wù)注解應(yīng)加在Service層而非Controller層
- 默認情況下只對RuntimeException回滾,建議明確指定
rollbackFor - 只讀操作使用
@Transactional(readOnly = true)提升性能 - 避免在同一個類中自調(diào)用事務(wù)方法(因代理機制會失效)
異常處理規(guī)范
// 自定義業(yè)務(wù)異常示例
public class BusinessException extends RuntimeException {
private String errorCode;
public BusinessException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
// getter方法...
}
- 使用自定義業(yè)務(wù)異常替代RuntimeException
- 不同業(yè)務(wù)錯誤定義不同的錯誤碼
- 在Controller層統(tǒng)一處理業(yè)務(wù)異常
性能優(yōu)化建議
- 復(fù)雜查詢考慮添加緩存
- 批量操作使用批量處理方法
- 耗時操作考慮異步處理
測試考量
- Service層應(yīng)易于單元測試
- 使用Mock對象隔離依賴
- 測試應(yīng)覆蓋各種業(yè)務(wù)場景和異常分支
典型業(yè)務(wù)場景處理
- 分布式事務(wù):對于跨服務(wù)調(diào)用,考慮使用Seata等分布式事務(wù)解決方案
- 冪等性處理:對于支付等關(guān)鍵業(yè)務(wù),需要實現(xiàn)冪等性控制
- 業(yè)務(wù)流水號:重要業(yè)務(wù)操作應(yīng)生成唯一業(yè)務(wù)流水號便于追蹤
2.3 持久層(Mapper/DAO 層):數(shù)據(jù)的 "搬運工"
持久層作為應(yīng)用程序與數(shù)據(jù)庫之間的橋梁,專注于數(shù)據(jù)的持久化操作。其主要職責包括:
- 執(zhí)行基本的 CRUD 操作(Create/Read/Update/Delete)
- 處理數(shù)據(jù)庫事務(wù)
- 實現(xiàn)數(shù)據(jù)緩存(可選)
- 進行數(shù)據(jù)校驗(基礎(chǔ)級別)
與業(yè)務(wù)層不同,持久層不關(guān)心業(yè)務(wù)邏輯,只關(guān)注"數(shù)據(jù)如何存儲和獲取"。在現(xiàn)代 Java Web 開發(fā)中,MyBatis 已成為最流行的持久層框架之一,相比傳統(tǒng)的 JDBC 和 DAO 模式,它具有以下優(yōu)勢:
- 簡化了數(shù)據(jù)庫操作
- 提供自動的對象關(guān)系映射(ORM)
- 支持動態(tài)SQL
- 具有更好的性能
2.3.1 核心組件與配置
1. Mapper 接口
Mapper 接口是 MyBatis 的核心概念,它替代了傳統(tǒng)的 DAO 接口。與 DAO 不同:
- 不需要編寫實現(xiàn)類
- MyBatis 通過動態(tài)代理技術(shù)自動生成實現(xiàn)
- 方法簽名直接對應(yīng) SQL 操作
// 典型 Mapper 接口定義
@Mapper
public interface UserMapper {
// 查詢方法
User selectById(Long id);
// 插入方法
int insert(User user);
// 更新方法
int update(User user);
// 刪除方法
int deleteById(Long id);
}
2. 關(guān)鍵注解
@Mapper:標記接口為 MyBatis Mapper 接口@MapperScan:在 Spring Boot 啟動類或配置類上使用,指定 Mapper 接口的掃描路徑@Param:用于多參數(shù)方法,指定參數(shù)名稱
3. XML 映射文件
XML 文件是 SQL 語句的主要存放位置,通常包含:
- 結(jié)果映射定義(
<resultMap>) - SQL 查詢語句(
<select>) - 插入語句(
<insert>) - 更新語句(
<update>) - 刪除語句(
<delete>)
2.3.2 示例詳解
1. Mapper 接口增強版
@Mapper
public interface UserMapper {
// 基礎(chǔ)CRUD操作
List<User> selectAll();
User selectById(@Param("id") Long id);
int insert(User user);
int update(User user);
int deleteById(@Param("id") Long id);
// 分頁查詢
List<User> selectByPage(@Param("offset") int offset,
@Param("pageSize") int pageSize);
// 條件查詢
List<User> selectByCondition(@Param("condition") UserQueryCondition condition);
// 批量操作
int batchInsert(@Param("users") List<User> users);
int batchUpdate(@Param("users") List<User> users);
}
2. XML 映射文件詳解
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 高級結(jié)果映射 -->
<resultMap id="UserDetailResultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<result column="create_time" property="createTime"
jdbcType="TIMESTAMP"/>
<!-- 關(guān)聯(lián)映射 -->
<association property="department" javaType="Department">
<id column="dept_id" property="id"/>
<result column="dept_name" property="name"/>
</association>
</resultMap>
<!-- 動態(tài)SQL查詢 -->
<select id="selectByCondition" resultMap="UserDetailResultMap">
SELECT u.*, d.id as dept_id, d.name as dept_name
FROM user u
LEFT JOIN department d ON u.dept_id = d.id
<where>
<if test="condition.username != null">
AND u.username LIKE CONCAT('%', #{condition.username}, '%')
</if>
<if test="condition.email != null">
AND u.email = #{condition.email}
</if>
<if test="condition.createTimeStart != null">
AND u.create_time >= #{condition.createTimeStart}
</if>
</where>
ORDER BY u.create_time DESC
</select>
<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO user (username, email, create_time)
VALUES
<foreach collection="users" item="user" separator=",">
(#{user.username}, #{user.email}, #{user.createTime})
</foreach>
</insert>
</mapper>
2.3.3 高級特性與最佳實踐
動態(tài)SQL:
- 使用
<if>,<choose>,<when>,<otherwise>標簽實現(xiàn)條件判斷 <where>標簽自動處理WHERE子句<set>標簽用于UPDATE語句
關(guān)聯(lián)查詢:
- 一對一:
<association> - 一對多:
<collection> - 延遲加載:配置
lazyLoadingEnabled=true
性能優(yōu)化:
- 使用二級緩存(需謹慎)
- 批量操作代替單條操作
- 合理使用延遲加載
事務(wù)管理:
- 在Service層使用
@Transactional注解 - 配置適當?shù)氖聞?wù)傳播行為
分頁實現(xiàn):
- 使用PageHelper插件
- 手動實現(xiàn)分頁(limit offset)
2.3.4 常見問題解決方案
參數(shù)映射問題:
- 簡單類型參數(shù):直接使用
#{param} - 對象參數(shù):使用
#{propertyName} - Map參數(shù):使用
#{key} - 多參數(shù):必須使用
@Param注解
結(jié)果映射問題:
- 字段名與屬性名不一致時使用
<resultMap> - 復(fù)雜類型使用嵌套映射
- 使用
<constructor>進行構(gòu)造函數(shù)映射
SQL注入防護:
- 永遠使用
#{}而不是${}進行參數(shù)綁定 - 對用戶輸入進行嚴格校驗
性能監(jiān)控:
- 配置SQL日志輸出
- 使用MyBatis-Plus的性能分析插件
- 監(jiān)控慢SQL
通過以上規(guī)范和最佳實踐,可以構(gòu)建出高效、可維護的持久層,為應(yīng)用程序提供可靠的數(shù)據(jù)訪問支持。
三、基于三層架構(gòu)搭建 Spring MVC 項目
3.1 項目結(jié)構(gòu)(Maven)
一個標準的 Spring MVC 三層架構(gòu)項目結(jié)構(gòu)如下(以 IntelliJ IDEA 為例):
src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ ├── controller/ # 表現(xiàn)層(Controller) │ │ │ └── UserController.java │ │ ├── service/ # 業(yè)務(wù)邏輯層(Service) │ │ │ ├── UserService.java # 接口 │ │ │ └── impl/ │ │ │ └── UserServiceImpl.java # 實現(xiàn)類 │ │ ├── mapper/ # 持久層(Mapper) │ │ │ └── UserMapper.java │ │ ├── entity/ # 實體類(對應(yīng)數(shù)據(jù)庫表) │ │ │ └── User.java │ │ ├── exception/ # 自定義異常 │ │ │ └── BusinessException.java │ │ ├── config/ # Spring配置類 │ │ │ └── SpringMvcConfig.java │ │ └── util/ # 工具類 │ │ └── Result.java # 統(tǒng)一響應(yīng)封裝 │ ├── resources/ │ │ ├── mapper/ # Mapper XML文件 │ │ │ └── UserMapper.xml │ │ ├── application.properties # 全局配置(數(shù)據(jù)庫、MyBatis等) │ │ └── static/ # 靜態(tài)資源(CSS、JS、圖片) │ └── webapp/ # Web資源 │ ├── WEB-INF/ │ │ ├── views/ # 視圖頁面(JSP/HTML) │ │ │ └── userList.jsp │ │ └── web.xml # Web配置(可選,Spring Boot可省略) └── pom.xml # Maven依賴
各層職責說明
- 表現(xiàn)層(Controller):接收HTTP請求,參數(shù)校驗,調(diào)用Service并返回響應(yīng)
- 業(yè)務(wù)邏輯層(Service):處理業(yè)務(wù)邏輯,事務(wù)控制,調(diào)用Mapper層
- 持久層(Mapper):數(shù)據(jù)庫操作,與MyBatis框架交互
- 實體層(Entity):定義數(shù)據(jù)模型,與數(shù)據(jù)庫表映射
- 配置層(Config):Spring相關(guān)配置,如MVC配置、組件掃描等
3.2 核心依賴(pom.xml)
<!-- Spring MVC核心依賴 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.28</version>
</dependency>
<!-- MyBatis依賴 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- MyBatis整合Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.2</version>
</dependency>
<!-- MySQL驅(qū)動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- 數(shù)據(jù)庫連接池(HikariCP) -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
<!-- JSP依賴(若使用JSP視圖) -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!-- lombok(簡化實體類代碼) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<optional>true</optional>
</dependency>
<!-- JSON處理(Jackson) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
3.3 核心配置(application.properties)
# 數(shù)據(jù)庫配置(HikariCP) spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/spring_mvc_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456 # HikariCP連接池配置 spring.datasource.hikari.maximum-pool-size=10 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.idle-timeout=300000 # MyBatis配置 # Mapper XML文件路徑 mybatis.mapper-locations=classpath:mapper/*.xml # 實體類別名掃描包(簡化XML中的type配置) mybatis.type-aliases-package=com.example.entity # 開啟MyBatis日志(便于調(diào)試SQL) mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl # Spring MVC視圖解析器配置(JSP) spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp # 靜態(tài)資源訪問配置(CSS/JS/圖片) spring.mvc.static-path-pattern=/static/**
3.4 配置類(SpringMvcConfig.java)
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration // 標記為配置類
@EnableWebMvc // 開啟Spring MVC功能
@ComponentScan("com.example") // 掃描組件(Controller/Service等)
@MapperScan("com.example.mapper") // 掃描MyBatis Mapper接口
public class SpringMvcConfig implements WebMvcConfigurer {
// 配置視圖解析器(JSP)
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
// 配置靜態(tài)資源訪問(避免靜態(tài)資源被DispatcherServlet攔截)
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
// 配置JSON消息轉(zhuǎn)換器(自動注冊Jackson)
// Spring會自動注冊MappingJackson2HttpMessageConverter
}
3.5 功能測試:驗證三層架構(gòu)流程
3.5.1 數(shù)據(jù)庫表準備(MySQL)
CREATE DATABASE IF NOT EXISTS spring_mvc_db;
USE spring_mvc_db;
CREATE TABLE IF NOT EXISTS `user` (
`user_id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '用戶ID',
`username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用戶名',
`nickname` VARCHAR(50) DEFAULT '' COMMENT '昵稱',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用戶表';
-- 初始化測試數(shù)據(jù)
INSERT INTO `user`(username, nickname) VALUES
('admin', '管理員'),
('user1', '普通用戶1'),
('user2', '普通用戶2');
3.5.2 接口測試(Postman)
1. 查詢所有用戶(GET)
- 請求地址:
http://localhost:8080/spring-mvc-demo/user/list - 請求方式:GET
預(yù)期結(jié)果:
- 返回userList.jsp頁面
- 頁面中展示數(shù)據(jù)庫中的用戶列表
- 頁面包含用戶ID、用戶名、昵稱和創(chuàng)建時間信息
2. 根據(jù)ID查詢用戶(GET)
- 請求地址:
http://localhost:8080/spring-mvc-demo/user/1 - 請求方式:GET
- 請求頭:
Accept: application/json
預(yù)期結(jié)果:
{
"code": 200,
"msg": "success",
"data": {
"userId": 1,
"username": "admin",
"nickname": "管理員",
"createTime": "2024-05-20 10:30:00"
}
}
3. 新增用戶(POST)
- 請求地址:
http://localhost:8080/spring-mvc-demo/user/add - 請求方式:POST
- 請求頭:
Content-Type: application/json
請求體:
{
"username": "newuser",
"nickname": "新用戶"
}
預(yù)期結(jié)果:
{
"code": 200,
"msg": "添加成功",
"data": null
}
四、三層架構(gòu)常見問題與解決方案
4.1 表現(xiàn)層常見問題
問題 1:Controller 無法接收請求(404 錯誤)
可能原因分析:
請求路徑映射問題:
- 前端發(fā)送的請求路徑與后端
@RequestMapping注解定義的路徑不匹配 - 常見錯誤包括:大小寫不一致(如
/userInfovs/userinfo)、缺少或多余斜杠(如/api/uservs/api/user/) - 特殊字符編碼問題(如空格應(yīng)編碼為
%20)
Controller 配置問題:
- 類未添加
@Controller或@RestController注解
類未被Spring組件掃描到,可能原因:
@ComponentScan配置的包路徑不正確- Controller類所在的包不在主啟動類的同級或子級目錄下
- 使用了錯誤的掃描注解(如
@ServletComponentScan)
DispatcherServlet 配置問題:
web.xml中url-pattern配置為/*會攔截所有請求,包括靜態(tài)資源和JSP- 未正確配置靜態(tài)資源處理器
- 缺少必要的Servlet映射配置
解決方案及實施步驟:
路徑核對:
- 使用Postman等工具直接測試Controller接口
- 在Controller方法中添加日志輸出,確認請求是否到達
- 檢查是否有
@PathVariable參數(shù)但請求未提供
注解檢查:
@RestController // 或 @Controller
@RequestMapping("/api/users")
public class UserController {
// 確保方法上有@RequestMapping或其派生注解
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// ...
}
}
DispatcherServlet 配置:
推薦配置:
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern> <!-- 而不是 /* -->
</servlet-mapping>
Spring Boot中配置靜態(tài)資源:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
問題 2:參數(shù)綁定失敗(400 錯誤)
可能原因深度分析:
類型不匹配:
- 前端傳遞字符串"123",但后端使用
Integer接收 - 集合類型參數(shù)未正確格式化(如
List<String>需要?names=aa&names=bb)
日期格式化:
常見日期格式?jīng)_突:
- 前端:
"2024-05-20" - 后端期望:
"2024/05/20"或時間戳
時區(qū)問題(如UTC與本地時區(qū)差異)
命名不一致:
- 駝峰命名與下劃線命名轉(zhuǎn)換問題
- 嵌套對象屬性訪問(如
user.address.city)
完整解決方案:
基礎(chǔ)類型處理:
@GetMapping("/detail")
public Result detail(@RequestParam("user_id") Integer userId) {
// 明確指定參數(shù)名
}
日期處理最佳實踐:
@PostMapping("/schedule")
public Result createSchedule(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@RequestBody @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") Date endTime) {
// 分別處理URL參數(shù)和JSON體中的日期
}
復(fù)雜對象綁定:
// 實體類
public class User {
@JsonProperty("user_id") // 處理JSON字段名
private Long userId;
@RequestParam("user_name") // 處理URL參數(shù)名
private String userName;
}
// Controller
@PostMapping("/update")
public Result updateUser(@Valid User user) {
// 支持混合綁定方式
}
補充技巧:
全局日期格式配置(application.yml):
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
自定義參數(shù)解析器:實現(xiàn)HandlerMethodArgumentResolver處理特殊參數(shù)類型
4.2 業(yè)務(wù)邏輯層常見問題
問題 1:事務(wù)不生效(數(shù)據(jù)提交后未回滾)
詳細原因分析:
注解位置問題:
@Transactional注解在private/protected方法上無效- 注解被同類中的非事務(wù)方法調(diào)用
異常處理問題:
- 捕獲了異常但未重新拋出
- 拋出的異常類型不是
RuntimeException - 自定義異常未繼承
RuntimeException
代理機制問題:
- 使用
this.method()調(diào)用導(dǎo)致繞過Spring代理 - 特殊場景:異步方法、synchronized方法
完整解決方案:
正確的事務(wù)配置:
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void createOrder(OrderDTO dto) throws BusinessException {
// 業(yè)務(wù)代碼
}
}
解決自調(diào)用問題方案:
方案1:重構(gòu)代碼結(jié)構(gòu)
方案2:通過AopContext獲取代理對象
((OrderService) AopContext.currentProxy()).internalMethod();
方案3:使用@Autowired注入自身(需配合@Lazy)
事務(wù)調(diào)試技巧:
開啟事務(wù)日志:
logging.level.org.springframework.transaction.interceptor=DEBUG logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
問題 2:Service 層循環(huán)依賴(BeanCreationException)
典型場景分析:
直接循環(huán)依賴:
@Service
class AService { @Autowired BService b; }
@Service
class BService { @Autowired AService a; }
間接循環(huán)依賴: A → B → C → A
構(gòu)造器注入導(dǎo)致的不可解循環(huán):
@Service
class AService {
private final BService b;
public AService(BService b) { this.b = b; }
}
進階解決方案:
架構(gòu)層面重構(gòu):
- 提取公共邏輯到新的
CommonService - 使用門面模式封裝相關(guān)服務(wù)
技術(shù)解決方案:
// 方案1:使用setter注入 + @Lazy
@Service
class AService {
private BService b;
@Autowired
public void setB(@Lazy BService b) { this.b = b; }
}
// 方案2:使用ApplicationContext
@Service
class BService implements ApplicationContextAware {
private ApplicationContext context;
public void someMethod() {
AService a = context.getBean(AService.class);
}
}
Spring Boot 2.6+ 處理:
配置允許循環(huán)引用(不推薦):
spring.main.allow-circular-references=true
4.3 持久層常見問題
問題 1:Mapper 接口與 XML 不匹配(BindingException)
完整排查清單:
路徑匹配問題:
- 檢查XML文件是否在
resources目錄的對應(yīng)包路徑下 - Maven項目注意
src/main/resources和src/main/java的目錄結(jié)構(gòu)一致性
ID匹配問題:
- 方法重載導(dǎo)致混淆
- 泛型方法特殊處理
配置問題:
- MyBatis配置文件中
<mappers>配置錯誤 - Spring Boot中
mybatis.mapper-locations配置不完整
詳細解決方案:
項目結(jié)構(gòu)規(guī)范:
src/main/java
└─com/example/mapper
UserMapper.java
src/main/resources
└─com/example/mapper
UserMapper.xmlXML配置示例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectById" resultType="com.example.entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
Spring Boot配置:
# 確保掃描到Mapper接口 mybatis.mapper-locations=classpath*:mapper/**/*.xml # 開啟MyBatis日志 logging.level.com.example.mapper=DEBUG
問題 2:SQL 執(zhí)行報錯(SQLSyntaxErrorException)
深度排查指南:
SQL語法問題:
- 數(shù)據(jù)庫方言差異(MySQL vs Oracle)
- 保留關(guān)鍵字沖突(如使用
order作為表名) - 分頁語法差異
參數(shù)處理問題:
#{}和${}混用導(dǎo)致的語法錯誤- 參數(shù)類型不匹配(如字符串參數(shù)未加引號)
數(shù)據(jù)庫連接問題:
- 連接池配置不當
- 數(shù)據(jù)庫版本不兼容
- SSL連接配置錯誤
專業(yè)解決方案:
SQL調(diào)試技巧:
# 打印完整執(zhí)行的SQL(包括參數(shù)) mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
參數(shù)處理規(guī)范:
<!-- 正確用法 -->
<select id="findUsers" resultType="User">
SELECT * FROM user
WHERE username = #{name}
AND create_time > #{date,jdbcType=TIMESTAMP}
</select>
<!-- 動態(tài)表名用法(需確保安全) -->
<select id="selectByTable" resultType="map">
SELECT * FROM ${tableName}
WHERE id = #{id}
</select>
數(shù)據(jù)庫連接配置:
# 完整連接配置示例 spring.datasource.url=jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
高級技巧:
使用MyBatis-Plus的SQL注入器防止SQL錯誤
配置SQL執(zhí)行超時時間:
<select id="complexQuery" timeout="30">
<!-- 復(fù)雜查詢 -->
</select>
五、三層架構(gòu)優(yōu)化建議
5.1 代碼復(fù)用與解耦
統(tǒng)一異常處理
使用Spring提供的@ControllerAdvice+@ExceptionHandler實現(xiàn)全局異常處理機制,可以避免在各個Controller中重復(fù)編寫try-catch塊。這種集中式異常處理方式具有以下優(yōu)勢:
- 減少重復(fù)代碼,提高代碼整潔度
- 統(tǒng)一異常響應(yīng)格式,便于前端處理
- 可靈活分類處理不同類型的異常
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理業(yè)務(wù)異常
* @param e 業(yè)務(wù)異常對象
* @return 統(tǒng)一響應(yīng)結(jié)果
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
public Result<?> handleBusinessException(BusinessException e) {
log.error("業(yè)務(wù)異常:{}", e.getMessage(), e);
return Result.fail(e.getCode(), e.getMessage());
}
/**
* 處理系統(tǒng)異常
* @param e 異常對象
* @return 統(tǒng)一響應(yīng)結(jié)果
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Result<?> handleException(Exception e) {
log.error("系統(tǒng)異常:", e);
return Result.fail(500, "系統(tǒng)繁忙,請稍后再試");
}
}
通用Service/DAO層設(shè)計
通過抽取通用CRUD操作方法到基類中,可以大幅減少重復(fù)代碼。這種設(shè)計模式特別適合具有大量相似CRUD操作的系統(tǒng):
1.定義通用Mapper接口
public interface BaseMapper<T> {
int insert(T entity); // 插入單條記錄
int deleteById(@Param("id") Serializable id); // 根據(jù)主鍵刪除
int updateById(@Param("entity") T entity); // 根據(jù)主鍵更新
T selectById(@Param("id") Serializable id); // 根據(jù)主鍵查詢
List<T> selectList(@Param("entity") T entity); // 條件查詢列表
Page<T> selectPage(Page<T> page, @Param("entity") T entity); // 分頁查詢
}
2.業(yè)務(wù)Mapper繼承通用接口
public interface UserMapper extends BaseMapper<User> {
// 自定義方法
@Select("SELECT * FROM user WHERE username = #{username}")
User selectByUsername(@Param("username") String username);
// 復(fù)雜查詢示例
@Select("SELECT u.* FROM user u JOIN department d ON u.dept_id = d.id WHERE d.name = #{deptName}")
List<User> selectByDepartmentName(@Param("deptName") String deptName);
}
3.通用Service實現(xiàn)
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements BaseService<T> {
@Autowired
protected M baseMapper;
@Override
public boolean save(T entity) {
return baseMapper.insert(entity) > 0;
}
@Override
public boolean updateById(T entity) {
return baseMapper.updateById(entity) > 0;
}
// 其他通用方法實現(xiàn)...
}
5.2 性能優(yōu)化
緩存設(shè)計
在Service層合理使用緩存可以顯著提升系統(tǒng)性能,特別是對于讀多寫少的場景:
緩存使用場景:
- 高頻訪問的配置數(shù)據(jù)
- 用戶基礎(chǔ)信息
- 商品詳情等靜態(tài)數(shù)據(jù)
- 計算結(jié)果緩存
緩存實現(xiàn)示例:
@Service
public class UserServiceImpl implements UserService {
@Resource
private RedisTemplate<String, User> redisTemplate;
@Resource
private UserMapper userMapper;
// 緩存key前綴
private static final String USER_CACHE_PREFIX = "user:id:";
// 緩存過期時間(小時)
private static final long CACHE_EXPIRE_HOURS = 1;
@Override
@Transactional(readOnly = true)
public User getUserById(Integer userId) {
String key = USER_CACHE_PREFIX + userId;
// 1. 先查緩存
User user = redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 2. 緩存未命中,查數(shù)據(jù)庫
user = userMapper.selectById(userId);
if (user != null) {
// 3. 設(shè)置緩存
redisTemplate.opsForValue().set(
key,
user,
CACHE_EXPIRE_HOURS,
TimeUnit.HOURS
);
// 4. 異步更新用戶訪問記錄
CompletableFuture.runAsync(() ->
updateUserAccessTime(userId)
);
}
return user;
}
// 緩存一致性處理
@Override
@CacheEvict(key = "#user.id", condition = "#user.id != null")
public boolean updateUser(User user) {
return userMapper.updateById(user) > 0;
}
}
批量操作優(yōu)化
使用批量操作可以大幅減少數(shù)據(jù)庫交互次數(shù),提高性能:
1.MyBatis批量插入示例:
<!-- 批量插入用戶 -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO user (username, password, nickname, create_time)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(#{item.username}, #{item.password}, #{item.nickname},
<choose>
<when test="item.createTime != null">#{item.createTime}</when>
<otherwise>NOW()</otherwise>
</choose>)
</foreach>
</insert>
2.批量更新示例:
@Transactional
public int batchUpdateUserStatus(List<Integer> userIds, Integer status) {
return userMapper.batchUpdateStatus(userIds, status);
}
<!-- XML映射 -->
<update id="batchUpdateStatus">
UPDATE user SET status = #{status} WHERE id IN
<foreach collection="userIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
5.3 擴展性優(yōu)化
接口版本控制
良好的版本控制策略可以保證系統(tǒng)平滑升級:
1.URL路徑版本控制:
@RestController
@RequestMapping("/api/v1/user")
public class UserControllerV1 {
@GetMapping("/list")
public Result<List<User>> listUsers() {
// V1版本實現(xiàn)
}
}
@RestController
@RequestMapping("/api/v2/user")
public class UserControllerV2 {
@GetMapping("/list")
public Result<PageInfo<User>> listUsers() {
// V2版本實現(xiàn),返回分頁數(shù)據(jù)
}
}
2.請求頭版本控制:
@GetMapping("/user/list")
public Result<?> listUsers(@RequestHeader("X-API-Version") String version) {
if ("2.0".equals(version)) {
// 新版本邏輯
} else {
// 默認版本邏輯
}
}
多數(shù)據(jù)源支持
使用Spring的AbstractRoutingDataSource實現(xiàn)動態(tài)數(shù)據(jù)源切換:
1.配置多數(shù)據(jù)源:
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource dynamicDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
}
2.動態(tài)數(shù)據(jù)源路由:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
依賴注入優(yōu)化
使用構(gòu)造器注入可以避免循環(huán)依賴問題,提高代碼可測試性:
1.推薦做法:
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final RoleService roleService;
@Autowired
public UserServiceImpl(UserMapper userMapper, RoleService roleService) {
this.userMapper = userMapper;
this.roleService = roleService;
}
// 業(yè)務(wù)方法...
}
2.使用Lombok簡化代碼:
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final RoleService roleService;
// 自動生成構(gòu)造器,無需手動編寫
}
3.循環(huán)依賴解決方案:
// 使用@Lazy注解解決循環(huán)依賴
@Service
public class OrderServiceImpl implements OrderService {
private final UserService userService;
public OrderServiceImpl(@Lazy UserService userService) {
this.userService = userService;
}
}
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
JAVA實現(xiàn)經(jīng)典游戲坦克大戰(zhàn)的示例代碼
小時候大家都玩過坦克大戰(zhàn)吧,熟悉的旋律和豐富的關(guān)卡陪伴了我們一整個寒暑假。本文將通過Java+Swing實現(xiàn)這一經(jīng)典游戲,感興趣的可以學習一下2022-01-01
詳解在springmvc中解決FastJson循環(huán)引用的問題
本篇文章主要介紹了在springmvc中解決FastJson循環(huán)引用的問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01
SpringBoot中實現(xiàn)多線程6種方式總結(jié)
這篇文章主要為大家詳細介紹了在Spring Boot中實現(xiàn)多線程的6種方式,文章的示例代碼講解詳細,大家可以結(jié)合不同場景和需求進行靈活選擇2025-08-08
Retrofit+RxJava實現(xiàn)帶進度條的文件下載
這篇文章主要為大家詳細介紹了Retrofit+RxJava實現(xiàn)帶進度條的文件下載,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-06-06
SpringCloud hystrix服務(wù)降級學習筆記
什么是服務(wù)降級?當服務(wù)器壓力劇增的情況下,根據(jù)實際業(yè)務(wù)情況及流量,對一些服務(wù)和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務(wù)器資源以保證核心交易正常運作或高效運作2022-10-10

