对应工程heima-aop-xml,heima-annotation-aop
前期准备
两个新的配置信息Connection的工具类,事务管理器。
通过使用aop来进行事务的控制,在调用业务层的transfer()方法之前开启一个事务,执行之后就进行事务提交,最后释放连接;如果出现异常就会出现事务回滚导致转账失败。
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="cn.smm.utils.ConnectionUtils">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="txManager" class="cn.smm.utils.TransactionManager">
<!-- 注入connectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!-- 配置aop -->
<aop:config>
<!-- 配置通用切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* cn.smm.service.impl.*.*(..))"/>
<aop:aspect id="txAdvice" ref="txManager">
<!-- 配置前置通知:开启事务 -->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!-- 配置后置通知:提交事务 -->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:回滚事务 -->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:释放连接 -->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
业务层代码
业务层加了一个转账方法
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("转账开始了。。。。。");
// 2.1.根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
// 2.2.根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
// 2.3.转出账户减钱
source.setMoney(source.getMoney() - money);
// 2.4.转入账户加钱
target.setMoney(target.getMoney() + money);
// 2.5.更新转出账户
accountDao.updateAccount(source);
// 如果在这里加一个异常,测试是否转出的人钱扣了,收的人没有收到钱
// 需要对账户事务进行同一线程连接管理,新建connectionutils
int i = 1 / 0;
// 2.6.更新转入账户
accountDao.updateAccount(target);
}
工具类代码
就是需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个可以控制事务的对象
//连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
// 获取当前线程上的连接
public Connection getThreadConnection() {
try {
// 1.先从ThreadLocal上获取
Connection conn = tl.get();
// 2,判断当前线程上是否有连接
if (conn == null) {
// 3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
// 4.返回当前线程的连接
return conn;
} catch (Exception w) {
throw new RuntimeException(w);
}
}
// 把连接和线程解绑
public void removeConnection(){
tl.remove();
}
}
事务管理代码
事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
//和事务管理相关的工具类,它包含了开启事务,提交事务,回滚事务,释放连接
public class TransactionManager {
private ConnectionUtils connectionUtils;
// 采用set方法进行依赖注入
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
// 开启事务
public void beginTransaction() {
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (Exception w) {
w.printStackTrace();
}
}
// 提交事务
public void commit() {
try {
connectionUtils.getThreadConnection().commit();
} catch (Exception w) {
w.printStackTrace();
}
}
// 回滚事务
public void rollback() {
try {
connectionUtils.getThreadConnection().rollback();
} catch (Exception w) {
w.printStackTrace();
}
}
// 释放连接
public void release() {
try {
connectionUtils.getThreadConnection().close();//此处的close不是关闭,而是返回连接池中
connectionUtils.removeConnection();
} catch (Exception w) {
w.printStackTrace();
}
}
}
junit单位代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer() {
as.transfer("aa", "bb", 100f);
}
}
}
注解版本
bean配置文件需要配置spring创建容器时要扫描的包,开启spring对注解aop的支持,同时使用xml配置QueryRunner和配置数据源要比注解的比较方便。
<!-- 配置QueryRunner -->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 连接数据库的必备信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
其他层的代码使用以往的常规操作,在ConnectionUtils那里进行如下配置
@Component("connectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
在TransactionManager中进行如下配置
@Component("txManager")
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
@Pointcut("execution(* cn.smm.service.impl.*.*(..))")
private void pt1(){}