spring-04-3

大家不懂可以留言我,虽然我也很菜

事务的基本知识,我专门有一篇博客写了: 事务基础

spring事务管理

spring为我们提供给了事务管理,我们就不需要手动去开启事务,关闭事务了,我们只需要配置一下就可以了,它就会帮我们把事务放在service层去.

spring提供了事务jar包: spring-tx-5.0.2-RELEASE.jar,里面大部分都是接口,其中有几个接口我们需要注意一下子.

  • SavepointManager: 保存点管理器,比如说,我这个事务里面包括四个操作,前两个如果对了就一定要提交,后两个无所谓,那么我们就可以在前两个操作后设置一个保存点savepoint对象,然后当第三个操作出错了之后,回滚到保存点对象的位置,前面的提交,这样就可以保证前两个操作一定提交了.

  • **PlatformTransactionManager:**平台事务管理器, spring要管理事务,必须使用事务管理器,进行事务配置时,必须配置事务管理器.

  • **TransactionStatus:**事务状态,spring用于记录当前事务运行状态,例如: 是否有保存点,事务是否完成等,spring根据状态进行相应操作.

  • **TransactionDefinition:**事务详情(包括事务定义,事务属性等),spring用于确定事务具体详情,例如: 隔离级别,是否只读,超时时间等,进行事务配置时,**必须配置详情,**spring会将配置项封装到对象实例中.

jdbc管理数据库通过spring管理事务

  • 导入包: jdbc开发数据库的话,要导入jdbc对应的包(包里面放的是spring事务中接口的实现类): spring-jdbc-5.0.2.RELEASE.jar,以及spring事务管理的jar包.

  • 通过spring-jdbc包下面的PlatformTransactionManager接口的实现类DataSourceTransactionManager作为事务管理器来实现事务管理

TransactionDefinition接口中的成员变量,隔离的变量在开头链接的博客里面有,这里只看传播行为的几个变量

  • 传播行为用于解决在两个业务中如何共享事务.

  • PROPAGATION_REQUIRED(默认值),required表示必须,即支持当前事务,A如果有事务,B将使用该事务,如果A没有事务,B将创建一个新的事务.

  • PROPAGATION_SUPPORTS,support表示支持,即支持当前事务,A如果有事务,B将使用该事务,如果A没有事务,B将以非事务执行.

  • PROPAGATION_MANDATORY,mandatory表示强制,即支持当前事务,A如果有事务,B将使用该事务,如果A没有事务,B将抛出异常.

  • PROPAGATION_REQUIRES_NEW,requires_new表示必须新的,A如果有事务,将A的事务挂起,B创建一个新的事务执行,如果A没有事务,B创建一个新的事务执行.

  • PROPAGATION_NOT_SUPPORTED,not_supported表示不支持,A如果有事务,将A的事务挂起,B以非事务执行,如果A没有事务,B以非事务执行.

  • PROPAGATION_NEVER,never表示从不,A如果有事务,B将抛出异常,A如果没有事务,B将以非事务执行.

  • PROPAGATION_NESTED,nested表示嵌套,A和B底层采用保存点机制,形成嵌套事务.

转账案例_手动管理事务(了解就可以了 )

实现过程

  • 创建数据库表并插入两条数据
		CREATE TABLE account(
		 id INT PRIMARY KEY AUTO_INCREMENT,
		 username VARCHAR(15),
		 money INT
		);
		
		INSERT INTO account(username, money) VALUES("user1", 1000);
		INSERT INTO acCOUNT(username, money) VALUES("user2", 1000);
  • 导入jar包:创建的spring项目的话,大部分jar包它都有,我们只需要导入c3p0连接池的jar包和数据库驱动的jar包就可以了,链接如下: jar包

  • Dao层

		public class AccountDao extends JdbcDaoSupport {
		    //进账
		    public void in(int money, String inner) {
		        String sql = "update account set money = money + ? where username = ?";
		
		        super.getJdbcTemplate().update(sql,money, inner);
		
		        System.out.println("已成功向"+ inner + "存钱" + money + "元");
		    }
		
		    //出账
		    public void out(int money, String outer) {
		        String sql = "update account set money = money - ? where username = ?";
		
		        super.getJdbcTemplate().update(sql,money, outer);
		
		        System.out.println("已成功从"+ outer + "取钱" + money + "元");
		    }
		}
  • service层
			public class AccountService {
		    //生成dao层对象,方便等会调用方法
		    private AccountDao accountDao;
		
		    //提供set方法,等会通过spring注入AccountDao对象
		    public void setAccountDao(AccountDao accountDao) {
		        this.accountDao = accountDao;
		    }
		
		    //spring配置事务模板
		    private TransactionTemplate transactionTemplate;
		
		    //提供set方法,由spring注入
		    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
		        this.transactionTemplate = transactionTemplate;
		    }
		
		    //转账方法
		    public void transfer(String inner, String outer, int money) {
		        //执行事务,出错之后就会自动回滚
		        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
		            @Override
		            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
		                //进账
		                accountDao.in(money, inner);
		
		                int i = 10 / 0;
		
		                //出账
		                accountDao.out(money, outer);
		            }
		        });
		    }
		}
  • 测试类
		public class UnitClass {
		
		    @Test
		    public void test() {
		        //获取容器对象
		        ApplicationContext context = new ClassPathXmlApplicationContext("beans1.xml");
		
		        //获取service层对象
		        AccountService service = (AccountService) context.getBean("accountService");
		
		        //调用方法
		        service.transfer("user1", "user2", 500);
		    }
		}
  • 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 
								http://www.springframework.org/schema/context/spring-context.xsd">
		
		     <!--让spring去读我们的连接数据库的配置文件-->
		    <context:property-placeholder location="db.properties"></context:property-placeholder>
		    <!--配置数据源对象,因为jdbctemplate必须要用到数据源-->
		    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		        <property name="driverClass" value="${driverClass}"></property>
		        <property name="jdbcUrl" value="${jdbcUrl}"></property>
		        <property name="user" value="${user}"></property>
		        <property name="password" value="${password}"></property>
		    </bean>
		
		    <!--配置事务管理器-->
		    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		        <!--通过看源码知道,它需要注入一个数据源属性-->
		        <property name="dataSource" ref="dataSource"></property>
		    </bean>
		
		    <!--配置一个事务模板对象-->
		    <bean id="template" class="org.springframework.transaction.support.TransactionTemplate">
		        <!--通过看源码知道,我们还需要配置一个事务管理器-->
		        <property name="transactionManager" ref="txManager"></property>
		    </bean>
		
		    <!--创建dao层对象,同时给其注入数据源,因为jdbctemplate的创建需要数据源-->
		    <bean id="dao" class="transaction_example1.dao.AccountDao">
		        <property name="dataSource" ref="dataSource"></property>
		    </bean>
		
		    <!--给service层注入dao层对象-->
		    <bean id="accountService" class="transaction_example1.service.AccountService">
		        <property name="accountDao" ref="dao"></property>
		        <!--配置事务管理模板-->
		        <property name="transactionTemplate" ref="template"></property>
		    </bean>
		</beans>
  • properties配置文件
		driverClass=com.mysql.jdbc.Driver
		jdbcUrl=jdbc:mysql://localhost:3306/spring_day04
		user=root
		password=root

转账案例_spring用工厂bean生成代理半自动管理事务(了解)

注意:

  • 这里的动态代理就是JDK自带的那种,必须针对接口,关于动态代理,参考这篇博客: 动态代理.

  • xml中属性详情必须配,不能少

  • 一个很恼火的错误:**com.sun.proxy.$Proxy0 cannot be cast to **,这就是你在容器中获取service代理对象或者自己创建代理对象的时候,生成的对象不是接口类型,而是对象类型导致的,例子如下:

			有一个接口User,它的实现类是UserImpl,现在要生成代理对象
			1. UserImpl u1 = (UserImpl)Proxy.newProxyInstance(参数省略...);
				这个就会报错--`com.sun.proxy.$Proxy0 cannot be cast to`
			2. User u2 = (User)Proxy.newProxyInstance(参数省略...);
				这个写法才是正确的,因为jdk的动态代理是针对接口的,记住记住!!!!

spring提供了管理事务的代理工厂bean,叫TransactionProxyFactoryBean,我们通过代理工厂的话就不用配置事务模板了,代码相对于纯手动管理要简单一些哈哈哈

实现过程

  • service类
		/**
		 * 在service层中调用dao层的方法
		 */
		public class AccountService implements Account{
		
		    //生成dao层对象,方便等会调用方法
		    private AccountDao accountDao;
		
		    //提供set方法,等会通过spring注入AccountDao对象
		    public void setAccountDao(AccountDao accountDao) {
		
		        this.accountDao = accountDao;
		    }
		
		
		    //转账方法
		    public void transfer(String inner, String outer, int money) {
		        //进账
		        accountDao.in(money, inner);
		
		        int i = 10 / 0;
		
		        //出账
		        accountDao.out(money, outer);
		    }
		}
  • 测试类
		public class UnitClass {
		
		    @Test
		    public void test() {
		        //获取容器对象
		        ApplicationContext context = new ClassPathXmlApplicationContext("beans2.xml");
		
		        //获取service层的代理对象,注意这里的Account必须是接口啊,不能是其实现类
		        Account service = (Account) context.getBean("proxyService");
		
		        //调用方法
		        service.transfer("user1", "user2", 500);
		    }
		}
  • 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 
								http://www.springframework.org/schema/context/spring-context.xsd">
		
		    <!--让spring去读我们的连接数据库的配置文件-->
		    <context:property-placeholder location="db.properties"></context:property-placeholder>
		    <!--配置数据源对象,因为jdbctemplate必须要用到数据源-->
		    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		        <property name="driverClass" value="${driverClass}"></property>
		        <property name="jdbcUrl" value="${jdbcUrl}"></property>
		        <property name="user" value="${user}"></property>
		        <property name="password" value="${password}"></property>
		    </bean>
		
		    <!--配置事务管理器-->
		    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		        <!--通过看源码知道,它需要注入一个数据源属性-->
		        <property name="dataSource" ref="dataSource"></property>
		    </bean>
		
		    <!--创建dao层对象,同时给其注入数据源,因为jdbctemplate的创建需要数据源-->
		    <bean id="dao" class="transaction_example2.dao.AccountDao">
		        <property name="dataSource" ref="dataSource"></property>
		    </bean>
		
		    <!--配置service,给service层注入dao层对象-->
		    <bean id="accountService" class="transaction_example2.service.AccountService">
		        <property name="accountDao" ref="dao"></property>
		    </bean>
		
		    <!--配置一个工厂代理-->
		    <bean id="proxyService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		        <!--配置接口-->
		        <property name="proxyInterfaces" value="transaction_example2.service.Account"></property>
		
		        <!--目标对象-->
		        <property name="target" ref="accountService"></property>
		
		        <!--切面对象不用写,spring帮我们做了-->
		        <!--配置事务管理器-->
		        <property name="transactionManager" ref="txManager"></property>
		
		        <!--配置事务属性,也就是事务详情
		            key:哪些方法需要使用事务,写方法名
		            value: 写事务的详情控制
		            格式: propagation(传播行为),isolation(隔离级别),readonly(是否只读)
		                   -Exception(异常回滚),+Exception(异常提交)
		                   前两个必须写,后面的可以不写
		                   当设置了只读之后,就说明这个代理对象只能读,不能写,所以对数据库的修改会失败.
		                 设置了+Exception之后,表示就算有异常,异常前面的语句照样提交,类似于保存点的功能
		            -->
		        <property name="transactionAttributes">
		            <props>
		                <!--有多个方法就写多个prop标签就好了-->
		                <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,+Exception</prop>
		            </props>
		        </property>
		    </bean>
		</beans>

spring基于AOP的事务管理(这个要掌握)

手动管理事务我们每次有新的业务方法都还要加到事务中去,还要自己手动生成对象调用,这就很麻烦,而半自动代理,还是要配置什么隔离级别一堆,还是麻烦,所以我们决定用AOP切面改进.

注意

  1. 如果我们service层中的类实现了接口,比如上面的AccountService,那么在容器内获取该对象的时候,就必须是接口类型的,因为这个就是通过代理对象完成的,只是不需要我们配置代理对象而已.

  2. 如果service层中的类没有实现接口,那么在在容器内获取该对象的时候,就直接写类类型就可以,不需要是接口类型,我的例子里实现了接口,所以只能是接口类型.

使用案例及步骤(相对于上面一个半自动管理来说,这里只修改xml文件):

  • 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: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">
		
		    <!--让spring去读我们的连接数据库的配置文件-->
		    <context:property-placeholder location="db.properties"></context:property-placeholder>
		    <!--配置数据源对象,因为jdbctemplate必须要用到数据源-->
		    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		        <property name="driverClass" value="${driverClass}"></property>
		        <property name="jdbcUrl" value="${jdbcUrl}"></property>
		        <property name="user" value="${user}"></property>
		        <property name="password" value="${password}"></property>
		    </bean>
		
		    <!--配置事务管理器-->
		    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		        <!--通过看源码知道,它需要注入一个数据源属性-->
		        <property name="dataSource" ref="dataSource"></property>
		    </bean>
		
		    <!--创建dao层对象,同时给其注入数据源,因为jdbctemplate的创建需要数据源-->
		    <bean id="dao" class="transaction_example3.dao.AccountDao">
		        <property name="dataSource" ref="dataSource"></property>
		    </bean>
		
		    <!--配置service,给service层注入dao层对象-->
		    <bean id="accountService" class="transaction_example3.service.AccountService">
		        <property name="accountDao" ref="dao"></property>
		    </bean>
		
		    <!--1. 配置作为通知使用的事务管理器,使用前要在头部加上标签的引用,就是xmlns那些-->
		    <tx:advice transaction-manager="txManager" id="myAdvice">
		        <!--配置事务详情:传播行为,隔离级别等,使用aop配置时都可以省略,自动使用默认值-->
		        <tx:attributes>
		            <!--方法就是我们要被事务控制的方法,我们这里是转账方法-->
		            <tx:method name="transfer"/>
		        </tx:attributes>
		    </tx:advice>
		
		    <!--2. 使用spring的aop标签来配置切面
		        这里切面中的通知就是事务,是由spring的事务管理器管理的
		        切入点就还是转账方法
		        在这里区分一下切入点和连接点,一个类中的所有方法都是连接点
		            但是只有要被通知进行功能增强的方法才叫切入点-->
		    <aop:config>
		        <!--配置一个切入点-->
		        <aop:pointcut id="myPoint" expression="execution(* transaction_example3.service.*.transfer(..))"></aop:pointcut>
		
		        <!--3. 将事务与切入点关联-->
		        <aop:advisor advice-ref="myAdvice" pointcut-ref="myPoint"></aop:advisor>
		    </aop:config>
		</beans>

spring基于注解的事务管理(这个也要掌握)

不过事务管理的话一般还是写在xml里比较好,因为要是service很多的话,也懒得写,在xml里还一目了然些,不过没关系啦,看自己习惯,咋都行.

在xml中配置使用注解进行事务管理的时候这里有个大坑:

  • 配置如下,其中特别注意配置事务管理器的id,这个id只能写transactionManager,不能写其他的,不能写其他的,写其他的就会报错: NoSuchBeanDefinitionException: No bean named ‘transactionManager’ available
	<!--1. 配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--通过看源码知道,它需要注入一个数据源属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2. 配置事务的注解驱动,告诉虚拟机由注解来完成事务操作-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

实现步骤(没写的就说明和上面的一致,这里只有service和xml变化了)

  • service代码
		/**
		 * 在service层中调用dao层的方法
		 *      注解写在类名上,表示该类所有方法都会被事务控制
		 *      如果想事务控制只对某一个方法生效,将注解写在对应方法名上就行
		 */
		public class AccountService{
		
		    //生成dao层对象,方便等会调用方法
		    private AccountDao accountDao;
		
		    //提供set方法,等会通过spring注入AccountDao对象
		    public void setAccountDao(AccountDao accountDao) {
		
		        this.accountDao = accountDao;
		    }
		
		
		    //转账方法,注解也可以写成@Transactional
		    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
		    public void transfer(String inner, String outer, int money) {
		        //进账
		        accountDao.in(money, inner);
		
		        //int i = 10 / 0;
		
		        //出账
		        accountDao.out(money, outer);
		    }
		}
  • 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: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">
		
		    <!--让spring去读我们的连接数据库的配置文件-->
		    <context:property-placeholder location="db.properties"></context:property-placeholder>
		    <!--配置数据源对象,因为jdbctemplate必须要用到数据源-->
		    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		        <property name="driverClass" value="${driverClass}"></property>
		        <property name="jdbcUrl" value="${jdbcUrl}"></property>
		        <property name="user" value="${user}"></property>
		        <property name="password" value="${password}"></property>
		    </bean>
		
		    <tx:annotation-driven></tx:annotation-driven>
		    <!--创建dao层对象,同时给其注入数据源,因为jdbctemplate的创建需要数据源-->
		    <bean id="dao" class="transaction_example4.dao.AccountDao">
		        <property name="dataSource" ref="dataSource"></property>
		    </bean>
		
		    <!--配置service,给service层注入dao层对象-->
		    <bean id="accountService" class="transaction_example4.service.AccountService">
		        <property name="accountDao" ref="dao"></property>
		    </bean>
		
		    <!--1. 配置事务管理器-->
		    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		        <!--通过看源码知道,它需要注入一个数据源属性-->
		        <property name="dataSource" ref="dataSource"></property>
		    </bean>
		
		    <!--2. 配置事务的注解驱动,告诉虚拟机由注解来完成事务操作-->
		    <tx:annotation-driven transaction-manager="transactionManager"/>
		
		</beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值