spring中声明式事务控制学习记录

什么是事务?

    数据库的事务,指的是一组操作的执行单元;其中的一组操作指的是一组SQL指令(增、删、改),执行单元指这一组操作全部执行成功,或者全部回滚;(什么是单元,一个才整体交一个单元)

    简单理解一下,其实就是:事务是一组sql 语句的集合,这组sql语句要么全部执行成功,要么全部回滚

事务有四个基本要素,简称ACID

atomic(原子性):事务的所有操作,要么全部完成,要么全部回滚到事务开始前的状态

consistent(一致性):事务执行的结果必须是从一个一致的状态到另一个一致的状态,不管一个时间点上它有多少个并发事务

isolate(隔离性):一个事务的执行不能被其他事务干扰,

durable(持久性):事务一旦提交,事务对数据库的更改被永久性的保存在数据库中,

Spring提供的事务控制

  • 编程式事务控制

在代码中控制事务的开始、回滚、和结束,可以自由的控制在哪个方法的哪一行中进行事务控制,其控制的颗粒度更细(相对于声明式事务控制);因为是写在代码中,由程序员自己控制,因此在团队合作中,事务的管理会比较混乱。下面是一个案例:

public int insertDB(){
	Connection conn = null;
	PreparedStatement pstmt = null;
	int result = 0;
	try {
		conn = jdbcTemplate.getDataSource().getConnection();
		conn.setAutoCommit(false);  //默认是自动提交事务的,这个关闭自动提交
		String sql = "insert into lsjm_user (uname,pwd) values (?,?)";
		pstmt = conn.prepareStatement(sql);
		pstmt.setString(1, "801");
		pstmt.setString(2, "编程式事务控制");
		System.out.println("编程式事务控制");
		result =  pstmt.executeUpdate();
		Object e = 3/0;  // 创造一个异常,进入catch
		conn.commit(); // 手动提交事务,不写这一行,前面的插入数据到数据库是不起作用的,也不报异常
	} catch (Exception e) {
		try {
			conn.rollback();  // 发生了异常,进行事务回滚
		} catch (SQLException e1) {
			e1.printStackTrace();
		}  
	}finally{
		try {
			conn.setAutoCommit(true);
			pstmt.close();
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	return result ;
}
  • 声明式事务控制

Spring没有直接管理事务,提供事务管理器 调用第三方组件完成事务控制

事务管理器下面对应目标时使用

org.springframework.jdbc.datasource.DataSourceTransactionManager

在单一的JDBC Datasource中管理事务

org.springframework.orm.hibernate3.HibernateTransactionManager

当持久化机制是hibernate时,用它来管理事务

org.springframework.jdo.JdoTransactionManager

当持久化机制是Jdo时,用它来管理事务

org.springframework.transaction.jta.JtaTransactionManager

使用一个JTA实现来管理事务。在一个事务跨越多个资源时必须使用

org.springframework.orm.ojb.PersistenceBrokerTransactionManager

apacheojb用作持久化机制时,用它来管理事务

特点:

  • 配置在配置文件中,解除了和代码的耦合度,不需要事务管理时,去掉配置即可(注解也算是配置)
  • 事务以bean组件的函数为单位,即一个函数正常执行完成后,这个函数中的数据库操作按照一次事务提交

因为事务以函数为单位,函数与函数之间肯定存在相互调用的关系,此时的事务怎么处理?

 -- spring 提供了一组配置供开发者选择,配置的方式成为事务的传播策略,比如方法A 调用了 方法B

传播行为

意义

REQUIRED

业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务

NOT_SUPPORTED

声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行

REQUIRESNEW

属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行

MANDATORY

该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。

SUPPORTS

这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行

Never

指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行

NESTED

如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效

数据库的隔离级别(这个需要再研究一下,暂时贴上)

隔离级别

含义

DEFAULT

使用后端数据库默认的隔离级别(spring中的的选择项)

READ_UNCOMMITED

允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读

READ_COMMITTED

允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生

REPEATABLE_READ

对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。

SERIALIZABLE

完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。

 

Spring声明式事务控制的使用 (junit测试Demo)

1、准备工作

  • 导jar包:
    • 数据库驱动jar :mysql-connector-java......
    • spring核心容器core、context;
    • spring集成jdbc 的 jar:spring-jdbc..., 事务控制的jar:spring-tx..., AOP的jar:spring-aop...
<dependencies>
	<!-- 测试 -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
	
	<!-- mysql数据库驱动 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.32</version>
	</dependency>
	
	<!-- spring核心容器core子模块的依赖信息 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-core</artifactId>
		<version>${spring.version}</version>
	</dependency>
	
	<!-- spring核心容器context子模块的依赖信息 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>
	
	<!-- spring核心容器context子模块的依赖jar包的依赖信息 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-support</artifactId>
		<version>${spring.version}</version>
	</dependency>
	
	<!-- spring 中的aop模块的jar包-->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aop</artifactId>
		<version>${spring.version}</version>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
		<version>${spring.version}</version>
	</dependency>
	
	<!-- spring集成jdbc的jar,该包中提供了JdbcTemplate工具类 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>${spring.version}</version>
	</dependency>
	
	<!-- spring中事务控制的jar -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-tx</artifactId>
		<version>${spring.version}</version>
	</dependency>
</dependencies>

2、声明式事务控制配置,内容 和 注释都写在配置文件中

<?xml version="1.0" encoding="UTF-8"?>

<!-- xmlns指的是命名空间,下面存放着各个版本的约束文件 -->
<!-- xmlns:xsi指的是标签符合w3c的规范 -->
<!-- xsi:schemaLocation:约束文件的路径,命名空间与约束文件的全路径组成 -->
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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">
        
        <!-- 数据源 -->
	<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/lsjm?useUnicode=true&amp;characterEncoding=utf8" />
 		<property name="username" value="admin" />
 		<property name="password" value="admin" />
 	</bean>
 	
 	<!-- 继承JdbcTemplate类的配置方式 -->
 	<bean id="userDao1" class="com.chaol.dao.impl.LsjmUserDaoImpl1">
 		<property name="dataSource" ref="dataSource"></property>
 	</bean>
 	
 	<!-- 加载spring的工具类JdbcTemplate为bean组件 -->
 	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
 		<property name="dataSource" ref="dataSource"></property>
 	</bean>
 	
 	<!-- 通过spring的依赖注入设定Dao类JdbcTemplate属性 -->
 	<bean id="userDao" class="com.chaol.dao.impl.LsjmUserDaoImpl">
 		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
 	</bean>
 	
 	<!-- 声明式事务控制配置, 分三步 -->
 	<!-- 第一步: 配置事务管理器 -->
 	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 		<property name="dataSource" ref="dataSource"></property>
 	</bean>
 	
 	<!-- 第二步: 配置事务通知, 指定对bean组件的哪些方法 进行事务管理-->
 	<tx:advice id="ad" transaction-manager="transactionManager">
 		<tx:attributes>
 			<!-- name:指定方法名(可用通配符); isolation:指定数据库隔离级别; propagation:事务传播策略; read-only:只有查询的函数配置为true效率更高  -->
 			<tx:method name="insert*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
 		</tx:attributes>
 	</tx:advice>
 	
 	<!-- 第三步:配置切入点,即配置哪些bean组件需要事务 -->
        <aop:config>
    	    <aop:pointcut expression="execution(* com.chaol.dao.impl.*.*(..))" id="targetPointcut"/>
    	    <!-- advice-ref:第二步事务通知中的id, pointcut-ref:上一行切入点的id -->
    	    <aop:advisor advice-ref="ad" pointcut-ref="targetPointcut"/>
        </aop:config>
</beans>

在上面的配置 中,配置了对com.chaol.dao.impl 下的任意类中 形如 insert* 的方法 做事务管理,写一个方法测试一下:

// 这个方法是在 com.chaol.dao.impl.LsjmUserDaoImpl类中
public int insert(){
	String sql = "insert into lsjm_user (uname,pwd) values (?,?)";
	int result = jdbcTemplate.update(sql,new Object[]{"txTest2", "txTest2"});
	Object e = 3/0;  // 创造一个异常,测试会不会回滚
	return result;
}

执行上面方法,取数据库中查看结果:

-- 可见函数中的数据库操作并没有保存到数据库中,函数发生异常,数据库操作全部回滚(没有抛出异常测试是好的,结果不贴了):

 

每一种能用配置写的 ,大都都可以用注解来配置,声明式事务控制的注解配置:

省去上面的第二步和第三部,然后加上:

<!-- 开启注解自动扫描 -->
<context:component-scan base-package="com.chaol.dao.impl"></context:component-scan>
<!--  采用@Transactional注解方式使用事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 第一步: 配置事务管理器(这一步还是需要的) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>

然后在需要事务控制的方法上用@Transactional 注解(测试结果不贴了):

@Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED,readOnly=false)
public int insert(){
	String sql = "insert into lsjm_user (uname,pwd) values (?,?)";
	int result = jdbcTemplate.update(sql,new Object[]{"txTest3", "txTest3"});
	Object e = 3/0;  // 创造一个异常,测试会不会回滚
	return result;
}

 

在用junit测试的过程中遇到一个问题,下面贴一下我的junit代码:

public class LsjmUserDaoTest {

	//LsjmUserDaoImpl lsjmUserDaoImpl  
	LsjmUserDao lsjmUserDaoImpl;
	
	@Before
	public void before(){
		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
		lsjmUserDaoImpl =(LsjmUserDao) ac.getBean("userDao");
		
		//lsjmUserDaoImpl =(LsjmUserDaoImpl) ac.getBean("userDao");
	}
	
	@Test
	public void test(){
		System.out.println(lsjmUserDaoImpl.insert());
	}
}

问题:

如下 使用 接口 LsjmUserDao 的实现类 LsjmUserDaoImpl 来转换对象时,会报错,信息如下:

LsjmUserDaoImpl lsjmUserDaoImpl = (LsjmUserDaoImpl) ac.getBean("userDao");

java.lang.ClassCastException: com.sun.proxy.$Proxy10 cannot be cast to com.chaol.dao.impl.LsjmUserDaoImpl

解决:

用接口来转换 ac.getBean("userDao") 获取得到的对象

LsjmUserDao lsjmUserDaoImpl = (LsjmUserDao) ac.getBean("userDao");

原因:不能用接口的实现类来转换Proxy的实现类,它们是同级,应该用共同的接口来转换(百度到的)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值