Spring 和 MyBatis 整合完整指南
概述
Spring 和 MyBatis 的整合是企业级应用开发中最常见的技术组合之一。通过整合,我们可以充分利用 Spring 的 IoC 容器管理 MyBatis 的核心组件,同时使用 Spring 的声明式事务管理功能。本章将详细介绍多种整合方式及其最佳实践。
💡 整合的核心价值:
- 统一管理:由 Spring 容器管理 SqlSessionFactory、Mapper 等组件
- 事务集成:使用 Spring 的声明式事务管理
- 简化配置:减少 MyBatis 的配置工作
- 提高效率:自动管理资源的创建和释放
整合方式对比
| 整合方式 | 特点 | 适用场景 | 配置复杂度 |
|---|---|---|---|
| 传统 DAO | 继承 SqlSessionDaoSupport | 简单 CRUD,遗留项目 | 高 |
| MapperFactoryBean | 单个 Mapper 配置 | 少量 Mapper | 中 |
| MapperScannerConfigurer | 批量扫描 Mapper | 推荐使用 | 低 |
| @MapperScan | 注解方式扫描 | SpringBoot 项目 | 最低 |
环境准备
Maven 依赖配置
<dependencies>
<!-- Spring 核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<!-- Spring JDBC 和事务支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.23</version>
</dependency>
<!-- MyBatis 核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- MyBatis-Spring 整合包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.9.0</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.23</version>
<scope>test</scope>
</dependency>
</dependencies>
数据库准备
-- 创建数据库
CREATE DATABASE IF NOT EXISTS ssm_demo CHARACTER SET utf8mb4;
USE ssm_demo;
-- 创建客户表
CREATE TABLE t_customer (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL COMMENT '用户名',
jobs VARCHAR(50) COMMENT '职业',
phone VARCHAR(20) COMMENT '电话',
email VARCHAR(50) COMMENT '邮箱',
status INT DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入测试数据
INSERT INTO t_customer (username, jobs, phone, email) VALUES
('张三', 'Java工程师', '13800138001', 'zhangsan@email.com'),
('李四', '前端工程师', '13800138002', 'lisi@email.com'),
('王五', '测试工程师', '13800138003', 'wangwu@email.com');
传统DAO方式整合
1. 创建实体类
package com.example.domain;
import java.util.Date;
public class Customer {
private Integer id;
private String username;
private String jobs;
private String phone;
private String email;
private Integer status;
private Date createTime;
private Date updateTime;
// 构造方法
public Customer() {}
public Customer(String username, String jobs, String phone) {
this.username = username;
this.jobs = jobs;
this.phone = phone;
}
// Getter 和 Setter 方法
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getJobs() {
return jobs;
}
public void setJobs(String jobs) {
this.jobs = jobs;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", username='" + username + '\'' +
", jobs='" + jobs + '\'' +
", phone='" + phone + '\'' +
", email='" + email + '\'' +
", status=" + status +
'}';
}
}
2. 创建 Mapper XML 文件
在 src/main/resources 下创建 mapper/CustomerMapper.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="CustomerMapper">
<!-- 根据ID查询客户 -->
<select id="findCustomerById" parameterType="int"
resultType="com.example.domain.Customer">
SELECT * FROM t_customer WHERE id = #{id}
</select>
<!-- 查询所有客户 -->
<select id="findAllCustomers" resultType="com.example.domain.Customer">
SELECT * FROM t_customer ORDER BY id
</select>
<!-- 插入客户 -->
<insert id="insertCustomer" parameterType="com.example.domain.Customer"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_customer (username, jobs, phone, email)
VALUES (#{username}, #{jobs}, #{phone}, #{email})
</insert>
<!-- 更新客户 -->
<update id="updateCustomer" parameterType="com.example.domain.Customer">
UPDATE t_customer
<set>
<if test="username != null">username = #{username},</if>
<if test="jobs != null">jobs = #{jobs},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="email != null">email = #{email},</if>
<if test="status != null">status = #{status},</if>
</set>
WHERE id = #{id}
</update>
<!-- 删除客户 -->
<delete id="deleteCustomer" parameterType="int">
DELETE FROM t_customer WHERE id = #{id}
</delete>
</mapper>
3. 创建 MyBatis 配置文件
在 src/main/resources 下创建 mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置别名 -->
<typeAliases>
<package name="com.example.domain"/>
</typeAliases>
<!-- 加载 Mapper 文件 -->
<mappers>
<mapper resource="mapper/CustomerMapper.xml"/>
</mappers>
</configuration>
4. 创建 DAO 层接口和实现类
package com.example.dao;
import com.example.domain.Customer;
import java.util.List;
public interface CustomerDao {
// 根据ID查询客户
Customer findCustomerById(int id);
// 查询所有客户
List<Customer> findAllCustomers();
// 插入客户
int insertCustomer(Customer customer);
// 更新客户
int updateCustomer(Customer customer);
// 删除客户
int deleteCustomer(int id);
}
package com.example.dao.impl;
import com.example.dao.CustomerDao;
import com.example.domain.Customer;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
/**
* 基于传统 DAO 方式的实现
* 继承 SqlSessionDaoSupport 获得 SqlSession
*/
public class CustomerDaoImpl extends SqlSessionDaoSupport implements CustomerDao {
@Override
public Customer findCustomerById(int id) {
return getSqlSession().selectOne("CustomerMapper.findCustomerById", id);
}
@Override
public List<Customer> findAllCustomers() {
return getSqlSession().selectList("CustomerMapper.findAllCustomers");
}
@Override
public int insertCustomer(Customer customer) {
return getSqlSession().insert("CustomerMapper.insertCustomer", customer);
}
@Override
public int updateCustomer(Customer customer) {
return getSqlSession().update("CustomerMapper.updateCustomer", customer);
}
@Override
public int deleteCustomer(int id) {
return getSqlSession().delete("CustomerMapper.deleteCustomer", id);
}
}
5. Spring 配置文件
在 src/main/resources 下创建 applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm_demo?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!-- 连接池配置 -->
<property name="initialSize" value="5"/>
<property name="maxTotal" value="20"/>
<property name="maxIdle" value="10"/>
<property name="minIdle" value="5"/>
<property name="maxWaitMillis" value="3000"/>
</bean>
<!-- 配置 SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!-- 配置 DAO 实现类 -->
<bean id="customerDao" class="com.example.dao.impl.CustomerDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
6. 测试代码
package com.example.test;
import com.example.dao.CustomerDao;
import com.example.domain.Customer;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class CustomerDaoTest {
private ApplicationContext context;
private CustomerDao customerDao;
@Before
public void setUp() {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
customerDao = (CustomerDao) context.getBean("customerDao");
}
@Test
public void testFindCustomerById() {
Customer customer = customerDao.findCustomerById(1);
System.out.println("查询结果:" + customer);
}
@Test
public void testFindAllCustomers() {
List<Customer> customers = customerDao.findAllCustomers();
System.out.println("客户总数:" + customers.size());
customers.forEach(System.out::println);
}
@Test
public void testInsertCustomer() {
Customer customer = new Customer("赵六", "产品经理", "13800138006");
customer.setEmail("zhaoliu@email.com");
int result = customerDao.insertCustomer(customer);
System.out.println("插入结果:" + result);
System.out.println("新客户ID:" + customer.getId());
}
@Test
public void testUpdateCustomer() {
Customer customer = customerDao.findCustomerById(1);
customer.setJobs("高级Java工程师");
customer.setEmail("zhangsan_new@email.com");
int result = customerDao.updateCustomer(customer);
System.out.println("更新结果:" + result);
}
@Test
public void testDeleteCustomer() {
int result = customerDao.deleteCustomer(4);
System.out.println("删除结果:" + result);
}
}
Mapper接口方式整合
相比传统 DAO 方式,Mapper 接口方式更加简洁,是目前的主流方式。
方式一:基于 MapperFactoryBean
1. 创建 Mapper 接口
package com.example.mapper;
import com.example.domain.Customer;
import java.util.List;
public interface CustomerMapper {
// 根据ID查询客户
Customer findCustomerById(int id);
// 查询所有客户
List<Customer> findAllCustomers();
// 插入客户
int insertCustomer(Customer customer);
// 更新客户
int updateCustomer(Customer customer);
// 删除客户
int deleteCustomer(int id);
}
2. 创建 Mapper XML 文件
在 src/main/resources/com/example/mapper 下创建 CustomerMapper.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.CustomerMapper">
<!-- 根据ID查询客户 -->
<select id="findCustomerById" parameterType="int"
resultType="com.example.domain.Customer">
SELECT * FROM t_customer WHERE id = #{id}
</select>
<!-- 查询所有客户 -->
<select id="findAllCustomers" resultType="com.example.domain.Customer">
SELECT * FROM t_customer ORDER BY id
</select>
<!-- 插入客户 -->
<insert id="insertCustomer" parameterType="com.example.domain.Customer"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_customer (username, jobs, phone, email)
VALUES (#{username}, #{jobs}, #{phone}, #{email})
</insert>
<!-- 更新客户 -->
<update id="updateCustomer" parameterType="com.example.domain.Customer">
UPDATE t_customer
<set>
<if test="username != null">username = #{username},</if>
<if test="jobs != null">jobs = #{jobs},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="email != null">email = #{email},</if>
<if test="status != null">status = #{status},</if>
</set>
WHERE id = #{id}
</update>
<!-- 删除客户 -->
<delete id="deleteCustomer" parameterType="int">
DELETE FROM t_customer WHERE id = #{id}
</delete>
</mapper>
3. 修改 MyBatis 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置别名 -->
<typeAliases>
<package name="com.example.domain"/>
</typeAliases>
<!-- 加载 Mapper 文件 -->
<mappers>
<mapper resource="com/example/mapper/CustomerMapper.xml"/>
</mappers>
</configuration>
4. Spring 配置文件(MapperFactoryBean 方式)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm_demo?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!-- 连接池配置 -->
<property name="initialSize" value="5"/>
<property name="maxTotal" value="20"/>
<property name="maxIdle" value="10"/>
<property name="minIdle" value="5"/>
<property name="maxWaitMillis" value="3000"/>
</bean>
<!-- 配置 SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!-- 使用 MapperFactoryBean 配置单个 Mapper -->
<bean id="customerMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.example.mapper.CustomerMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
5. 测试代码
package com.example.test;
import com.example.mapper.CustomerMapper;
import com.example.domain.Customer;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MapperFactoryBeanTest {
private ApplicationContext context;
private CustomerMapper customerMapper;
@Before
public void setUp() {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取 Mapper 接口的代理对象
customerMapper = (CustomerMapper) context.getBean("customerMapper");
}
@Test
public void testFindCustomerById() {
Customer customer = customerMapper.findCustomerById(1);
System.out.println("查询结果:" + customer);
}
@Test
public void testInsertCustomer() {
Customer customer = new Customer("孙七", "架构师", "13800138007");
customer.setEmail("sunqi@email.com");
int result = customerMapper.insertCustomer(customer);
System.out.println("插入结果:" + result);
System.out.println("新客户ID:" + customer.getId());
}
}
Mapper 接口开发规范
使用 Mapper 接口编程需要遵循以下规范:
| 规范项 | 要求 | 说明 |
|---|---|---|
| 命名一致性 | Mapper 接口名与 XML 文件名一致 | 如 CustomerMapper.java 和 CustomerMapper.xml |
| namespace | XML 的 namespace 必须是接口的全限定名 | 如 com.example.mapper.CustomerMapper |
| 方法名 | 接口方法名必须与 XML 中的 id 一致 | 如方法 findCustomerById 对应 <select id="findCustomerById"> |
| 参数类型 | 方法参数类型与 parameterType 一致 | 支持基本类型、包装类、JavaBean、Map 等 |
| 返回类型 | 方法返回类型与 resultType/resultMap 对应 | 单个对象、List、Map 等 |
💡 提示:遵循这些规范后,MyBatis 会自动生成 Mapper 接口的代理实现类,无需手动编写实现代码。
MapperScannerConfigurer整合
MapperScannerConfigurer 可以批量扫描 Mapper 接口,是最推荐的整合方式。
方式二:基于 MapperScannerConfigurer
1. Spring 配置文件(MapperScannerConfigurer 方式)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm_demo?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!-- 连接池配置 -->
<property name="initialSize" value="5"/>
<property name="maxTotal" value="20"/>
<property name="maxIdle" value="10"/>
<property name="minIdle" value="5"/>
<property name="maxWaitMillis" value="3000"/>
</bean>
<!-- 配置 SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 可选:配置 Mapper XML 文件位置 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- 使用 MapperScannerConfigurer 批量扫描 Mapper 接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定要扫描的包路径 -->
<property name="basePackage" value="com.example.mapper"/>
<!-- 可选:指定 SqlSessionFactory 的 bean 名称 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 扫描 Service 层组件 -->
<context:component-scan base-package="com.example.service"/>
</beans>
2. 创建 Service 层
package com.example.service;
import com.example.domain.Customer;
import java.util.List;
public interface CustomerService {
Customer getCustomerById(int id);
List<Customer> getAllCustomers();
boolean addCustomer(Customer customer);
boolean updateCustomer(Customer customer);
boolean deleteCustomer(int id);
}
package com.example.service.impl;
import com.example.domain.Customer;
import com.example.mapper.CustomerMapper;
import com.example.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class CustomerServiceImpl implements CustomerService {
@Autowired
private CustomerMapper customerMapper;
@Override
@Transactional(readOnly = true)
public Customer getCustomerById(int id) {
return customerMapper.findCustomerById(id);
}
@Override
@Transactional(readOnly = true)
public List<Customer> getAllCustomers() {
return customerMapper.findAllCustomers();
}
@Override
public boolean addCustomer(Customer customer) {
return customerMapper.insertCustomer(customer) > 0;
}
@Override
public boolean updateCustomer(Customer customer) {
return customerMapper.updateCustomer(customer) > 0;
}
@Override
public boolean deleteCustomer(int id) {
return customerMapper.deleteCustomer(id) > 0;
}
}
3. 测试代码
package com.example.test;
import com.example.domain.Customer;
import com.example.mapper.CustomerMapper;
import com.example.service.CustomerService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MapperScannerTest {
@Autowired
private CustomerMapper customerMapper;
@Autowired
private CustomerService customerService;
@Test
public void testMapperScan() {
// 直接注入 Mapper 接口
Customer customer = customerMapper.findCustomerById(1);
System.out.println("Mapper查询结果:" + customer);
}
@Test
public void testServiceLayer() {
// 通过 Service 层访问
List<Customer> customers = customerService.getAllCustomers();
System.out.println("Service查询结果:");
customers.forEach(System.out::println);
}
@Test
public void testTransaction() {
Customer customer = new Customer("测试事务", "测试职位", "13800138008");
customer.setEmail("test@email.com");
boolean result = customerService.addCustomer(customer);
System.out.println("添加结果:" + result);
System.out.println("新客户ID:" + customer.getId());
}
}
SpringBoot整合MyBatis
SpringBoot 通过自动配置极大简化了 MyBatis 的整合过程。
1. Maven 依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
</parent>
<dependencies>
<!-- SpringBoot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis SpringBoot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 数据源(使用 HikariCP) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. application.yml 配置
spring:
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/ssm_demo?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# HikariCP 连接池配置
hikari:
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 600000
max-lifetime: 1800000
connection-timeout: 30000
# MyBatis 配置
mybatis:
# 配置文件位置
config-location: classpath:mybatis-config.xml
# Mapper XML 文件位置
mapper-locations: classpath:mapper/*.xml
# 类型别名包路径
type-aliases-package: com.example.domain
# 配置项
configuration:
# 开启驼峰命名转换
map-underscore-to-camel-case: true
# 开启延迟加载
lazy-loading-enabled: true
# 关闭积极加载
aggressive-lazy-loading: false
# 开启二级缓存
cache-enabled: true
# 日志配置
logging:
level:
com.example.mapper: debug
3. 主启动类
package com.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@MapperScan("com.example.mapper") // 扫描 Mapper 接口
@EnableTransactionManagement // 开启事务管理
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4. Mapper 接口(注解方式)
package com.example.mapper;
import com.example.domain.Customer;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper // 标识为 Mapper 接口
public interface CustomerMapper {
// 使用注解方式编写 SQL
@Select("SELECT * FROM t_customer WHERE id = #{id}")
Customer findCustomerById(int id);
@Select("SELECT * FROM t_customer ORDER BY id")
List<Customer> findAllCustomers();
@Insert("INSERT INTO t_customer(username, jobs, phone, email) " +
"VALUES(#{username}, #{jobs}, #{phone}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertCustomer(Customer customer);
@Update("UPDATE t_customer SET username=#{username}, jobs=#{jobs}, " +
"phone=#{phone}, email=#{email} WHERE id=#{id}")
int updateCustomer(Customer customer);
@Delete("DELETE FROM t_customer WHERE id = #{id}")
int deleteCustomer(int id);
// 复杂查询使用 @Select + @Results
@Select("SELECT * FROM t_customer WHERE jobs = #{jobs}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "jobs", column = "jobs"),
@Result(property = "phone", column = "phone"),
@Result(property = "email", column = "email")
})
List<Customer> findCustomersByJob(String jobs);
}
5. Controller 层
package com.example.controller;
import com.example.domain.Customer;
import com.example.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/customers")
public class CustomerController {
@Autowired
private CustomerService customerService;
@GetMapping("/{id}")
public Customer getCustomer(@PathVariable int id) {
return customerService.getCustomerById(id);
}
@GetMapping
public List<Customer> getAllCustomers() {
return customerService.getAllCustomers();
}
@PostMapping
public String addCustomer(@RequestBody Customer customer) {
boolean result = customerService.addCustomer(customer);
return result ? "添加成功" : "添加失败";
}
@PutMapping("/{id}")
public String updateCustomer(@PathVariable int id,
@RequestBody Customer customer) {
customer.setId(id);
boolean result = customerService.updateCustomer(customer);
return result ? "更新成功" : "更新失败";
}
@DeleteMapping("/{id}")
public String deleteCustomer(@PathVariable int id) {
boolean result = customerService.deleteCustomer(id);
return result ? "删除成功" : "删除失败";
}
}
事务管理
1. 声明式事务配置
@Service
@Transactional // 类级别事务
public class CustomerServiceImpl implements CustomerService {
@Autowired
private CustomerMapper customerMapper;
// 只读事务,优化性能
@Transactional(readOnly = true)
public Customer getCustomerById(int id) {
return customerMapper.findCustomerById(id);
}
// 需要事务,默认传播行为
@Transactional
public boolean addCustomer(Customer customer) {
return customerMapper.insertCustomer(customer) > 0;
}
// 设置事务超时和回滚规则
@Transactional(
timeout = 30,
rollbackFor = Exception.class,
noRollbackFor = BusinessException.class
)
public boolean updateCustomer(Customer customer) {
return customerMapper.updateCustomer(customer) > 0;
}
// 使用新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditLog(String operation) {
// 记录审计日志,独立事务
}
}
2. 编程式事务
@Service
public class TransactionService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private CustomerMapper customerMapper;
public void batchInsert(List<Customer> customers) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
for (Customer customer : customers) {
customerMapper.insertCustomer(customer);
}
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
}
});
}
}
性能优化
1. 连接池优化
spring:
datasource:
hikari:
# 最小空闲连接数
minimum-idle: 10
# 最大连接数
maximum-pool-size: 50
# 连接测试查询
connection-test-query: SELECT 1
# 连接池名称
pool-name: SSM-HikariCP
# 连接泄露检测阈值
leak-detection-threshold: 60000
2. MyBatis 缓存配置
<!-- 在 Mapper XML 中配置二级缓存 -->
<mapper namespace="com.example.mapper.CustomerMapper">
<cache
eviction="LRU"
flushInterval="60000"
size="1024"
readOnly="true"/>
<!-- 禁用特定查询的缓存 -->
<select id="getRealTimeData" useCache="false">
SELECT * FROM real_time_table
</select>
</mapper>
3. 批量操作优化
@Mapper
public interface CustomerMapper {
// 批量插入
@Insert({
"<script>",
"INSERT INTO t_customer(username, jobs, phone, email) VALUES",
"<foreach collection='list' item='item' separator=','>",
"(#{item.username}, #{item.jobs}, #{item.phone}, #{item.email})",
"</foreach>",
"</script>"
})
int batchInsert(List<Customer> customers);
// 批量更新
@Update({
"<script>",
"<foreach collection='list' item='item' separator=';'>",
"UPDATE t_customer SET",
"username = #{item.username},",
"jobs = #{item.jobs}",
"WHERE id = #{item.id}",
"</foreach>",
"</script>"
})
int batchUpdate(List<Customer> customers);
}
最佳实践
1. 项目结构规范
src/main/java/
└── com.example/
├── domain/ # 实体类
├── mapper/ # Mapper接口
├── service/ # Service接口
│ └── impl/ # Service实现
├── controller/ # Controller层
├── config/ # 配置类
├── exception/ # 异常处理
└── util/ # 工具类
src/main/resources/
├── mapper/ # Mapper XML文件
├── static/ # 静态资源
├── templates/ # 模板文件
├── application.yml # 应用配置
└── mybatis-config.xml # MyBatis配置
2. 代码规范
// 1. Mapper 接口规范
@Mapper
public interface BaseMapper<T> {
T selectById(Long id);
List<T> selectAll();
int insert(T entity);
int update(T entity);
int deleteById(Long id);
}
// 2. Service 层规范
@Service
@Transactional(rollbackFor = Exception.class)
public abstract class BaseService<T> {
protected abstract BaseMapper<T> getMapper();
@Transactional(readOnly = true)
public T getById(Long id) {
return getMapper().selectById(id);
}
}
// 3. 统一返回结果
@Data
public class Result<T> {
private int 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;
}
}
3. 异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(DataAccessException.class)
public Result<String> handleDataAccessException(DataAccessException e) {
log.error("数据库操作异常", e);
return Result.error("数据库操作失败");
}
@ExceptionHandler(DuplicateKeyException.class)
public Result<String> handleDuplicateKeyException(DuplicateKeyException e) {
return Result.error("数据已存在,请勿重复添加");
}
}
常见问题
问题 1:找不到 Mapper Bean
原因:
- 未配置 @MapperScan 或 MapperScannerConfigurer
- Mapper 接口路径配置错误
- 缺少 @Mapper 注解
解决方案:
// 方案1:启动类添加 @MapperScan
@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application { }
// 方案2:Mapper 接口添加 @Mapper
@Mapper
public interface CustomerMapper { }
// 方案3:配置类中配置
@Configuration
public class MyBatisConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.example.mapper");
return configurer;
}
}
问题 2:事务不生效
原因:
- 方法不是 public
- 自调用问题
- 异常被捕获
- 未配置事务管理器
解决方案:
// 确保方法是 public
@Transactional
public void doSomething() { } // 正确
// 避免自调用
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void method1() {
serviceB.method2(); // 通过代理调用
}
}
// 正确抛出异常
@Transactional
public void method() {
try {
// 业务逻辑
} catch (Exception e) {
throw new RuntimeException(e); // 确保异常抛出
}
}
问题 3:MyBatis 类型处理器问题
解决方案:
// 自定义类型处理器
@MappedTypes(JsonNode.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonTypeHandler extends BaseTypeHandler<JsonNode> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
JsonNode parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter.toString());
}
@Override
public JsonNode getNullableResult(ResultSet rs, String columnName)
throws SQLException {
String json = rs.getString(columnName);
return parseJson(json);
}
}
// 配置类型处理器
mybatis:
type-handlers-package: com.example.handler
问题 4:分页查询
使用 PageHelper:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.5</version>
</dependency>
@Service
public class CustomerService {
public PageInfo<Customer> getCustomerPage(int pageNum, int pageSize) {
// 设置分页参数
PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<Customer> customers = customerMapper.findAllCustomers();
// 包装成分页结果
return new PageInfo<>(customers);
}
}
相关文章
前后章节导航
- 上一章:Web程序设计笔记13——第九章:MyBatis的关系映射
- 下一章:Web程序设计笔记15——第十一章:Spring MVC
- 相关指南:Spring Bean 管理完整指南 和 MyBatis 动态SQL完整指南
Spring 框架核心
- 第一章:Spring的基本应用 - Spring 框架入门
- 第二章:Spring中的Bean - Bean 管理基础
- Spring Bean 管理完整指南 - Bean 管理进阶
MyBatis 核心技术
- 第六章:初识MyBatis - MyBatis 基础
- 第七章:MyBatis核心配置 - 配置详解
- 第九章:MyBatis 的关系映射 - 关联查询
SSM 整合实践
- Spring 事务管理完整指南 - 事务管理深入
- 第十一章:Spring MVC - Web层框架
- 第十二章:Spring MVC 的核心类和注解
SpringBoot 进阶
- SpringBoot之项目文件作用 - SpringBoot 基础
- 整合JUnit、Mybatis - SpringBoot 整合 MyBatis
- 整合MyBatis-plus、Druid - 高级持久层框架
整合方式详解
1. 传统 DAO 模式
- 使用 SqlSessionDaoSupport
- 适合简单的 CRUD 操作
- 需要手动管理 SqlSession
2. Mapper 接口模式
- MapperFactoryBean:单个 Mapper 配置
- MapperScannerConfigurer:批量扫描 Mapper
- 推荐使用,更加简洁高效
3. 注解配置方式
- @MapperScan 注解扫描
- @Mapper 标识接口
- SpringBoot 中的主流方式
常见问题与解决
-
配置文件加载问题
- 检查 mapper 路径配置
- 确保 XML 文件在 classpath 下
- 注意包名和路径的对应关系
-
事务管理配置
- 使用 Spring 的事务管理器
- @Transactional 注解的正确使用
- 事务传播行为的设置
-
数据源配置
- 选择合适的连接池(DBCP、HikariCP、Druid)
- 连接池参数优化
- 数据库连接泄漏问题
最佳实践建议
-
代码组织结构
- Mapper 接口与 XML 同包名
- 统一的命名规范
- 合理的分层设计
-
性能优化
- 使用 MyBatis 二级缓存
- 合理使用延迟加载
- SQL 语句优化
-
开发效率
- 使用 MyBatis Generator
- 集成 MyBatis-Plus
- 热部署配置
扩展阅读
- MyBatis-Spring 官方文档
- Spring Boot 自动配置原理
- MyBatis-Plus 快速入门