十、Spring整合Mybatis
1. pom.xml依赖配置
<!-- Spring核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<!-- Spring AOP支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.23</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!-- MyBatis-Spring整合包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.0</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.36</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>
2. log4j.properties
# 设置根日志级别为INFO,输出到控制台
log4j.rootLogger=INFO, stdout
# 控制台输出配置
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# MyBatis日志输出级别
log4j.logger.com.hg.mapper=DEBUG
3. 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 1. IOC注解扫描 -->
<context:component-scan base-package="com.hg"></context:component-scan>
<!-- 2. 引入外部属性文件 -->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 3. 数据源配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 4. SqlSessionFactory配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 5. Mapper接口扫描配置 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hg.mapper"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
</beans>
4. db.properties数据库配置
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
5. Spring整合JUnit测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringMybatisTest {
@Autowired
private UserService userService;
@Test
public void testGetUser() {
User user = userService.getUserById(1L);
System.out.println(user);
}
// 其他测试方法...
}
十一、事务介绍
1. 什么是事务?
事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作。这些操作要么全部执行,要么全部不执行。
核心思想:执行多条SQL语句,要么全部成功,要么全部回滚。
2. 事务的特点(ACID属性)
| 属性 | 说明 | 示例 |
|---|---|---|
| 原子性 | 事务是最小的执行单位,不可再分割 | 转账操作:扣款和加款必须同时成功或失败 |
| 一致性 | 事务执行前后,数据完整性不被破坏 | 转账前后,两个账户总金额不变 |
| 隔离性 | 多个事务并发执行时互不干扰 | 事务A读取数据时,事务B不能修改该数据 |
| 持久性 | 事务一旦提交,对数据库的改变是永久的 | 转账成功后,即使系统崩溃,数据也不会丢失 |
3. MySQL控制事务
-- 开启事务
START TRANSACTION;
-- 执行SQL语句
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 提交事务(持久化到数据库)
COMMIT;
-- 或回滚事务(撤销所有操作)
ROLLBACK;
工作原理:
-
START TRANSACTION:创建undo_log记录老数据 -
COMMIT:删除undo_log,提交更改 -
ROLLBACK:根据undo_log恢复老数据
4. JDBC控制事务
Connection conn = null;
try {
// 获取数据库连接
conn = DriverManager.getConnection(url, username, password);
// 关闭自动提交,相当于START TRANSACTION
conn.setAutoCommit(false);
// 执行SQL操作
statement1.executeUpdate("UPDATE account SET balance = balance - 100 WHERE id = 1");
statement2.executeUpdate("UPDATE account SET balance = balance + 100 WHERE id = 2");
// 提交事务
conn.commit();
} catch (Exception e) {
// 回滚事务
if (conn != null) {
conn.rollback();
}
e.printStackTrace();
} finally {
// 关闭资源
if (conn != null) {
conn.close();
}
}
5. MyBatis控制事务
SqlSession sqlSession = null;
try {
// 获取SqlSession,参数false表示关闭自动提交
sqlSession = sqlSessionFactory.openSession(false);
// 获取Mapper
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
// 执行操作
mapper.deduct(1L, 100);
mapper.add(2L, 100);
// 提交事务
sqlSession.commit();
} catch (Exception e) {
// 回滚事务
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
// 关闭SqlSession
if (sqlSession != null) {
sqlSession.close();
}
}
十二、Spring事务控制的API
1. PlatformTransactionManager(平台事务管理器)
作用:Spring事务管理的核心接口,负责事务的开启、提交和回滚。
常用实现类:
-
DataSourceTransactionManager:适用于JDBC和MyBatis -
HibernateTransactionManager:适用于Hibernate -
JpaTransactionManager:适用于JPA
配置示例:
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2. TransactionDefinition(事务定义)
作用:定义事务的属性,包括隔离级别、传播行为、超时时间等。
常用属性:
(1)隔离级别(Isolation Level)
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| READ_UNCOMMITTED | ❌ | ❌ | ❌ | 最高 |
| READ_COMMITTED | ✅ | ❌ | ❌ | 高 |
| REPEATABLE_READ | ✅ | ✅ | ❌ | 中 |
| SERIALIZABLE | ✅ | ✅ | ✅ | 最低 |
-
默认值:使用底层数据库的默认隔离级别
-
MySQL默认:REPEATABLE_READ
-
Oracle默认:READ_COMMITTED
(2)传播行为(Propagation Behavior)
| 传播行为 | 说明 | 适用情况 |
|---|---|---|
| REQUIRED(默认) | 如果当前存在事务,则加入该事务;如果不存在,则新建一个事务 | 增删改操作 |
| SUPPORTS | 如果当前存在事务,则加入该事务;如果不存在,则以非事务方式运行 | 查询操作 |
| REQUIRES_NEW | 新建事务,如果当前存在事务,则挂起当前事务 | 独立事务操作 |
| NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则挂起当前事务 | 不涉及事务的操作 |
| NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 | 禁止在事务中运行 |
| MANDATORY | 必须在一个已有的事务中运行,否则抛出异常 | 强制要求事务 |
| NESTED | 如果当前存在事务,则在嵌套事务内执行 | 复杂的业务场景 |
(3)其他属性
| 属性 | 说明 | 默认值 |
|---|---|---|
| 超时时间 | 事务最长执行时间(秒),超时则自动回滚 | -1(永不超时) |
| 是否只读 | 标记为只读事务,优化查询性能 | false |
| 回滚规则 | 指定哪些异常触发回滚 | RuntimeException和Error |
3. TransactionStatus(事务状态)
作用:表示事务的当前状态,可以用于控制事务的提交和回滚。
常用方法:
public interface TransactionStatus extends SavepointManager {
// 判断是否有保存点
boolean hasSavepoint();
// 判断是否是新事务
boolean isNewTransaction();
// 设置只回滚
void setRollbackOnly();
// 判断是否标记为只回滚
boolean isRollbackOnly();
// 刷新事务
void flush();
// 判断事务是否已完成
boolean isCompleted();
}
十三、Spring声明式事务管理配置
1. 添加事务管理依赖
<!-- Spring事务管理 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.23</version>
</dependency>
<!-- Spring JDBC支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
2. 完整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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 1. IOC注解扫描 -->
<context:component-scan base-package="com.hg"></context:component-scan>
<!-- 2. 引入外部属性文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 3. 数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 4. SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 5. Mapper接口扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hg.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- ========== 事务配置 ========== -->
<!-- 6. 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 7. 开启注解驱动的事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
3. Service层事务使用示例
@Service
@Transactional // 类级别注解,所有public方法都应用事务
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
// 查询方法使用只读事务,提高性能
@Override
@Transactional(readOnly = true)
public User getUserById(Long id) {
return userMapper.selectById(id);
}
// 转账操作,需要完整的事务控制
@Override
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为:有事务就加入,没有就新建
isolation = Isolation.DEFAULT, // 隔离级别:使用数据库默认
timeout = 30, // 超时时间:30秒
rollbackFor = Exception.class // 回滚规则:所有异常都回滚
)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
// 1. 扣款
User fromUser = userMapper.selectById(fromId);
fromUser.setBalance(fromUser.getBalance().subtract(amount));
userMapper.update(fromUser);
// 2. 加款
User toUser = userMapper.selectById(toId);
toUser.setBalance(toUser.getBalance().add(amount));
userMapper.update(toUser);
// 如果发生异常,Spring会自动回滚上述两个操作
}
// 支持当前事务,如果没有事务则以非事务运行
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public List<User> getAllUsers() {
return userMapper.selectAll();
}
}
4. 事务传播行为示例
@Service
public class OrderService {
@Autowired
private UserService userService;
@Transactional
public void createOrder(Order order) {
// 1. 保存订单
orderMapper.insert(order);
// 2. 扣减库存(在同一个事务中)
productService.deductStock(order.getProductId(), order.getQuantity());
// 3. 记录日志(新建独立事务)
logService.addLog("创建订单", order.getId());
}
}
@Service
public class ProductService {
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock(Long productId, Integer quantity) {
// 这个方法会加入createOrder的事务
productMapper.updateStock(productId, quantity);
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addLog(String operation, Long targetId) {
// 这个方法会新建事务,不受外层事务影响
logMapper.insert(new Log(operation, targetId));
}
}
总结
通过Spring整合MyBatis和配置声明式事务管理,我们可以:
-
简化配置:通过注解和XML配置,减少模板代码
-
统一管理:集中管理数据库连接和事务配置
-
提高效率:利用AOP实现非侵入式的事务管理
-
增强维护性:事务配置与业务代码分离,便于维护
-
保证一致性:确保业务操作的原子性和一致性
这种整合方式是企业级Java开发的标配,能够有效提高开发效率和系统稳定性。