事物的失效

来源:互联网 发布:算法工程师面试题 编辑:程序博客网 时间:2024/06/06 01:22

我们开发项目做一些操作避免不了使用事物来保证数据的可靠性,但是有时候我们基于注解的事物有的时候可能和我们的预期不太一样,这是怎么回事呢?

首先我们来看一段代码,

@Componentpublic class TransactionalTest {    @Resource    CatDao catDao;    @Resource    TransactionalTest12 transactionalTest12;    @Resource    UserDao userDao;    //这里采用默认传递级别    @Transactional    public void saveCatAndUser(){        //一些逻辑操作 这里略过        System.out.println("开始保存Cat");        try {            saveUser();        }catch (Exception e){            System.out.println("已经处理捕获的异常");        }        Cat cat = new Cat();        cat.setCatAge(12);        cat.setName("Tom");        catDao.saveCat(cat);        //之后一些操作  这里省略    }    @Transactional(propagation=Propagation.REQUIRES_NEW)    public void saveUser(){        //一些逻辑操作 等等 略        System.out.println("开始保存User");        User user = new User();        user.setAge(25);        user.setName("Jack");        userDao.saveUser(user);        //这里只是模拟抛出异常        throw new RuntimeException("保存User失败");    }}
看上面的代码,我们在一个有事物的方法中saveCatAndUser中调用了另一个有事物的方法
saveUser(并且方法级别是新开一个事物)按理说这会有两个事物产生,是彼此独立的,首先这个
saveUser方法抛出了一个异常,这个事物肯定是会回滚的,由于saveCatAndUser捕获了异常没有抛出,这个方法
没有异常应该是提交成功的,这样看来应该是cat有数据而user没有数据,到底结果是什么样的呢,我们编写一个测试类:
@Testpublic void sakljd(){    System.out.println("准备测试事物");    transactionalTest.saveCatAndUser();}
运行完之后输出结果:
准备测试事物[2017-12-18 22:41:49]-[main-DEBUG]-[org.springframework.beans.factory.support.AbstractBeanFactory-doGetBean(246)]: Returning cached instance of singleton bean 'transactionManager'[2017-12-18 22:41:49]-[main-DEBUG]-[org.springframework.transaction.support.AbstractPlatformTransactionManager-getTransaction(365)]: Creating new transaction with name [transactional.service.TransactionalTest.saveCatAndUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''Mon Dec 18 22:41:50 CST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.Mon Dec 18 22:41:50 CST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.[2017-12-18 22:41:50]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doBegin(248)]: Acquired Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java] for JDBC transaction[2017-12-18 22:41:50]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doBegin(265)]: Switching JDBC Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java] to manual commit开始保存Cat开始保存User已经处理捕获的异常[2017-12-18 22:41:50]-[main-DEBUG]-[org.springframework.transaction.support.AbstractPlatformTransactionManager-processCommit(752)]: Initiating transaction commit[2017-12-18 22:41:50]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doCommit(310)]: Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java][2017-12-18 22:41:50]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doCleanupAfterCompletion(368)]: Releasing JDBC Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java] after transaction[2017-12-18 22:41:50]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceUtils-doReleaseConnection(327)]: Returning JDBC Connection to DataSource[2017-12-18 22:41:50]-[main-DEBUG]-[org.springframework.test.context.support.DirtiesContextTestExecutionListener-afterTestMethod(86)]: After test method: context [[TestContext@783e6358 testClass = MybatisAndTransactional, testInstance = MybatisAndTransactional@17550481, testMethod = sakljd@MybatisAndTransactional, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@735f7ae5 testClass = MybatisAndTransactional, locations = '{classpath:springTransactional.xml}', classes = '{}', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader']]], class dirties context [false], class mode [null], method dirties context [false].[2017-12-18 22:41:50]-[main-DEBUG]-[org.springframework.test.context.support.DirtiesContextTestExecutionListener-afterTestClass(113)]: After test class: context [[TestContext@783e6358 testClass = MybatisAndTransactional, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [MergedContextConfiguration@735f7ae5 testClass = MybatisAndTransactional, locations = '{classpath:springTransactional.xml}', classes = '{}', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader']]], dirtiesContext [false].[2017-12-18 22:41:50]-[Thread-0- INFO]-[org.springframework.context.support.AbstractApplicationContext-doClose(1042)]: Closing org.springframework.context.support.GenericApplicationContext@59e84876: startup date [Mon Dec 18 22:41:49 CST 2017]; root of context hierarchy[2017-12-18 22:41:50]-[Thread-0-DEBUG]-[org.springframework.beans.factory.support.AbstractBeanFactory-doGetBean(246)]: Returning cached instance of singleton bean 'sqlSessionFactory'[2017-12-18 22:41:50]-[Thread-0-DEBUG]-[org.springframework.beans.factory.support.AbstractBeanFactory-doGetBean(246)]: Returning cached instance of singleton bean 'lifecycleProcessor'[2017-12-18 22:41:50]-[Thread-0- INFO]-[org.springframework.beans.factory.support.DefaultSingletonBeanRegistry-destroySingletons(444)]: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@799d4f69: defining beans [org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0,userDaoImpl,catService,transactionalTest,transactionalTest12,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,dataSource,org.mybatis.spring.mapper.MapperScannerConfigurer#0,sqlSessionFactory,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,transactionManager,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,catDao,userDao]; root of factory hierarchy[2017-12-18 22:41:50]-[Thread-0-DEBUG]-[org.springframework.beans.factory.support.DefaultSingletonBeanRegistry-destroyBean(500)]: Retrieved dependent beans for bean 'userService': [MybatisAndTransactional]

我们看到这个打印信息我们发现只创建了一个事物,并没有创建两个事物,我们看数据库也是两张表中都有数据,怎么回事呢?
这个结果表明了事物没有按我们想的来执行,原因是什么我来解释一下:
这个其实是springaop搞的鬼,这种以注解形式的@Transactional来声明事物其实就是用AOP来实现的,
那为什么AOP会出现这种情况呢,这就要从Aop的底层实现来说起,我们知道SpringAop的底层实现使用动态代理来做的
那我们来看一下动态代理的实现原理:()
我们先来编写一个动态代理的实现:
定义一个女明星接口:
package transactional.proxy;/** * 女明星类 * Created by 340092 on 2017/12/18. */public interface GirlStar {    //表演    void show();    //签名    void signature();}
定义一个苍老师的实现类:
package transactional.proxy;/** * cang老师类 * Created by 340092 on 2017/12/18. */public class Cang implements GirlStar {    public void show() {        System.out.println("表演节目");    }    public void signature() {        System.out.println("给粉丝签名");    }}
定义一个代理类:
package transactional.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * 代理类 * Created by 340092 on 2017/12/18. */public class MyProxy implements InvocationHandler {    private Object o;    public MyProxy(Object o) {        this.o = o;    }    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("化妆");        Object invoke = method.invoke(o, args);        System.out.println("回家");        return invoke;    }    //生成代理类    public Object getProxy(){        return Proxy.newProxyInstance(this.getClass().getClassLoader(),o.getClass().getInterfaces(),this);    }}

用一个main方法测试:
package transactional.proxy;/** * Created by 340092 on 2017/12/18. */public class Main {    public static void main(String[] args) {        GirlStar girlStar = new Cang();        MyProxy proxy = new MyProxy(girlStar);        GirlStar girl= (GirlStar) proxy.getProxy();        girl.show();        girl.signature();    }}
输出的结果是:
化妆表演节目回家化妆给粉丝签名回家
我们看到这两个方法都得到了增强.因为这两个方法都是动态生成的代理类来调用,方法得到了增强
如果我们这样做呢,在show方法里调用signature呢,我们来试一下
package transactional.proxy;/** * cang老师类 * Created by 340092 on 2017/12/18. */public class Cang implements GirlStar {    public void show() {        System.out.println("表演节目");        signature();    }    public void signature() {        System.out.println("给粉丝签名");    }}

package transactional.proxy;/** * Created by 340092 on 2017/12/18. */public class Main {    public static void main(String[] args) {        GirlStar girlStar = new Cang();        MyProxy proxy = new MyProxy(girlStar);        GirlStar girl= (GirlStar) proxy.getProxy();        girl.show();    }}
输出的结果是
化妆表演节目给粉丝签名回家
我们看到这个结果,那是因为show方法是代理类来调用,而signature()是cang本身调用(this.),和代理类就没有关系
了,所以方法没有得到增强,这个和之前那个事物是一样的,因为都是这个原理所以没有开启另外那个事物,没有交给
代理类来做,所以没有得到事物的增强,那这种方式如何解决呢,(最简单一种方式放在不相干的两个类中,没有this)
我们不这样做,我们用另外一种方式:
首先在配置文件上加上
<aop:aspectj-autoproxy expose-proxy="true"/>
这个标识把aop动态代理暴露到spring上下文放在了ThreadLocal中这样应用程序就可以用AopContext.currentProxy();获取这个代理类,我们把代码改造一下:
首先在配置文件上加上<aop:aspectj-autoproxy expose-proxy="true"/>
然后改一下事物那个类:
package transactional.service;import org.springframework.aop.framework.AopContext;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import transactional.bean.Cat;import transactional.bean.User;import transactional.dao.CatDao;import transactional.dao.UserDao;import javax.annotation.Resource;import java.lang.reflect.Proxy;/** * Created by 340092 on 2017/11/23. */@Componentpublic class TransactionalTest {    @Resource    CatDao catDao;    @Resource    TransactionalTest12 transactionalTest12;    @Resource    UserDao userDao;    //这里采用默认传递级别    @Transactional    public void saveCatAndUser(){        //一些逻辑操作 这里略过        System.out.println("开始保存Cat");        Cat cat = new Cat();        cat.setCatAge(12);        cat.setName("Tom");        catDao.saveCat(cat);        try {            TransactionalTest transactionalTest= (TransactionalTest) AopContext.currentProxy();            transactionalTest.saveUser();        }catch (Exception e){            System.out.println("已经处理捕获的异常");        }        //之后一些操作  这里省略    }    @Transactional(propagation=Propagation.REQUIRES_NEW)    public void saveUser(){        //一些逻辑操作 等等 略        System.out.println("开始保存User");        User user = new User();        user.setAge(25);        user.setName("Jack");        userDao.saveUser(user);        //这里只是模拟抛出异常        throw new RuntimeException("保存User失败");    }
控制台输出:
准备测试事物[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.beans.factory.support.AbstractBeanFactory-doGetBean(246)]: Returning cached instance of singleton bean 'transactionManager'[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.transaction.support.AbstractPlatformTransactionManager-getTransaction(365)]: Creating new transaction with name [transactional.service.TransactionalTest.saveCatAndUser]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''Mon Dec 18 23:34:19 CST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.Mon Dec 18 23:34:19 CST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doBegin(248)]: Acquired Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java] for JDBC transaction[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doBegin(265)]: Switching JDBC Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java] to manual commit开始保存Cat[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.beans.factory.support.AbstractBeanFactory-doGetBean(246)]: Returning cached instance of singleton bean 'transactionManager'[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.transaction.support.AbstractPlatformTransactionManager-handleExistingTransaction(415)]: Suspending current transaction, creating new transaction with name [transactional.service.TransactionalTest.saveUser]Mon Dec 18 23:34:19 CST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doBegin(248)]: Acquired Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java] for JDBC transaction[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doBegin(265)]: Switching JDBC Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java] to manual commit开始保存User[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.transaction.support.AbstractPlatformTransactionManager-processRollback(843)]: Initiating transaction rollback[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doRollback(325)]: Rolling back JDBC transaction on Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java][2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doCleanupAfterCompletion(368)]: Releasing JDBC Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java] after transaction[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceUtils-doReleaseConnection(327)]: Returning JDBC Connection to DataSource[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.transaction.support.AbstractPlatformTransactionManager-cleanupAfterCompletion(1012)]: Resuming suspended transaction after completion of inner transaction已经处理捕获的异常[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.transaction.support.AbstractPlatformTransactionManager-processCommit(752)]: Initiating transaction commit[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doCommit(310)]: Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java][2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceTransactionManager-doCleanupAfterCompletion(368)]: Releasing JDBC Connection [jdbc:mysql://localhost:3306/test3, UserName=root@localhost, MySQL Connector Java] after transaction[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.jdbc.datasource.DataSourceUtils-doReleaseConnection(327)]: Returning JDBC Connection to DataSource[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.test.context.support.DirtiesContextTestExecutionListener-afterTestMethod(86)]: After test method: context [[TestContext@783e6358 testClass = MybatisAndTransactional, testInstance = MybatisAndTransactional@17550481, testMethod = sakljd@MybatisAndTransactional, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@735f7ae5 testClass = MybatisAndTransactional, locations = '{classpath:springTransactional.xml}', classes = '{}', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader']]], class dirties context [false], class mode [null], method dirties context [false].[2017-12-18 23:34:19]-[main-DEBUG]-[org.springframework.test.context.support.DirtiesContextTestExecutionListener-afterTestClass(113)]: After test class: context [[TestContext@783e6358 testClass = MybatisAndTransactional, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [MergedContextConfiguration@735f7ae5 testClass = MybatisAndTransactional, locations = '{classpath:springTransactional.xml}', classes = '{}', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader']]], dirtiesContext [false].[2017-12-18 23:34:19]-[Thread-0- INFO]-[org.springframework.context.support.AbstractApplicationContext-doClose(1042)]: Closing org.springframework.context.support.GenericApplicationContext@59e84876: startup date [Mon Dec 18 23:34:18 CST 2017]; root of context hierarchy[2017-12-18 23:34:19]-[Thread-0-DEBUG]-[org.springframework.beans.factory.support.AbstractBeanFactory-doGetBean(246)]: Returning cached instance of singleton bean 'sqlSessionFactory'[2017-12-18 23:34:19]-[Thread-0-DEBUG]-[org.springframework.beans.factory.support.AbstractBeanFactory-doGetBean(246)]: Returning cached instance of singleton bean 'lifecycleProcessor'[2017-12-18 23:34:19]-[Thread-0- INFO]-[org.springframework.beans.factory.support.DefaultSingletonBeanRegistry-destroySingletons(444)]: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@76329302: defining beans [org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0,userDaoImpl,catService,transactionalTest,transactionalTest12,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.aop.config.internalAutoProxyCreator,dataSource,org.mybatis.spring.mapper.MapperScannerConfigurer#0,sqlSessionFactory,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,transactionManager,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,catDao,userDao]; root of factory hierarchy[2017-12-18 23:34:19]-[Thread-0-DEBUG]-[org.springframework.beans.factory.support.DefaultSingletonBeanRegistry-destroyBean(500)]: Retrieved dependent beans for bean 'userService': [MybatisAndTransactional]
可以看到创建了两个事物,其中一个回滚操作,另外一个提交,表中数据也和我们预期的一样,
这样就实现了我们想要的效果.

原创粉丝点击