当前位置: 代码网 > it编程>编程语言>Java > Spring MVC的三层架构使用及解析

Spring MVC的三层架构使用及解析

2025年09月26日 Java 我要评论
一、spring mvc 三层架构概述在传统的 java web 开发中(如 servlet+jsp),代码往往混杂在一起:数据处理、页面交互、业务逻辑全部写在 servlet 中,导致项目维护困难、

一、spring mvc 三层架构概述

在传统的 java web 开发中(如 servlet+jsp),代码往往混杂在一起:数据处理、页面交互、业务逻辑全部写在 servlet 中,导致项目维护困难、扩展性差。

这种开发模式存在以下典型问题:

  • 单个servlet文件可能包含数百行代码,混合处理业务逻辑和视图渲染
  • 数据库操作直接嵌入业务逻辑中,难以更换数据源
  • 代码复用率低,相似功能需要重复实现
  • 单元测试困难,各功能模块耦合度过高

spring mvc 的三层架构正是为解决这些问题而生,通过职责拆分,将系统分为三个核心层级,每层专注于特定功能,既降低了耦合度,又提升了代码的可复用性和可维护性。

这种分层架构模式借鉴了企业级应用开发的成熟经验,并针对web应用场景进行了优化。

1.1 三层架构的核心定义

spring mvc 的三层架构并非独立存在,而是相互协作、自上而下的调用关系,具体包括:

表现层(presentation layer)
  • 直接与用户交互,负责接收请求、返回响应

主要功能包括:

  • 接收http请求参数并进行基本校验
  • 调用service层处理业务逻辑
  • 返回响应(页面渲染或json数据)
  • 核心组件:spring mvc的controller

典型实现示例:

@restcontroller
@requestmapping("/users")
public class usercontroller {
    @autowired
    private userservice userservice;
    
    @getmapping("/{id}")
    public user getuser(@pathvariable long id) {
        return userservice.getuserbyid(id);
    }
}
业务逻辑层(business logic layer,简称service层)
  • 处理核心业务逻辑的中枢

主要职责包括:

  • 复杂业务规则的实现
  • 数据有效性校验
  • 事务管理(通过@transactional注解)
  • 异常处理
  • 组合多个数据操作完成业务功能

典型实现示例:

@service
public class userserviceimpl implements userservice {
    @autowired
    private usermapper usermapper;
    
    @transactional
    @override
    public user createuser(userdto userdto) {
        // 业务校验
        if(usermapper.existsbyusername(userdto.getusername())) {
            throw new businessexception("用户名已存在");
        }
        
        // 数据转换
        user user = new user();
        beanutils.copyproperties(userdto, user);
        
        // 持久化操作
        usermapper.insert(user);
        return user;
    }
}
持久层(persistence layer)
  • 专注于数据持久化操作

主要特点:

  • 只关心"如何访问数据",不关心业务逻辑
  • 提供标准的crud操作方法
  • 支持多种持久化技术(jdbc、mybatis、jpa等)

核心组件:

  • dao(data access object)或repository
  • mybatis的mapper接口

典型实现示例:

@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 三层架构的调用流程

请求接收阶段

  • 用户通过浏览器访问/users/123
  • http请求被spring mvc的前端控制器dispatcherservlet拦截
  • dispatcherservlet查找所有已注册的handlermapping

请求路由阶段

  • handlermapping根据url路径/users/123匹配到usercontroller的getuser方法
  • 将路径变量123解析为方法参数id

业务处理阶段

  • controller调用userservice的getuserbyid方法

service层可能执行以下操作:

  • 参数校验(如id有效性)
  • 业务规则判断(如权限检查)
  • 调用持久层获取数据

数据访问阶段

  • service调用usermapper的selectbyid方法
  • mybatis执行sql:select * from users where id = 123
  • 将查询结果映射为user对象返回

响应返回阶段

  • 查询结果沿调用链返回:mapper → service → controller
  • controller将user对象转换为json格式
  • dispatcherservlet将响应写入httpservletresponse

视图渲染阶段(可选)

如果返回的是视图(如jsp):

  • dispatcherservlet将modelandview交给viewresolver
  • viewresolver解析视图名称,定位具体的jsp文件
  • 视图引擎渲染jsp,生成html响应

三层架构交互时序图示例

client → dispatcherservlet → controller → service → mapper → db
                                   ↑                |
                                   |                ↓
client ← dispatcherservlet ← controller ← service ← mapper

这种分层架构使得系统各部分的职责更加清晰,便于团队协作开发、单元测试和后期维护。例如:

  • 前端开发人员只需要关注controller层的接口定义
  • 业务分析师可以基于service层的代码理解业务规则
  • dba可以优化mapper层的sql语句而不影响业务逻辑

二、各层详细拆解

2.1 表现层(controller 层):用户交互的 "入口"

表现层是 spring mvc 框架中与用户直接交互的层级,作为系统的"门面",负责处理 http 请求和响应。它的核心组件是 controller 类,主要职责包括:

  1. 接收客户端请求(解析请求参数、请求头等)
  2. 进行基础参数校验
  3. 调用 service 层处理业务逻辑
  4. 组装响应数据并返回给客户端
  5. 处理异常情况并返回友好错误信息

2.1.1 核心组件与注解详解

@controller

  • 作用:标识一个类作为 spring mvc 的控制器
  • 实现原理:spring 会在启动时扫描带有该注解的类,并将其注册为 spring bean
  • 配套机制:配合组件扫描注解 @componentscan 使用

示例:

@controller
public class homecontroller {
    // 控制器方法...
}

@requestmapping

核心功能:

  • 路径映射:将 http 请求映射到控制器方法
  • 请求方法限定:通过 method 属性指定处理的 http 方法
  • 参数匹配:通过 params 属性匹配特定请求参数
  • 头部匹配:通过 headers 属性匹配特定请求头

高级用法:

  • 通配符支持:如 "/user/*" 可匹配 "/user/123" 等路径
  • 类级别与方法级别组合:类级别定义基础路径,方法级别定义具体路径
  • 媒体类型限定:通过 produces/consumes 指定处理的内容类型

示例:

@requestmapping(value = "/products", method = requestmethod.get)
public string listproducts() {...}

@getmapping/@postmapping

优点:

  • 代码更简洁:相比 @requestmapping(method = requestmethod.get) 更易读
  • 语义更明确:直接表明处理的 http 方法
  • 支持继承:可以组合使用 @requestmapping 的类级别注解

示例对比:

// 传统方式
@requestmapping(value = "/user", method = requestmethod.get)

// 简化方式
@getmapping("/user")

@requestparam

主要参数:

  • name/value:指定绑定的请求参数名称
  • required:是否为必须参数(默认 true)
  • defaultvalue:参数默认值

使用场景:

  • 处理查询参数:如 ?page=1&size=10
  • 处理表单数据:如 application/x-www-form-urlencoded

示例:

@getmapping("/search")
public string search(@requestparam(name = "keyword", required = false, defaultvalue = "") string keyword) {...}

@pathvariable

特点:

  • 用于 restful 风格的 url 参数获取
  • 支持正则表达式匹配路径变量
  • 可配合 @requestmapping 的通配符使用

示例:

@getmapping("/users/{userid}/orders/{orderid}")
public string getorder(@pathvariable long userid, @pathvariable string orderid) {...}

@responsebody

工作机制:

  • 通过 httpmessageconverter 将返回值转换为指定格式
  • 常用转换器:mappingjackson2httpmessageconverter(json)
  • 可自定义转换器处理特殊格式

典型应用:

  • 前后端分离架构中的 api 接口
  • ajax 请求响应
  • 移动端接口开发

示例:

@responsebody
@getmapping("/api/user/{id}")
public user getuser(@pathvariable long id) {...}

@restcontroller

组合优势:

  • 减少样板代码:无需在每个方法上添加 @responsebody
  • 语义更清晰:明确表示该类是纯 api 控制器
  • 自动配置:默认启用 json 序列化

实现原理:

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@documented
@controller
@responsebody
public @interface restcontroller {...}

2.1.2 示例:一个完整的 controller 实现

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  // 启用方法参数验证
public class usercontroller {
    
    private final userservice userservice;
    
    // 推荐使用构造器注入
    public usercontroller(userservice userservice) {
        this.userservice = userservice;
    }

    /**
     * 用户列表页面
     * @param page 页码(从1开始)
     * @param size 每页条数
     * @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 统一响应结果
     */
    @getmapping("/{userid}")
    @responsebody
    public result<user> getuserdetail(@pathvariable @min(1) long userid) {
        user user = userservice.getuserbyid(userid);
        return result.success(user);
    }

    /**
     * 创建新用户
     * @param userdto 用户数据
     * @return 创建结果
     */
    @postmapping
    @responsebody
    public result<long> createuser(@requestbody @valid userdto userdto) {
        long userid = userservice.createuser(userdto);
        return result.success(userid);
    }
}

2.1.3 最佳实践与注意事项

职责分离原则

controller 应保持"瘦身",仅处理:

  • 请求/响应转换
  • 基础参数验证
  • 异常捕获

所有业务逻辑应委托给 service 层

参数校验建议

使用 jsr-380 规范注解:

  • @notnull/@notempty/@notblank
  • @size(min=, max=)
  • @pattern(regexp=)
  • @min/@max

分组校验:通过 groups 属性实现不同场景的校验规则

自定义校验:实现 constraintvalidator 接口

统一响应格式

推荐结构:

public class result<t> {
    private int code;    // 状态码
    private string msg;  // 消息
    private t data;      // 数据
    private long timestamp = system.currenttimemillis();
    // 构造方法、静态工厂方法等...
}

使用示例:

@getmapping("/{id}")
public result<user> getuser(@pathvariable long id) {
    user user = userservice.getuserbyid(id);
    return result.success(user);
}

异常处理

推荐使用 @controlleradvice 统一处理异常

示例:

@controlleradvice
public class globalexceptionhandler {
    
    @exceptionhandler(businessexception.class)
    @responsebody
    public result<?> handlebusinessexception(businessexception e) {
        return result.fail(e.geterrorcode(), e.getmessage());
    }
}

性能考虑

避免在 controller 中进行:

  • 复杂计算
  • 数据库操作
  • 耗时 i/o 操作

使用异步处理:@async、deferredresult 等

安全建议

  • 对敏感参数进行过滤
  • 重要操作添加权限校验
  • 防止 csrf 攻击
  • 输入参数进行 xss 防护

测试建议

  • 使用 mockmvc 进行单元测试

测试用例应覆盖:

  • 正常流程
  • 参数校验失败情况
  • 异常情况处理
  • 边界条件

2.2 业务逻辑层(service 层):系统的 "大脑"

service 层是整个系统的核心业务处理中枢,负责实现业务规则、处理事务、协调多个持久层操作,是表现层与持久层之间的 "桥梁"。

它相当于应用系统的"大脑",负责处理复杂的业务逻辑,确保业务流程的正确性和数据一致性。

2.2.1 核心组件与注解

@service 注解
  • 作用:标记一个类为 service 层组件,spring 会自动扫描并注册为 bean,供 controller 层注入
  • 使用场景:所有业务逻辑处理类都应该使用此注解

示例

@service
public class orderserviceimpl implements orderservice {...}
@transactional 注解
  • 作用:声明事务管理,可用于类或方法上,指定事务的各种属性

常用参数

  • propagation:事务传播行为(如required, requires_new)
  • isolation:事务隔离级别(如default, read_committed)
  • rollbackfor:指定哪些异常需要回滚
  • timeout:事务超时时间

示例

@transactional(propagation = propagation.required, 
             isolation = isolation.default,
             timeout = 30,
             rollbackfor = exception.class)
接口与实现类设计

优势

  • 面向接口编程,便于后续扩展
  • 方便进行单元测试(可mock接口)
  • 实现多态特性

典型结构

├── service
│   ├── userservice.java          // 接口
│   └── impl
│       └── userserviceimpl.java  // 实现类

2.2.2 示例:service 接口与实现类

1. service接口设计
/**
 * 用户服务接口
 * 定义业务契约,不包含具体实现
 */
public interface userservice {

    /**
     * 查询所有用户
     * @return 用户列表(可能为空列表)
     */
    list<user> getallusers();

    /**
     * 根据id查询用户
     * @param userid 用户id
     * @return 用户实体
     * @throws businessexception 当id无效时抛出
     */
    user getuserbyid(integer userid) throws businessexception;

    /**
     * 新增用户
     * @param user 用户实体
     * @return 操作是否成功
     * @throws businessexception 当用户名已存在等业务异常时抛出
     */
    boolean adduser(user user) throws businessexception;
    
    /**
     * 批量导入用户
     * @param users 用户列表
     * @return 成功导入的数量
     */
    @transactional
    int batchimportusers(list<user> users);
}
2. service实现类详解
import org.springframework.stereotype.service;
import org.springframework.transaction.annotation.transactional;
import javax.annotation.resource;

/**
 * 用户服务实现类
 * 实现具体的业务逻辑
 */
@service // 标记为service组件
public class userserviceimpl implements userservice {

    // 使用@resource或@autowired注入持久层组件
    @resource
    private usermapper usermapper;
    
    @resource
    private roleservice roleservice;
    
    @resource
    private logservice logservice;

    // 简单查询操作通常不需要事务
    @override
    public list<user> getallusers() {
        // 直接调用mapper层方法获取数据
        // 可添加缓存逻辑提升性能
        return usermapper.selectall();
    }

    @override
    public user getuserbyid(integer userid) throws businessexception {
        // 业务校验
        if (userid == null || userid <= 0) {
            throw new businessexception(errorcode.invalid_user_id, "用户id无效");
        }
        
        // 查询用户
        user user = usermapper.selectbyid(userid);
        
        // 业务处理:如果用户不存在
        if (user == null) {
            throw new businessexception(errorcode.user_not_found, "用户不存在");
        }
        
        return user;
    }

    // 需要事务管理的业务方法
    @override
    @transactional(rollbackfor = exception.class)
    public boolean adduser(user user) throws businessexception {
        // 1. 参数校验
        if (user == null || stringutils.isempty(user.getusername())) {
            throw new businessexception(errorcode.invalid_param, "用户信息不完整");
        }
        
        // 2. 业务校验(用户名唯一性检查)
        user existinguser = usermapper.selectbyusername(user.getusername());
        if (existinguser != null) {
            throw new businessexception(errorcode.username_exists, "用户名已存在");
        }
        
        // 3. 设置默认值等业务处理
        user.setcreatetime(new date());
        user.setstatus(1); // 默认激活状态
        
        // 4. 调用mapper层保存数据
        int rows = usermapper.insert(user);
        
        // 5. 关联操作(如分配默认角色)
        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) {
                // 记录导入失败的用户
                log.warn("导入用户失败: {}", user.getusername(), e);
            }
        }
        return successcount;
    }
}

2.2.3 开发注意事项

职责划分原则

  • service层应专注于业务逻辑处理
  • 所有数据库操作必须通过mapper/dao层完成
  • 避免在service层直接编写sql语句

事务管理最佳实践

  • 事务注解应加在service层而非controller层
  • 默认情况下只对runtimeexception回滚,建议明确指定rollbackfor
  • 只读操作使用@transactional(readonly = true)提升性能
  • 避免在同一个类中自调用事务方法(因代理机制会失效)

异常处理规范

// 自定义业务异常示例
public class businessexception extends runtimeexception {
    private string errorcode;
    
    public businessexception(string errorcode, string message) {
        super(message);
        this.errorcode = errorcode;
    }
    
    // getter方法...
}
  • 使用自定义业务异常替代runtimeexception
  • 不同业务错误定义不同的错误码
  • 在controller层统一处理业务异常

性能优化建议

  • 复杂查询考虑添加缓存
  • 批量操作使用批量处理方法
  • 耗时操作考虑异步处理

测试考量

  • service层应易于单元测试
  • 使用mock对象隔离依赖
  • 测试应覆盖各种业务场景和异常分支

典型业务场景处理

  • 分布式事务:对于跨服务调用,考虑使用seata等分布式事务解决方案
  • 幂等性处理:对于支付等关键业务,需要实现幂等性控制
  • 业务流水号:重要业务操作应生成唯一业务流水号便于追踪

2.3 持久层(mapper/dao 层):数据的 "搬运工"

持久层作为应用程序与数据库之间的桥梁,专注于数据的持久化操作。其主要职责包括:

  1. 执行基本的 crud 操作(create/read/update/delete)
  2. 处理数据库事务
  3. 实现数据缓存(可选)
  4. 进行数据校验(基础级别)

与业务层不同,持久层不关心业务逻辑,只关注"数据如何存储和获取"。在现代 java web 开发中,mybatis 已成为最流行的持久层框架之一,相比传统的 jdbc 和 dao 模式,它具有以下优势:

  • 简化了数据库操作
  • 提供自动的对象关系映射(orm)
  • 支持动态sql
  • 具有更好的性能

2.3.1 核心组件与配置

1. mapper 接口

mapper 接口是 mybatis 的核心概念,它替代了传统的 dao 接口。与 dao 不同:

  • 不需要编写实现类
  • mybatis 通过动态代理技术自动生成实现
  • 方法签名直接对应 sql 操作
// 典型 mapper 接口定义
@mapper
public interface usermapper {
    // 查询方法
    user selectbyid(long id);
    
    // 插入方法
    int insert(user user);
    
    // 更新方法
    int update(user user);
    
    // 删除方法
    int deletebyid(long id);
}
2. 关键注解
  • @mapper:标记接口为 mybatis mapper 接口
  • @mapperscan:在 spring boot 启动类或配置类上使用,指定 mapper 接口的扫描路径
  • @param:用于多参数方法,指定参数名称
3. xml 映射文件

xml 文件是 sql 语句的主要存放位置,通常包含:

  • 结果映射定义(<resultmap>)
  • sql 查询语句(<select>)
  • 插入语句(<insert>)
  • 更新语句(<update>)
  • 删除语句(<delete>)

2.3.2 示例详解

1. mapper 接口增强版
@mapper
public interface usermapper {
    // 基础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">
    
    <!-- 高级结果映射 -->
    <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"/>
        <!-- 关联映射 -->
        <association property="department" javatype="department">
            <id column="dept_id" property="id"/>
            <result column="dept_name" property="name"/>
        </association>
    </resultmap>

    <!-- 动态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 高级特性与最佳实践

动态sql

  • 使用<if>, <choose>, <when>, <otherwise>标签实现条件判断
  • <where>标签自动处理where子句
  • <set>标签用于update语句

关联查询

  • 一对一:<association>
  • 一对多:<collection>
  • 延迟加载:配置lazyloadingenabled=true

性能优化

  • 使用二级缓存(需谨慎)
  • 批量操作代替单条操作
  • 合理使用延迟加载

事务管理

  • 在service层使用@transactional注解
  • 配置适当的事务传播行为

分页实现

  • 使用pagehelper插件
  • 手动实现分页(limit offset)

2.3.4 常见问题解决方案

参数映射问题

  • 简单类型参数:直接使用#{param}
  • 对象参数:使用#{propertyname}
  • map参数:使用#{key}
  • 多参数:必须使用@param注解

结果映射问题

  • 字段名与属性名不一致时使用<resultmap>
  • 复杂类型使用嵌套映射
  • 使用<constructor>进行构造函数映射

sql注入防护

  • 永远使用#{}而不是${}进行参数绑定
  • 对用户输入进行严格校验

性能监控

  • 配置sql日志输出
  • 使用mybatis-plus的性能分析插件
  • 监控慢sql

通过以上规范和最佳实践,可以构建出高效、可维护的持久层,为应用程序提供可靠的数据访问支持。

三、基于三层架构搭建 spring mvc 项目

3.1 项目结构(maven)

一个标准的 spring mvc 三层架构项目结构如下(以 intellij idea 为例):

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           ├── controller/       # 表现层(controller)
│   │           │   └── usercontroller.java
│   │           ├── service/          # 业务逻辑层(service)
│   │           │   ├── userservice.java      # 接口
│   │           │   └── impl/
│   │           │       └── userserviceimpl.java  # 实现类
│   │           ├── mapper/           # 持久层(mapper)
│   │           │   └── usermapper.java
│   │           ├── entity/           # 实体类(对应数据库表)
│   │           │   └── user.java
│   │           ├── exception/        # 自定义异常
│   │           │   └── businessexception.java
│   │           ├── config/           # spring配置类
│   │           │   └── springmvcconfig.java
│   │           └── util/             # 工具类
│   │               └── result.java   # 统一响应封装
│   ├── resources/
│   │   ├── mapper/                   # mapper xml文件
│   │   │   └── usermapper.xml
│   │   ├── application.properties    # 全局配置(数据库、mybatis等)
│   │   └── static/                   # 静态资源(css、js、图片)
│   └── webapp/                       # web资源
│       ├── web-inf/
│       │   ├── views/                # 视图页面(jsp/html)
│       │   │   └── userlist.jsp
│       │   └── web.xml               # web配置(可选,spring boot可省略)
└── pom.xml                           # maven依赖

各层职责说明

  • 表现层(controller):接收http请求,参数校验,调用service并返回响应
  • 业务逻辑层(service):处理业务逻辑,事务控制,调用mapper层
  • 持久层(mapper):数据库操作,与mybatis框架交互
  • 实体层(entity):定义数据模型,与数据库表映射
  • 配置层(config):spring相关配置,如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驱动 -->
<dependency>
    <groupid>mysql</groupid>
    <artifactid>mysql-connector-java</artifactid>
    <version>8.0.33</version>
    <scope>runtime</scope>
</dependency>

<!-- 数据库连接池(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)

# 数据库配置(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日志(便于调试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

# 静态资源访问配置(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");
    }

    // 配置静态资源访问(避免静态资源被dispatcherservlet拦截)
    @override
    public void addresourcehandlers(resourcehandlerregistry registry) {
        registry.addresourcehandler("/static/**")
                .addresourcelocations("classpath:/static/");
    }
    
    // 配置json消息转换器(自动注册jackson)
    // spring会自动注册mappingjackson2httpmessageconverter
}

3.5 功能测试:验证三层架构流程

3.5.1 数据库表准备(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 '创建时间'
) engine=innodb default charset=utf8mb4 comment '用户表';

-- 初始化测试数据
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

预期结果

  • 返回userlist.jsp页面
  • 页面中展示数据库中的用户列表
  • 页面包含用户id、用户名、昵称和创建时间信息
2. 根据id查询用户(get)
  • 请求地址http://localhost:8080/spring-mvc-demo/user/1
  • 请求方式:get
  • 请求头accept: application/json

预期结果

{
  "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": "新用户"
}

预期结果

{
  "code": 200,
  "msg": "添加成功",
  "data": null
}

四、三层架构常见问题与解决方案

4.1 表现层常见问题

问题 1:controller 无法接收请求(404 错误)

可能原因分析:

请求路径映射问题

  • 前端发送的请求路径与后端@requestmapping注解定义的路径不匹配
  • 常见错误包括:大小写不一致(如/userinfo vs /userinfo)、缺少或多余斜杠(如/api/user vs /api/user/
  • 特殊字符编码问题(如空格应编码为%20

controller 配置问题

  • 类未添加@controller@restcontroller注解

类未被spring组件扫描到,可能原因:

  • @componentscan配置的包路径不正确
  • controller类所在的包不在主启动类的同级或子级目录下
  • 使用了错误的扫描注解(如@servletcomponentscan

dispatcherservlet 配置问题

  • web.xmlurl-pattern配置为/*会拦截所有请求,包括静态资源和jsp
  • 未正确配置静态资源处理器
  • 缺少必要的servlet映射配置

解决方案及实施步骤:

路径核对

  • 使用postman等工具直接测试controller接口
  • 在controller方法中添加日志输出,确认请求是否到达
  • 检查是否有@pathvariable参数但请求未提供

注解检查

@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中配置静态资源:

@override
public void addresourcehandlers(resourcehandlerregistry registry) {
    registry.addresourcehandler("/static/**")
           .addresourcelocations("classpath:/static/");
}

问题 2:参数绑定失败(400 错误)

可能原因深度分析:

类型不匹配

  • 前端传递字符串"123",但后端使用integer接收
  • 集合类型参数未正确格式化(如list<string>需要?names=aa&names=bb

日期格式化

常见日期格式冲突:

  • 前端:"2024-05-20"
  • 后端期望:"2024/05/20"或时间戳

时区问题(如utc与本地时区差异)

命名不一致

  • 驼峰命名与下划线命名转换问题
  • 嵌套对象属性访问(如user.address.city

完整解决方案:

基础类型处理

@getmapping("/detail")
public result detail(@requestparam("user_id") integer userid) {
    // 明确指定参数名
}

日期处理最佳实践

@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参数和json体中的日期
}

复杂对象绑定

// 实体类
public class user {
    @jsonproperty("user_id")  // 处理json字段名
    private long userid;
    
    @requestparam("user_name")  // 处理url参数名
    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

自定义参数解析器:实现handlermethodargumentresolver处理特殊参数类型

4.2 业务逻辑层常见问题

问题 1:事务不生效(数据提交后未回滚)

详细原因分析:

注解位置问题

  • @transactional注解在private/protected方法上无效
  • 注解被同类中的非事务方法调用

异常处理问题

  • 捕获了异常但未重新抛出
  • 抛出的异常类型不是runtimeexception
  • 自定义异常未继承runtimeexception

代理机制问题

  • 使用this.method()调用导致绕过spring代理
  • 特殊场景:异步方法、synchronized方法

完整解决方案:

正确的事务配置

@service
public class orderservice {
    @transactional(rollbackfor = exception.class, propagation = propagation.required)
    public void createorder(orderdto dto) throws businessexception {
        // 业务代码
    }
}

解决自调用问题方案

方案1:重构代码结构

方案2:通过aopcontext获取代理对象

((orderservice) aopcontext.currentproxy()).internalmethod();

方案3:使用@autowired注入自身(需配合@lazy

事务调试技巧

开启事务日志:

logging.level.org.springframework.transaction.interceptor=debug
logging.level.org.springframework.jdbc.datasource.datasourcetransactionmanager=debug

问题 2:service 层循环依赖(beancreationexception)

典型场景分析:

直接循环依赖

@service
class aservice { @autowired bservice b; }

@service
class bservice { @autowired aservice a; }

间接循环依赖: a → b → c → a

构造器注入导致的不可解循环

@service
class aservice {
    private final bservice b;
    public aservice(bservice b) { this.b = b; }
}

进阶解决方案:

架构层面重构

  • 提取公共逻辑到新的commonservice
  • 使用门面模式封装相关服务

技术解决方案

// 方案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+ 处理

配置允许循环引用(不推荐):

spring.main.allow-circular-references=true

4.3 持久层常见问题

问题 1:mapper 接口与 xml 不匹配(bindingexception)

完整排查清单:

路径匹配问题

  • 检查xml文件是否在resources目录的对应包路径下
  • maven项目注意src/main/resourcessrc/main/java的目录结构一致性

id匹配问题

  • 方法重载导致混淆
  • 泛型方法特殊处理

配置问题

  • mybatis配置文件中<mappers>配置错误
  • spring boot中mybatis.mapper-locations配置不完整

详细解决方案:

项目结构规范

src/main/java
  └─com/example/mapper
        usermapper.java
src/main/resources
  └─com/example/mapper
        usermapper.xml

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">
    <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 执行报错(sqlsyntaxerrorexception)

深度排查指南:

sql语法问题

  • 数据库方言差异(mysql vs oracle)
  • 保留关键字冲突(如使用order作为表名)
  • 分页语法差异

参数处理问题

  • #{}${}混用导致的语法错误
  • 参数类型不匹配(如字符串参数未加引号)

数据库连接问题

  • 连接池配置不当
  • 数据库版本不兼容
  • ssl连接配置错误

专业解决方案:

sql调试技巧

# 打印完整执行的sql(包括参数)
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.stdoutimpl

参数处理规范

<!-- 正确用法 -->
<select id="findusers" resulttype="user">
    select * from user 
    where username = #{name} 
    and create_time > #{date,jdbctype=timestamp}
</select>

<!-- 动态表名用法(需确保安全) -->
<select id="selectbytable" resulttype="map">
    select * from ${tablename} 
    where id = #{id}
</select>

数据库连接配置

# 完整连接配置示例
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执行超时时间:

<select id="complexquery" timeout="30">
    <!-- 复杂查询 -->
</select>

五、三层架构优化建议

5.1 代码复用与解耦

统一异常处理

使用spring提供的@controlleradvice+@exceptionhandler实现全局异常处理机制,可以避免在各个controller中重复编写try-catch块。这种集中式异常处理方式具有以下优势:

  1. 减少重复代码,提高代码整洁度
  2. 统一异常响应格式,便于前端处理
  3. 可灵活分类处理不同类型的异常
@controlleradvice
public class globalexceptionhandler {
    /**
     * 处理业务异常
     * @param e 业务异常对象
     * @return 统一响应结果
     */
    @exceptionhandler(businessexception.class)
    @responsebody
    public result<?> handlebusinessexception(businessexception e) {
        log.error("业务异常:{}", e.getmessage(), e);
        return result.fail(e.getcode(), e.getmessage());
    }

    /**
     * 处理系统异常
     * @param e 异常对象
     * @return 统一响应结果
     */
    @exceptionhandler(exception.class)
    @responsebody
    public result<?> handleexception(exception e) {
        log.error("系统异常:", e);
        return result.fail(500, "系统繁忙,请稍后再试");
    }
}

通用service/dao层设计

通过抽取通用crud操作方法到基类中,可以大幅减少重复代码。这种设计模式特别适合具有大量相似crud操作的系统:

1.定义通用mapper接口

public interface basemapper<t> {
    int insert(t entity);  // 插入单条记录
    int deletebyid(@param("id") serializable id);  // 根据主键删除
    int updatebyid(@param("entity") t entity);  // 根据主键更新
    t selectbyid(@param("id") serializable id);  // 根据主键查询
    list<t> selectlist(@param("entity") t entity);  // 条件查询列表
    page<t> selectpage(page<t> page, @param("entity") t entity);  // 分页查询
}

2.业务mapper继承通用接口

public interface usermapper extends basemapper<user> {
    // 自定义方法
    @select("select * from user where username = #{username}")
    user selectbyusername(@param("username") string username);
    
    // 复杂查询示例
    @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实现

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;
    }
    
    // 其他通用方法实现...
}

5.2 性能优化

缓存设计

在service层合理使用缓存可以显著提升系统性能,特别是对于读多写少的场景:

缓存使用场景:

  • 高频访问的配置数据
  • 用户基础信息
  • 商品详情等静态数据
  • 计算结果缓存

缓存实现示例:

@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. 缓存未命中,查数据库
        user = usermapper.selectbyid(userid);
        if (user != null) {
            // 3. 设置缓存
            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;
    }
}

批量操作优化

使用批量操作可以大幅减少数据库交互次数,提高性能:

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 扩展性优化

接口版本控制

良好的版本控制策略可以保证系统平滑升级:

1.url路径版本控制:

@restcontroller
@requestmapping("/api/v1/user")
public class usercontrollerv1 {
    @getmapping("/list")
    public result<list<user>> listusers() {
        // v1版本实现
    }
}

@restcontroller
@requestmapping("/api/v2/user")
public class usercontrollerv2 {
    @getmapping("/list")
    public result<pageinfo<user>> listusers() {
        // v2版本实现,返回分页数据
    }
}

2.请求头版本控制:

@getmapping("/user/list")
public result<?> listusers(@requestheader("x-api-version") string version) {
    if ("2.0".equals(version)) {
        // 新版本逻辑
    } else {
        // 默认版本逻辑
    }
}

多数据源支持

使用spring的abstractroutingdatasource实现动态数据源切换:

1.配置多数据源:

@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.动态数据源路由:

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();
    }
}

依赖注入优化

使用构造器注入可以避免循环依赖问题,提高代码可测试性:

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;
    }
    
    // 业务方法...
}

2.使用lombok简化代码:

@service
@requiredargsconstructor
public class userserviceimpl implements userservice {
    private final usermapper usermapper;
    private final roleservice roleservice;
    
    // 自动生成构造器,无需手动编写
}

3.循环依赖解决方案:

// 使用@lazy注解解决循环依赖
@service
public class orderserviceimpl implements orderservice {
    private final userservice userservice;
    
    public orderserviceimpl(@lazy userservice userservice) {
        this.userservice = userservice;
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com