问题
SpringMVC三层架构有什么好处?
答案
1. 核心概念
三层架构(Three-Tier Architecture)是一种经典的软件架构模式,将应用程序划分为三个逻辑层:
+-------------------+
| Controller层 | 表现层(Presentation Layer)
+-------------------+
↓
+-------------------+
| Service层 | 业务逻辑层(Business Logic Layer)
+-------------------+
↓
+-------------------+
| Dao层 | 数据访问层(Data Access Layer)
+-------------------+
↓
+-------------------+
| Database | 数据库
+-------------------+
层次职责:
- Controller层:接收HTTP请求,参数校验,调用Service,返回响应
- Service层:业务逻辑处理,事务控制,调用Dao
- Dao层:数据库访问,CRUD操作,SQL执行
2. 三层架构的详细职责
2.1 Controller层(表现层)
核心职责:
- 接收客户端请求(HTTP参数解析)
- 参数校验与数据绑定
- 调用Service层获取业务数据
- 封装响应数据并返回(JSON/XML/视图)
- 异常处理与统一响应
示例代码:
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
/**
* 创建用户
*/
@PostMapping
public Result<User> createUser(@RequestBody @Valid UserDTO userDTO) {
// 1. 参数校验(通过@Valid自动校验)
// 2. 调用Service层
User user = userService.createUser(userDTO);
// 3. 封装响应
return Result.success(user);
}
/**
* 分页查询用户
*/
@GetMapping
public Result<PageInfo<User>> listUsers(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
PageInfo<User> pageInfo = userService.listUsers(pageNum, pageSize);
return Result.success(pageInfo);
}
/**
* 根据ID查询用户
*/
@GetMapping("/{id}")
public Result<User> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user == null) {
return Result.error("用户不存在");
}
return Result.success(user);
}
/**
* 更新用户
*/
@PutMapping("/{id}")
public Result<Void> updateUser(@PathVariable Long id,
@RequestBody @Valid UserDTO userDTO) {
userService.updateUser(id, userDTO);
return Result.success();
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public Result<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return Result.success();
}
}
注意事项:
- ❌ 不应包含业务逻辑(如复杂计算、状态判断)
- ❌ 不应直接访问Dao层(必须通过Service)
- ✅ 保持轻量级,只做请求分发和响应封装
2.2 Service层(业务逻辑层)
核心职责:
- 核心业务逻辑处理
- 事务管理(@Transactional)
- 数据组装与转换
- 调用多个Dao完成复杂业务
- 业务异常处理
示例代码:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 创建用户(包含事务控制)
*/
@Transactional(rollbackFor = Exception.class)
@Override
public User createUser(UserDTO userDTO) {
// 1. 业务校验
if (userDao.existsByUsername(userDTO.getUsername())) {
throw new BusinessException("用户名已存在");
}
// 2. 数据转换与加密
User user = new User();
BeanUtils.copyProperties(userDTO, user);
user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
user.setCreateTime(new Date());
// 3. 保存用户
userDao.insert(user);
// 4. 分配默认角色
Role defaultRole = roleDao.selectByName("ROLE_USER");
userDao.insertUserRole(user.getId(), defaultRole.getId());
// 5. 发送欢迎邮件(异步)
emailService.sendWelcomeEmail(user.getEmail());
return user;
}
/**
* 分页查询用户
*/
@Override
public PageInfo<User> listUsers(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userDao.selectAll();
return new PageInfo<>(users);
}
/**
* 更新用户信息
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void updateUser(Long id, UserDTO userDTO) {
// 1. 检查用户是否存在
User user = userDao.selectById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 2. 检查用户名是否冲突
if (!user.getUsername().equals(userDTO.getUsername())) {
if (userDao.existsByUsername(userDTO.getUsername())) {
throw new BusinessException("用户名已被占用");
}
}
// 3. 更新数据
BeanUtils.copyProperties(userDTO, user);
user.setUpdateTime(new Date());
userDao.update(user);
}
/**
* 删除用户(逻辑删除)
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteUser(Long id) {
User user = userDao.selectById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 逻辑删除
user.setDeleted(true);
user.setDeleteTime(new Date());
userDao.update(user);
// 删除用户角色关联
userDao.deleteUserRoles(id);
}
}
注意事项:
- ✅ 事务控制在Service层(@Transactional)
- ✅ 可以调用多个Dao(如创建用户时操作user表和user_role表)
- ✅ 封装复杂的业务逻辑(如状态机、流程控制)
- ❌ 不应包含HTTP相关代码(如HttpServletRequest)
2.3 Dao层(数据访问层)
核心职责:
- 数据库CRUD操作
- SQL语句封装
- ORM映射(MyBatis、JPA)
- 单表或简单关联查询
示例代码:
// MyBatis方式
@Mapper
public interface UserDao {
/**
* 插入用户
*/
@Insert("INSERT INTO user(username, password, email, create_time) " +
"VALUES(#{username}, #{password}, #{email}, #{createTime})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
/**
* 根据ID查询
*/
@Select("SELECT * FROM user WHERE id = #{id} AND deleted = false")
User selectById(Long id);
/**
* 查询所有用户
*/
@Select("SELECT * FROM user WHERE deleted = false ORDER BY create_time DESC")
List<User> selectAll();
/**
* 更新用户
*/
@Update("UPDATE user SET username = #{username}, email = #{email}, " +
"update_time = #{updateTime} WHERE id = #{id}")
int update(User user);
/**
* 检查用户名是否存在
*/
@Select("SELECT COUNT(1) FROM user WHERE username = #{username} AND deleted = false")
boolean existsByUsername(String username);
/**
* 插入用户角色关联
*/
@Insert("INSERT INTO user_role(user_id, role_id) VALUES(#{userId}, #{roleId})")
int insertUserRole(@Param("userId") Long userId, @Param("roleId") Long roleId);
/**
* 删除用户角色关联
*/
@Delete("DELETE FROM user_role WHERE user_id = #{userId}")
int deleteUserRoles(Long userId);
}
JPA方式:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 自定义查询方法
User findByUsername(String username);
boolean existsByUsername(String username);
@Query("SELECT u FROM User u WHERE u.email = :email AND u.deleted = false")
User findByEmail(@Param("email") String email);
@Modifying
@Query("UPDATE User u SET u.deleted = true WHERE u.id = :id")
int logicDeleteById(@Param("id") Long id);
}
注意事项:
- ✅ 只负责数据访问,不包含业务逻辑
- ✅ 单一职责,一个Dao对应一个实体或表
- ❌ 不应调用其他Dao(复杂关联查询可通过SQL JOIN实现)
- ❌ 不应包含事务控制(事务在Service层)
3. 三层架构的核心优势
3.1 职责分离,降低耦合
好处:
- 每层专注自己的职责,代码清晰易懂
- 修改某层不影响其他层(如更换数据库只改Dao层)
- 团队协作高效(前端对接Controller,后端专注Service和Dao)
示例:
// 从MySQL迁移到MongoDB,只需修改Dao层
// Service层和Controller层无需改动
// 原MySQL实现
@Mapper
public interface UserDao {
User selectById(Long id);
}
// 新MongoDB实现
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private MongoTemplate mongoTemplate;
public User selectById(Long id) {
return mongoTemplate.findById(id, User.class);
}
}
3.2 复用性强
好处:
- Service层可被多个Controller调用
- Dao层可被多个Service调用
- 减少重复代码
示例:
// UserService被多个Controller复用
@RestController
@RequestMapping("/admin/users")
public class AdminUserController {
@Autowired
private UserService userService; // 复用UserService
@GetMapping
public Result<List<User>> listUsers() {
return Result.success(userService.listUsers(1, 100).getList());
}
}
@RestController
@RequestMapping("/api/users")
public class ApiUserController {
@Autowired
private UserService userService; // 复用UserService
@GetMapping("/profile")
public Result<User> getProfile() {
Long userId = getCurrentUserId();
return Result.success(userService.getUserById(userId));
}
}
3.3 易于测试
好处:
- 每层可独立单元测试
- Mock依赖,隔离测试环境
- 提高测试覆盖率
示例:
// Service层单元测试
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@InjectMocks
private UserServiceImpl userService;
@Mock
private UserDao userDao;
@Mock
private PasswordEncoder passwordEncoder;
@Test
public void testCreateUser_success() {
// 1. 准备测试数据
UserDTO userDTO = new UserDTO();
userDTO.setUsername("testuser");
userDTO.setPassword("123456");
// 2. Mock Dao层行为
when(userDao.existsByUsername(anyString())).thenReturn(false);
when(passwordEncoder.encode(anyString())).thenReturn("encrypted");
when(userDao.insert(any(User.class))).thenReturn(1);
// 3. 执行测试
User user = userService.createUser(userDTO);
// 4. 验证结果
assertNotNull(user);
assertEquals("testuser", user.getUsername());
verify(userDao, times(1)).insert(any(User.class));
}
@Test(expected = BusinessException.class)
public void testCreateUser_duplicateUsername() {
UserDTO userDTO = new UserDTO();
userDTO.setUsername("existuser");
// Mock用户名已存在
when(userDao.existsByUsername("existuser")).thenReturn(true);
// 执行测试,期望抛出异常
userService.createUser(userDTO);
}
}
3.4 便于维护和扩展
好处:
- 新增功能时只需扩展对应层
- 修改业务逻辑不影响数据访问
- 符合开闭原则(对扩展开放,对修改关闭)
示例:
// 新增导出功能,扩展Controller层
@GetMapping("/export")
public void exportUsers(HttpServletResponse response) throws IOException {
List<User> users = userService.listUsers(1, 1000).getList();
// 导出为Excel
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; filename=users.xlsx");
ExcelWriter writer = EasyExcel.write(response.getOutputStream(), User.class).build();
writer.write(users);
writer.finish();
}
3.5 事务管理清晰
好处:
- 事务边界明确(Service层)
- 避免事务泄漏或嵌套事务问题
- 便于控制事务粒度
示例:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductDao productDao;
@Autowired
private InventoryService inventoryService;
/**
* 创建订单(事务控制)
*/
@Transactional(rollbackFor = Exception.class)
@Override
public Order createOrder(OrderDTO orderDTO) {
// 1. 创建订单
Order order = new Order();
orderDao.insert(order);
// 2. 扣减库存
for (OrderItem item : orderDTO.getItems()) {
inventoryService.deductStock(item.getProductId(), item.getQuantity());
}
// 3. 更新商品销量
productDao.incrementSales(orderDTO.getItems());
return order;
// 事务在方法结束时自动提交,异常时自动回滚
}
}
4. 三层架构最佳实践
4.1 DTO与实体分离
// DTO:用于接收前端参数
@Data
public class UserDTO {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度6-20位")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
}
// Entity:对应数据库表
@Data
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String email;
private Date createTime;
private Date updateTime;
private Boolean deleted;
}
// VO:返回给前端的数据(隐藏敏感字段)
@Data
public class UserVO {
private Long id;
private String username;
private String email;
// 不包含password字段
}
4.2 统一异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
return Result.error(e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.error(message);
}
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("系统繁忙,请稍后重试");
}
}
4.3 统一响应格式
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
}
5. 面试答题总结
标准回答模板:
SpringMVC三层架构将应用划分为 Controller(表现层)、Service(业务逻辑层)、Dao(数据访问层),主要好处包括:
- 职责分离,降低耦合:每层专注自己的职责,修改某层不影响其他层
- 复用性强:Service层可被多个Controller调用,Dao层可被多个Service调用
- 易于测试:每层可独立单元测试,通过Mock隔离依赖
- 便于维护和扩展:新增功能只需扩展对应层,符合开闭原则
- 事务管理清晰:事务边界在Service层,避免事务泄漏
职责划分:
- Controller:接收请求、参数校验、调用Service、返回响应
- Service:业务逻辑、事务控制、调用Dao、数据组装
- Dao:数据库CRUD、SQL封装、ORM映射
最佳实践:
- DTO与实体分离(UserDTO接收参数,User对应数据库,UserVO返回前端)
- 统一异常处理(@RestControllerAdvice)
- 统一响应格式(Result
)
常见追问:
- Controller层可以直接调用Dao吗? → 不可以,必须通过Service层,否则事务控制和业务逻辑无法统一管理
- 事务为什么放在Service层? → 业务逻辑可能调用多个Dao,事务边界应覆盖整个业务操作
- 三层架构和MVC模式的区别? → MVC是表现层模式(Model-View-Controller),三层架构是整体架构(包含业务层和数据层)