spring(三)之事务、事务管理器(TransactionManager)简介及实现事务的4种方式

1、事务

        一组业务ACID操作,要么全部成功,要么全部不成功。

       事务特性:①原子性,针对整体而言(一个事务不可以被拆分);②一致性,针对数据而言(一个事务执行之前和执行之后必须处于一致性状态,一个事务包含的所以操作要么全部成功,要么全部失败。比如转账前,A和B各有250元,A转250给B,结果A有0元,B有500元,A和B相加的钱,不管转账前还是转账后都是500元,这就是一致性);③隔离性,针对并发而言(对任意两个并发的事务T1和T2,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行)④持久性,针对结果而言(指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的)

(1)隔离问题(事务并发问题):

       ①脏读:一个事务读到另一个事务未提交的内容(读取未提交内容);就是一个事务读到了别的事务回滚前的脏数据,比如事务B执行过程中修改了数据X,在未提交前(即未保存到数据库前),事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。

        ②不可重复读:指在一个事务多次读取同一数据,读出来的数据不一致。比如库存数为100,事务A读取库存数为100,切换到事务B事务B开启事务–>事务B减库存50–>提交,数据库里面库存变为50元,此时切换回事务A,事务A再查一次查出库存为50,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读
       ③幻读:两个事务,第一个事务将所有行的数据都修改了,第二个事务将插入一条数据提交,第1个事务提交发现有一条数据并没有修改。比如系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A修改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

(2)隔离级别--决绝问题

隔离级别  脏读  不可重复读 幻读 加锁读
Read Uncommitted(读取未提交内容)  否
Read Committed(读取提交内容)  否  是  是  否
Repeatable Read(可重复读)  否  否  是  否
Serializable(可串行化)  否  否  否  是

    ① Read Uncommitted(读取未提交内容): 会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )[保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题]

  ②Read Committed(读取提交内容):会出现不可重复读、幻读问题(锁定正在读取的行) [大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。]
  ③Repeatable Read(可重复读): 会出幻读(锁定所读取的所有行)[保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。]

  ④Serializable(可串行化): 保证所有的情况不会发生(锁表)[最严格的级别,事务串行执行,资源消耗最大。]

  不可重复读的重点是修改 : 
    同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了 
  幻读的重点在于新增或者删除 
    同样的条件 , 第 1 次和第 2 次读出来的记录数不一样

       解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。

2、PlatformTransactionManager 事务管理器

      (1)事务管理器是什么?为什么需要管理事务?

         事务是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作,这样可以防止出现脏数据,防止数据库数据出现问题这一系列处理需要我们对其进行管理。

         JDBC中是通过Connection对象进行事务管理,默认是自动提交事务,可以手工将自动提交关闭,通过commit方法进行提交,rollback方法进行回滚,如果不提交,则数据不会真正的插入到数据库中。Hibernate中是通过Transaction进行事务管理,处理方法与JDBC中类似。

          Spring中也有自己的事务管理机制,一般是使用TransactionManager(事务管理器)进行管理,可以通过Spring的注入来完成此功能。

     (2)先导入两个包:spring-jdbc-3.2.0.RELEASE.jar和spring-tx-3.2.0.RELEASE.jar,如果用了Hibernate还要导入spring-orm-3.2.0.RELEASE.jar

3、转账案例(实现事务的4种方式)

(1)无事务管理

①创建数据库表account:

并插入两条数据:id=1,username="jack",money=1000和id=2,username="rose",money=1000。

②dao层:接口IAccountDao 

package com.gyf.dao;

public interface IAccountDao {
 
	//扣钱
	public void out(String outer,Integer money);
	//进账
	public void in(String inner,Integer money);
}

   实现类:AccountDaoImpl

package com.gyf.dao.impl;

import org.springframework.jdbc.core.support.JdbcDaoSupport;

import com.gyf.dao.IAccountDao;

public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

	@Override
	public void out(String outer, Integer money) {
		getJdbcTemplate().update("update account set money = money-? where username = ?", money,outer);
	}

	@Override
	public void in(String inner, Integer money) {
		getJdbcTemplate().update("update account set money = money+ ? where username =?", money,inner);
	} 

}

③service层:接口 IAccountService

package com.gyf.service;

public interface IAccountService {

	/*转账
	 * @param outer 转出账号
	 * @param inner 转入账号
	 * @param money 转入金额
	 */
	public void transfer(String  outer,String inner,Integer money);
}

实现类:AccountServiceImpl

package com.gyf.service.impl;
import org.springframework.transaction.annotation.Transactional;

import com.gyf.dao.IAccountDao;
import com.gyf.service.IAccountService;
public class AccountServiceImpl2 implements IAccountService {
	private IAccountDao accountDao;//由spring注入
	public void setAccountDao(IAccountDao accountDao) {
		this.accountDao = accountDao;
	}

	@Override
	public void transfer(String outer, String inner, Integer money) {
		accountDao.out(outer, money);//扣钱
		
		accountDao.in(inner, money);//进账
	}

}

④web层,beans09.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        https://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        https://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/aop
                        https://www.springframework.org/schema/aop/spring-aop.xsd">
      
     <!-- 读取db.properties文件 -->
     <context:property-placeholder location="classpath:db.properties"/>
     <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <property name="driverClass" value="${driverClass}" />
           <property name="jdbcUrl" value="${jdbcUrl}" />
            <property name="user" value="${user}" />
             <property name="password" value="${password}" />
     </bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 配置service -->
    <bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl">
       <property name="accountDao" ref="accountDao"></property>
    </bean>
    
    
</beans>

⑤test层:lesson09

package com.gyf.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gyf.service.IAccountService;
public class lesson09 {   
	@Test
	public void test1() {
		//转账测试
		//获取service		
		ApplicationContext context=new ClassPathXmlApplicationContext("beans09.xml");		
		IAccountService  accountService= (IAccountService) context.getBean("accountService");		
		accountService.transfer("jack", "rose", 100);
	}
}

执行结果:

(2)加上事务(手动管理事务)(了解)

①修改service层实现类AccountServiceImpl

package com.gyf.service.impl;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.gyf.dao.IAccountDao;
import com.gyf.service.IAccountService;

public class AccountServiceImpl implements IAccountService {

	private IAccountDao accountDao;//由spring注入
	
	public void setAccountDao(IAccountDao accountDao) {
		this.accountDao = accountDao;
	}

	//spring配置事务模板【由spring注入】
	private TransactionTemplate transactionTemplate;
	
	public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
		this.transactionTemplate = transactionTemplate;
	}

	@Override
	public void transfer(String outer, String inner, Integer money) {
		
		this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
			
			@Override
			protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
				accountDao.out(outer, money);//扣钱
				int i=10/0;//此时执行的话,转账会失败,因为转账的过程出现了错误,事务会回滚
				accountDao.in(inner, money);//进账
				
			}
		});
		

	}

}

 ②修改web层,beans09.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        https://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        https://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/aop
                        https://www.springframework.org/schema/aop/spring-aop.xsd">
      
     <!-- 读取db.properties文件 -->
     <context:property-placeholder location="classpath:db.properties"/>
     <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <property name="driverClass" value="${driverClass}" />
           <property name="jdbcUrl" value="${jdbcUrl}" />
            <property name="user" value="${user}" />
             <property name="password" value="${password}" />
     </bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
   
    <!-- 配置事务管理器 -->
     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <!-- 配置dataSource -->
       <property name="dataSource" ref="dataSource"></property>
     </bean>
     <!-- 配置事务模板 -->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
      <!-- 事务管理器 -->
      <property name="transactionManager" ref="txManager"></property>
    </bean>
    
    <!-- 配置service -->
    <bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl">
       <property name="accountDao" ref="accountDao"></property>
       <!-- 事务模板 -->
       <property name="transactionTemplate" ref="transactionTemplate"></property>
    </bean>
</beans>

执行结果:

报错了,要想转账成功,就把service层实现类AccountServiceImpl中的int i=10/0;删了或者注销掉。

(3)工厂bean生成代理,半自动管理事务

①在service层,新建AccountServiceImpl2类

package com.gyf.service.impl;
import org.springframework.transaction.annotation.Transactional;
import com.gyf.dao.IAccountDao;
import com.gyf.service.IAccountService;
public class AccountServiceImpl2 implements IAccountService {
	private IAccountDao accountDao;//由spring注入
	public void setAccountDao(IAccountDao accountDao) {
		this.accountDao = accountDao;
	}
	@Override
	public void transfer(String outer, String inner, Integer money) {
		accountDao.out(outer, money);//扣钱
		//int i=10/0;
		accountDao.in(inner, money);//进账
	}

}

②在web层新建beans10.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        https://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        https://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/aop
                        https://www.springframework.org/schema/aop/spring-aop.xsd">  
     <!-- 读取db.properties文件 -->
     <context:property-placeholder location="classpath:db.properties"/>
     <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <property name="driverClass" value="${driverClass}" />
           <property name="jdbcUrl" value="${jdbcUrl}" />
            <property name="user" value="${user}" />
             <property name="password" value="${password}" />
     </bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 配置事务管理器 -->
     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <!-- 配置dataSource -->
       <property name="dataSource" ref="dataSource"></property>
     </bean> 
    <!-- 配置service -->
    <bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl2">
       <property name="accountDao" ref="accountDao"></property>
    </bean>
      <!-- 配置工厂代理 -->
    <bean id="proxyService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
       <!-- 接口 -->
       <property name="proxyInterfaces" value="com.gyf.service.IAccountService"></property>
       <!-- 目标对象 -->
       <property name="target" ref="accountService"></property>
       <!-- 事务管理器 -->
       <property name="transactionManager" ref="txManager"></property>
       
       <!--transactionAttributes:事务属性/详情配置
            key:写方法名;value写事务配置;格式:PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly,-Exception,+Exception
                                                                                                                                  传播行为                                                隔离级别                             是否只读              异常回滚               异常提交
        -->
       <property name="transactionAttributes">
         <props>
            <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
            <prop key="add">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
            <prop key="delete">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
            <prop key="update">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
            <prop key="find">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
         </props>
       </property>     
    </bean>
</beans>

③在test层新建lesson10类

package com.gyf.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gyf.service.IAccountService;
public class lesson10 {  
	@Test
	public void test1() {
		//转账测试
		//获取service
		ApplicationContext context=new ClassPathXmlApplicationContext("beans10.xml");
		IAccountService  accountService= (IAccountService) context.getBean("proxyService");
		accountService.transfer("jack", "rose", 100);
	}
}

 

(4)基本aop的事务配置

新建web层beans11.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
                        https://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        https://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/aop
                        https://www.springframework.org/schema/aop/spring-aop.xsd
                        http://www.springframework.org/schema/tx
                        https://www.springframework.org/schema/tx/spring-tx.xsd">
      
     <!-- 读取db.properties文件 -->
     <context:property-placeholder location="classpath:db.properties"/>
     <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <property name="driverClass" value="${driverClass}" />
           <property name="jdbcUrl" value="${jdbcUrl}" />
            <property name="user" value="${user}" />
             <property name="password" value="${password}" />
     </bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
   
    <!-- 配置事务管理器 -->
     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <!-- 配置dataSource -->
       <property name="dataSource" ref="dataSource"></property>
     </bean>
   
    <!-- 配置service -->
    <bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl2">
       <property name="accountDao" ref="accountDao"></property>
    </bean>
     <!-- 使用aop标签来配置 -->
     
     <!--1、 配置事务通知管理器 -->
     <tx:advice id="txAdvice" transaction-manager="txManager">
       <!-- 事务详情 :传播行为,隔离级别-->
       <tx:attributes>
       <!-- 传播行为,隔离级别可以不用配置,但方法要配置-->
       <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
       </tx:attributes>
     </tx:advice>
   
     <!-- 2、事务通知 与 切入点 关联 -->
      <aop:config>
      <!--   <aop:pointcut expression="execution(* com.gyf.service..*.*(..))" id="myPointcut"/>
         <aop:advisor advice-ref="" pointcut-ref="myPointcut"/> 等价于下面的代码 --> 
         <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.gyf.service..*.*(..))"/>
      </aop:config>    
</beans>

②新建test层lesson11

package com.gyf.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gyf.service.IAccountService;

public class lesson11 {
     
	@Test
	public void test1() {
		//转账测试
		//获取service
		ApplicationContext context=new ClassPathXmlApplicationContext("beans11.xml");
		IAccountService  accountService= (IAccountService) context.getBean("accountService");
		accountService.transfer("jack", "rose", 100);
	}
}

(5)基本注解的事务配置

①修改service层的 AccountServiceImpl2

package com.gyf.service.impl;
import org.springframework.transaction.annotation.Transactional;
import com.gyf.dao.IAccountDao;
import com.gyf.service.IAccountService;
//@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT)功能和下面的一样
@Transactional
public class AccountServiceImpl2 implements IAccountService {
	private IAccountDao accountDao;//由spring注入
	public void setAccountDao(IAccountDao accountDao) {
		this.accountDao = accountDao;
	}
	@Override
	public void transfer(String outer, String inner, Integer money) {		
		accountDao.out(outer, money);//扣钱
		//int i=10/0;
		accountDao.in(inner, money);//进账
	}
}

②新建web层beans12.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
                        https://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        https://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/aop
                        https://www.springframework.org/schema/aop/spring-aop.xsd
                        http://www.springframework.org/schema/tx
                        https://www.springframework.org/schema/tx/spring-tx.xsd">
      
     <!-- 读取db.properties文件 -->
     <context:property-placeholder location="classpath:db.properties"/>
     <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <property name="driverClass" value="${driverClass}" />
           <property name="jdbcUrl" value="${jdbcUrl}" />
            <property name="user" value="${user}" />
             <property name="password" value="${password}" />
     </bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 配置service -->
    <bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl2">
       <property name="accountDao" ref="accountDao"></property>
    </bean>
     <!-- 注解的事务配置 -->
     <!--1、 配置事务管理器 -->
     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <!-- 配置dataSource -->
       <property name="dataSource" ref="dataSource"></property>
     </bean>   
     <!-- 2、开启事务注解驱动 -->
      <tx:annotation-driven transaction-manager="txManager"/>
</beans>

③新建test层的lesson12

package com.gyf.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gyf.service.IAccountService;
public class lesson12 { 
	@Test
	public void test1() {
		//转账测试
		//获取service
		ApplicationContext context=new ClassPathXmlApplicationContext("beans12.xml");
		IAccountService  accountService= (IAccountService) context.getBean("accountService");
		accountService.transfer("jack", "rose", 100);
	}
}
发布了57 篇原创文章 · 获赞 36 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/hqy1719239337/article/details/97944775