手写转账代理
Service类
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetName, float money) {
//1.根据名称查询转出行户
Account sourceAccount = accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account targetAccount = accountDao.findAccountByName(targetName);
//3.转出账户减钱
sourceAccount.setMoney(sourceAccount.getMoney() - money);
//4.转入账户加钱
targetAccount.setMoney(targetAccount.getMoney() + money);
//5.更新转出账户
accountDao.updateAccount(sourceAccount);
int i = 1 / 0;
//6.更新转入账户
accountDao.updateAccount(targetAccount);
}
}
以上为需要实现事务控制的事务层,再附上事务控制的代码
获取连接的工具类
/**
* 连接的工具类,用于从数据源获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Autowired
private DataSource dataSource;
public Connection getThreadConnection(){
//1.先从ThreadLocal上获取
Connection connection = threadLocal.get();
//2.判断当前线程上是否有连接
if(connection == null){
//3.从数据源中获取一个连接并绑定线程
try {
connection = dataSource.getConnection();
threadLocal.set(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return connection;
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
threadLocal.remove();
}
}
事务管理类
/**
* 和事务管理相关的工具类
* 开启事务
* 提交事务
* 回滚事务
* 释放连接
*/
public class TransactionManager {
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
private ConnectionUtils connectionUtils;
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();
connectionUtils.removeConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
说明:为什么要使用连接工具类把连接与线程绑定?
因为QueryRunner配置的是多例的,每次使用时都会创建一个新的连接,一个连接每次操作都有自己的事务,我们要控制事务就需要把连接固定,并手动控制事务。因为用了连接池,所以当需要就算QueryRunner是单例,每次获取的连接也不一定是同一个连接,将线程与连接绑定后,则可以保证事务前后一致性。
- 创建一个工厂类,用于获取经过代理的service,代理转账方法
/**
* 用于创建Service代理对象的工厂
*/
public class BeanFactory {
public void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
private IAccountService accountService;
private TransactionManager transactionManager;
public final void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* 获取Service的代理对象
* @return
*/
public IAccountService getAccountService(){
IAccountService proxyAccountService = (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
/**
* 添加事务的支持
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
if ("transfer".equals(method.getName())) {
String sourceName = (String) args[0];
String targetName = (String) args[1];
Float money = (Float) args[2];
try {
transactionManager.beginTransaction();
res = method.invoke(accountService,sourceName,targetName,money);
transactionManager.commit();
} catch (Exception e) {
System.out.println("事务回滚");
transactionManager.rollback();
e.printStackTrace();
} finally {
transactionManager.release();
}
}
return res;
}
});
return proxyAccountService;
}
}
- 配置代理工厂和代理的service
<bean id="accountService" class="cn.doubly.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<!--<property name="transactionManager" ref="transactionManager"/>-->
</bean>
<!--配置BeanFacotry-->
<bean id="beanFactory" class="cn.doubly.factory.BeanFactory">
<property name="accountService" ref="accountService"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>
<!--配置代理的Service-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
- 通过代理的service
@Resource(name = "proxyAccountService")
private IAccountService proxyAccountService ;
@Test
public void testTransfer(){
proxyAccountService.transfer("aaa","bbb",100);
}
至此,代理成功,可以成功控制事务!
AOP
AOP是面向切面编程,使用动态代理的技术。
导包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
完成Service,以及utils方法
配置bean.xml
<!-- 配置IOC,把Service注入-->
<bean id="accountService" class="cn.doubly.service.impl.AccountService"></bean>
<!-- spring中基于xml的AOP配置
1.把通知Bean交给Spring来管理
2.使用aop:config标签开始aop的配置
3.使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的id
4.在aop:aspect标签内部使用对应的标签来配置通知的类型
aop:before前置通知
method属性:用于指定Logger类中那个方法是前置通知
pointcut属性:用于指定切入点表达式,改表达式的含义只的是对业务层中那些方法增强
切入点表达式:
execution(表达式)
表达式: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
-->
<!-- 配置Logger-->
<bean id="logger" class="cn.doubly.utils.Logger"></bean>
<!-- 配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,且建立通知方法和切入点方法关联-->
<aop:before method="log" pointcut="execution(public void cn.doubly.service.impl.AccountService.addAccount())" ></aop:before>
</aop:aspect>
</aop:config>
切入点表达式:
execution(表达式)
表达式: 访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
- 访问修饰符可以省略
- 返回值可以使用通配符,表示返回任意值
- 包名可以使用通配符表示任意包。但是有几级包就需要写多少个*.
- 可以使用…表示当前包及其子包,
* *.AccountServiceImpl.addAccount()
- 类名和方法名都可以使用
*
实现通配,* *..*.*()
- 参数列表可以直接写数据类型,也可以使用…表示有无参数均可,
* *..*.*(..)
- 全通配:
* *..*.*(..)
实际开发中切入点表达式的通用写法,切到业务层实现类下的所有方法:* cn.doubly.service.impl.*.*(..)
通知分类
- 前置通知
- 后置通知
- 异常通知
- 最终通知
<!--配置AOP-->
<aop:config>
<!--配置PointCut,切入点-->
<aop:pointcut id="pointCut" expression="execution(* cn.doubly.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--前置通知-->
<aop:before method="beforeAction" pointcut-ref="pointCut" ></aop:before>
<!--后置通知-->
<aop:after-returning method="afterAction" pointcut-ref="pointCut"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="afterThrow" pointcut-ref="pointCut"></aop:after-throwing>
<!--最终通知-->
<aop:after method="finalAction" pointcut-ref="pointCut"></aop:after>
</aop:aspect>
</aop:config>
- 环绕通知:整个Invoke方法在执行就是环绕通知
<aop:aspect id="logAdvice" ref="logger">
<!--配置环绕通知-->
<aop:around method="arroundAction" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
public Object arroundAction(ProceedingJoinPoint pjp){
Object res = null;
try {
Object[] args = pjp.getArgs();
System.out.println("arroundAction");//前置
res = pjp.proceed(args);
System.out.println("arroundAction");//后置
return res;
} catch (Throwable throwable) {
System.out.println("arroundAction");//异常
throw new RuntimeException();
}finally {
System.out.println("arroundAction");//最终
}
}
注解AOP
<?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"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="cn.doubly" ></context:component-scan>
<!--开启aop注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
package cn.doubly.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component("logger")
@Aspect //表示当前类是一个通知类
public class Logger {
@Pointcut("execution(* cn.doubly.service.impl.*.*(..))")
private void ptl(){}
@Before("ptl()")
public void beforeAction(){
System.out.println("before");
}
@AfterReturning("ptl()")
public void afterAction(){
System.out.println("after");
}
@AfterThrowing("ptl()")
public void afterThrow(){
System.out.println("afterThrow");
}
@After("ptl()")
public void finalAction(){
System.out.println("finalAction");
}
@Around("ptl()")
public Object arroundAction(ProceedingJoinPoint pjp){
Object res = null;
try {
Object[] args = pjp.getArgs();
System.out.println("arroundAction");//前置
res = pjp.proceed(args);
System.out.println("arroundAction");//后置
return res;
} catch (Throwable throwable) {
System.out.println("arroundAction");//异常
throw new RuntimeException();
}finally {
System.out.println("arroundAction");//最终
}
}
}
基于xml的声明式事务控制
spring中基于xml的声明式事务控制配置步骤
1.配置事务管理器
2.配置事务的通知
此时我们需要导入事务的约束,tx命名空间和约束,同时也要aop的
使用tx:advice
标签配置事务通知
属性:
id
:给事务通知起一个唯一标识
transaction-manager
:给事务通知提供一个事务管理器引用
3.配置AOP中的通用切入点表达式
4.建立事务通知和切入点表达式的对应关系
5.配置事务的属性
是在事务的通知tx:advice
标签的内部
<?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:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="cn.doubly"></context:component-scan>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study?serverTimezone=Asia/Shanghai&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="accountDao" class="cn.doubly.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--1.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--5.配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--3.配置AOP中的通用切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* cn.doubly.service.impl.*.*(..))"/>
<!--4.建立事务通知和切入点表达式的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
</beans>
事务属性配置
isolation
:用于指定事务的隔离级别。默认值是DEFAULT
,表示使用数据库的默认隔离级别
propagation
:用于指定事务的传播行为。默认值是REQUIRED
,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS
.
read-only
:用于指定事务是否只读。只有查询方法才能设置为true
。默认值是false
,表示读写。
timeout
:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for
:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。
no-rollback-for
:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值,表示任何异常都回滚。
基于注解的声明式事务控制
spring中基于注解的声明式事务控制配置步骤
1.配置事务管理器
2.开启Spring对注解事务的支持
3.在需要事务支持的地方使用@Transactional注解
<?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:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="cn.doubly"></context:component-scan>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study?serverTimezone=Asia/Shanghai&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true) //注解配置事务
public class AccountServiceImpl implements IAccountService {
@Resource(name = "accountDao")
private IAccountDao accountDao;
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
@Transactional(propagation = Propagation.REQUIRED,readOnly = false) //注解不同属性配置
public void transfer(String source, String target, Float money) {
System.out.println("transfer...");
Account sourceAccount = accountDao.findAccountByName(source);
Account targetAccount = accountDao.findAccountByName(target);
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
accountDao.updateAccount(sourceAccount);
// int i = 1/0;
accountDao.updateAccount(targetAccount);
}
}