Spring MVC整合MyBatis案例

wen java案例 1

2025年最详尽的Spring MVC整合MyBatis实战指南:从零搭建到性能优化

目录导读

  1. 为什么需要整合? —— 架构演进与选型理由
  2. 环境准备与依赖管理 —— Maven坐标与版本兼容性
  3. 核心配置三部曲 —— web.xml、Spring容器、MyBatis工厂
  4. 代码层实战 —— Mapper接口、Service事务、Controller传参
  5. 常见性能陷阱与解决方案 —— N+1查询、一级缓存失效、连接池调优
  6. 高频面试问答 —— 深挖原理与易错点
  7. SEO关键词附录 —— 长尾词聚合

为什么需要整合?

在Java企业级开发中,Spring MVC负责呈现层请求分发,MyBatis专注SQL映射与数据访问层,两者整合后能实现松耦合架构:Controller不再直接触碰JDBC,而是通过Service接口调用Mapper代理对象,该组合相比Spring Data JPA,在复杂查询优化和SQL可控性上具备显著优势,尤其适合拥有DBA团队的传统企业与报表密集型系统。

Spring MVC整合MyBatis案例

根据Stack Overflow 2025年度开发者调查,超过47%的Java后端项目仍采用该技术栈,其稳定性和低学习曲线是核心驱动力,但盲目整合会导致Bean循环依赖、事务无法回滚等问题,本文将基于Spring 5.3.x + MyBatis 3.5.x + Tomcat 9组合,演示一套经过生产验证的搭建流程。


环境准备与依赖管理

基础诉求:JDK 1.8+、Maven 3.6+、MySQL 8.0。

在pom.xml中引入精确版本号(避免使用latest或范围版本导致构建不确定性):

<!-- Spring MVC 核心 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.39</version>
</dependency>
<!-- MyBatis 官方Spring整合模块 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.1.2</version>
</dependency>
<!-- 连接池:推荐HikariCP(需配合slf4j) -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.1.0</version>
</dependency>

版本冲突预警:如果同时引入spring-boot-starter-data-jpa,会与MyBatis的SqlSessionFactoryBean发生Bean工厂后置处理器冲突,解决方案是排除自动配置或使用@Primary标注。


核心配置三部曲

第一步:web.xml —— 启动Spring根容器与DispatcherServlet

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/applicationContext-*.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/springmvc-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

双层容器设计原则:根容器(Service/Dao)与子容器(Controller)分离,避免Controller被事务切面代理时产生双代理问题,注意:子容器不能扫描@Service@Repository

第二步:Spring核心配置文件 —— 数据源与事务

applicationContext-mybatis.xml中:

<!-- HikariCP数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="maximumPoolSize" value="20"/>
    <property name="idleTimeout" value="30000"/>
</bean>
<!-- SqlSessionFactory 整合为Spring单例 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <!-- 扫描别名包 -->
    <property name="typeAliasesPackage" value="com.example.entity"/>
    <!-- 自动映射下划线转驼峰 -->
    <property name="configuration">
        <bean class="org.apache.ibatis.session.Configuration">
            <property name="mapUnderscoreToCamelCase" value="true"/>
            <property name="cacheEnabled" value="true"/>
        </bean>
    </property>
</bean>

第三步:Mapper扫描注册

使用<mybatis:scan>标签(需引入mybatis-spring命名空间),或通过注解@MapperScan("com.example.mapper"),推荐接口与XML同名同包策略,这样无需在resources下配置路径映射。

<mybatis:scan base-package="com.example.mapper"/>
<!-- 同时配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>

代码层实战

Mapper接口与XML映射

public interface UserMapper {
    User selectByUsername(@Param("username") String username);
}

对应XML中:

<select id="selectByUsername" resultType="User">
    SELECT * FROM t_user WHERE username = #{username}
</select>

Service层事务边界

@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public void updateUserWithLog(User user) {
        userMapper.update(user);
        // 此处若抛出异常,则两步回滚
        logMapper.insertLog(...);
    }
}

Controller层JSON响应

@RestController
@RequestMapping("/api/user")
public class UserController {
    @GetMapping("/{id}")
    public ResponseEntity<UserVO> getById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getWithDetail(id));
    }
}

注意:使用@RestController时,需在springmvc-servlet.xml中配置<mvc:annotation-driven/>以及Jackson消息转换器。


常见性能陷阱与解决方案

  1. N+1查询问题:当一对多关联时,遍历外键发送多条SQL。改良:使用<collection>配合join或懒加载,但推荐在Service层显式批量查询。
  2. 一级缓存失效:MyBatis一级缓存是SqlSession级别,Spring整合后每次Mapper调用都会获取新SqlSession。策略:开启二级缓存(<cache/>),或使用@CacheNamespace
  3. 连接池耗尽:流量突刺时,默认HikariCP的minimumIdle=10会导致频繁创建连接。调整:将maximumPoolSize设置为CPU核心数+1,避免数据库线程上下文切换。

高频面试问答

Q1:Spring MVC整合MyBatis时,为什么SqlSessionTemplate是线程安全的? A:SqlSessionTemplate内部使用JDK动态代理,每次方法调用都会通过SqlSessionInterceptor获取新的SqlSession并自动关闭,它使用ThreadLocal绑定连接,确保同一线程的事务一致性,因此可全局共享。

Q2:如果Mapper接口与XML的namespace不一致会发生什么? A:会产生BindingException,MyBatis通过namespace + id 唯一绑定接口方法,若XML中namespace指错接口或id不匹配,Spring容器初始化时会抛出异常。

Q3:如何实现多数据源动态切换? A:通过AbstractRoutingDataSource,在MyBatis的DataSource设置代理,用ThreadLocal存储当前数据源key,注意事务配置需使用DynamicDataSourceTransactionManager,且避免跨库分布式事务。


SEO关键词聚合

  • Spring MVC MyBatis整合配置详解
  • MyBatis Spring事务管理最佳实践
  • MyBatis一对多懒加载性能优化
  • Spring MVC Restful API 数据库整合教程
  • HikariCP连接池参数调优2025

抱歉,评论功能暂时关闭!