三 后台环境搭建
8. 声明式事务
支点: git checkout -b 3.8.0_transaction
8.1 目标
-
在框架环境下通过一系列的配置由 Spring 来管理事务. 通过事务操作
-
- 让我们写的代码能够享受框架提供的服务
try {
// 开启事务 (关闭自动提交)
// 代码中有一个错误 所有数据库操作 ( 增删改 ) 回滚
connection.setAutoCommiit(false);
// 核心操作
adminService.saveAdmin(admin);
// 提交事务
connection.commit();
} catch (Exception e) {
// 回滚事务
connection.roolBack();
} finally {
// 释放数据库连接
connection.close();
}
8.2 思路
8.2.1 选择合适的事务管理器
-
事务定位方法
-
- 注解:@Transactional
- 配置:切入点表达式 <推荐配置>
8.2.2 配置文件结构
图 8-1 事务管理的配置结构图
8.3 代码
8.3.1 配置事务管理器
8.3.2 配置 AOP
8.3.3 配置事务
spring-persist-tx.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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- 配置自动扫描的包: 只要是为了把 Service 扫描到 IOC 容器中 -->
<context:component-scan base-package="com.atguigu.crowd.service"/>
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置数据源 dataSource 会从 IOC 容器中找到-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务切面 -->
<aop:config>
<!-- 考虑到后面我们整合 SpringSecurity, 避免把 UserDetailsService 加入事务控制, 让切入点表达式定位到 ServiceImpl -->
<aop:pointcut id="txPointcut" expression="execution(* *..*ServiceImpl.*(..))"/>
<!-- 将切入点表达式和事务通知关联起来 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 配置事务属性 -->
<tx:attributes>
<!-- 查询方法: 配置只读属性, 让数据库知道这是个查询操作, 能够进行一定优化 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
<!-- 增删改方法: 配置事务传播行为、回滚异常 -->
<!--
propagation 属性:
REQUIRED: 默认值, 表示当前方法必须工作在事务中,
如果当前线程上没有已经开启的事务, 则自己开新事务。
如果已经有了那么就使用已有的事务
顾虑: 用别人的事务有可能 "被" 回滚
REQUIRES_NEW: 建议使用的值, 表示不管当前线程上有没有事务, 都要自己开事务, 在自己的事务中运行
好处: 不会受到其他事务回滚的影响
-->
<!--
rollback-for 属性: 配置事务方法针对上面样的异常回滚
默认: RuntimeException 运行时异常回滚
建议: 编译时异常和运行时异常都回滚
-->
<tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<tx:method name="update*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<tx:method name="remove*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<tx:method name="batch*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
</beans>
8.4 测试
8.4.1 创建服务接口和实现
atcrowdfunding03-admin-component
package com.atguigu.crowd.service.api;
import com.atguigu.crowd.entity.Admin;
public interface AdminService {
void saveAdmin(Admin admin);
}
package com.atguigu.crowd.service.impl;
import com.atguigu.crowd.entity.Admin;
import com.atguigu.crowd.mapper.AdminMapper;
import com.atguigu.crowd.service.api.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminMapper adminMapper;
@Override
public void saveAdmin(Admin admin) {
adminMapper.insert(admin);
throw new RuntimeException();
}
}
8.4.2 Junit 测试
测试代码 testTx 方法
package com.atguigu.crowd.test;
import com.atguigu.crowd.entity.Admin;
import com.atguigu.crowd.mapper.AdminMapper;
import com.atguigu.crowd.service.api.AdminService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/* 在类上标记必要的注解, Spring 整合 Junit */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml", "classpath:spring-persist-tx.xml"})
public class CrowdTest {
@Autowired
private DataSource dataSource;
@Autowired
private AdminMapper adminMapper;
@Autowired
private AdminService adminService;
@Test
public void testTx() {
Admin admin = new Admin(null, "jerry", "123123", "杰瑞", "jerry@qq.com", null);
adminService.saveAdmin(admin);
}
@Test
public void testLog() {
// 1. 获取 Logger(日志记录) 对象, 这里传入的 Class 对象就是当前打印的类
Logger logger = LoggerFactory.getLogger(CrowdTest.class);
// 2. 根据不同日志级别打印日志
logger.debug("Hello I an Debug Level!!!");
logger.debug("Hello I an Debug Level!!!");
logger.debug("Hello I an Debug Level!!!");
logger.info("Hello I an Debug Level!!!");
logger.info("Hello I an Debug Level!!!");
logger.info("Hello I an Debug Level!!!");
logger.warn("Hello I an Debug Level!!!");
logger.warn("Hello I an Debug Level!!!");
logger.warn("Hello I an Debug Level!!!");
logger.error("Hello I an Debug Level!!!");
logger.error("Hello I an Debug Level!!!");
logger.error("Hello I an Debug Level!!!");
}
@Test
public void testInsertAdmin() {
Admin admin = new Admin(null, "tom", "123123", "汤姆", "tom@qq.com", null);
int insert = adminMapper.insert(admin);
// 在实际开发中, 所有想查看数值的地方都所有 sysout 方式打印, 会给项目上线运行带来问题
// sysout 本质上是一个 IO 操作, 通过 IO 的操作是比较消耗性能的, 如果项目中 sysout 多, 那么对性能的影响就比较大了
// 即使上线前专门花时间删除代码中的 sysout, 也就可能有遗漏, 而且非常麻烦
// 而如果使用日志系统, 那么通过日志级别就可以批量的控制信息的打印
System.out.println("受影响的行数" + insert);
}
@Test
public void testConnection() throws SQLException {
// 1. 通过数据源对象获取数据源连接
Connection connection = dataSource.getConnection();
// 2. 打印数据库连接
System.out.println(connection);
}
}
- 执行后保存操作会回滚
- 事务配置成功
- 将 logback.xml 日志级别改为
DEBUG
-
执行 testTx 方法
-
- 可以看到 SQL 下面的回滚信息
8.4.3 注意
- 在基于 XML 的声明式事务中, 事务属性的 tx:method 是必须配置的, 如果某个方法没有配置对应的 tx:method, 那么事务对这个方法不会生效