JAVA互联网架构学习之声明式事务

声明式事务
①基本原理:AOP
[1]前置通知:开启事务
[2]返回通知:提交事务
[3]异常通知:回滚事务
[4]后置通知:释放资源
②事务管理器

③导入jar包
[1]IOC容器需要的jar包
[2]AOP需要的jar包
[3]JdbcTemplate操作需要的jar包
[5]MySQL驱动和C3P0


④配置
[1]配置数据源
[2]配置JdbcTemplate,并装配数据源
[3]配置事务管理器,并装配数据源

	<!-- 配置数据库连接池 -->
	<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${jdbc.user}"></property>
		<property name="password" value="${jdbc.password}"></property>
		<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
		<property name="driverClass" value="${jdbc.driverClass}"></property>		
	</bean>
	
	<!-- 配置JDBCTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="comboPooledDataSource"></property>
	</bean>
	
	<!-- 配置事务管理器,并为事务管理器配置数据源!-->
	<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="comboPooledDataSource"></property>
	</bean>


[4]开启基于注解的声明式事务功能
	<!-- 开启基于注解的声明式事务功能,需要设置transaction-manager属性-->
	<!-- 如果 事务管理器的id正好是transaction-manager的默认值transactionManager,则可以省略-->
	<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>


[5]在事务方法上加@Transactional注解




事务属性的设置
①事务的传播行为
②事务的隔离级别
③事务根据什么异常不进行回滚
④事务的超时属性
⑤事务的只读属性


1.事务的传播行为

[1]解释:一个事务运行在另一个有事务的方法中,那么当前方法是开启新事务还是在原有的事务中运行。

[2]设置事务方法在调用其他事务方法时,自己的事务如何传播给被调用的方法。[默认是required]

[3]设置方式

      简介

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。


      说明

①REQUIRED传播行为

当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。因此在checkout()方法的开 始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。


②REQUIRES_NEW传播行为

表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。




  案例演示:
在上面的dao中增加一个方法,用于演示事务!
//为了测试事务的传播行为,需要增加一个数据库操作
public void updatePrice(String isbn, int price){
	String sql = "UPDATE book SET price = ? WHERE isbn = ?";
	jdbcTemplate.update(sql, price,isbn);
}


  同样,在service类中添加一个方法,如下所示:

@Transactional
public void updatePrice(String isbn, int price){
	bookDao.updatePrice(isbn, price);
}


     此时当前service类中就有两个事务方法了,然后我们现在新创建一个service类,加入到IOC容器中,并将bookService注入,
 然后新建一个事务方法,如下所示:
@Component
public class MultiTX {
	@Autowired
	private BookService bookService;
	
	@Transactional
	public void multiTx(){
		bookService.doCash("ISBN-003","Tom");
		bookService.updatePrice("ISBN-005",888);
	}
}



为了演示该service方法中调用的另外两个方法是不是使用了当前service方法的事务,这里我们将该service方法调用的第二个方法
弄出一个异常,然后看看该service方法调用的第一个service方法是不是回滚了!

先统一设置一下表中的数据:
UPDATE `account` SET balance = 10000;
UPDATE book_stock SET stock = 1000;
UPDATE book SET price = 1000; 



用测试类进行测试:先正常测试一遍,然后再整出个异常测试一下:

public class TestDataSource {
	private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
	private BookDao bean = ioc.getBean(BookDao.class);
	private BookService bookService  = ioc.getBean(BookService.class);
	private MultiTX multiTx = ioc.getBean(MultiTX.class);
	@Test
	public void test04() throws SQLException {		
		multiTx.multiTx();
	}
}



会发现@Transactional注解默认使用的传播属性就是required!
此时可以将BookService类中的两个方法都加上propagation=Propagation.REQUIRES_NEW 属性,也就是如下所示:
@Transactional(propagation=Propagation.REQUIRES_NEW)

然后在执行刚才的有异常的测试方法,可以发现被包含的两个事务无法回滚,因为把外层事务挂起了!



2.事务的隔离级别 :

用于解决并发问题[在两次获取价格的方法中间打断点]

 isolation=Isolation. READ_COMMITTED
 
先恢复数据表中的数据:
UPDATE `account` SET balance = 10000;
UPDATE book_stock SET stock = 1000;
UPDATE book SET price = 1000; 
修改service类中的doCash方法为:【即两次获取价格!】
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void doCash(String isbn,String username){
	int price = bookDao.findPriceByIsbn(isbn);
	System.out.println("price1:"+price);
	bookDao.updateStockByIsbn(isbn);
	bookDao.updateBalance(username, price);
	
	price = bookDao.findPriceByIsbn(isbn);
	System.out.println("price2:"+price);
}


在测试方法中调用BookService类中的doCash方法,然后在doCash方法中两次获取价格的方法中间打断点,就会发现
第一次是1000元,然后我们修改了数据库之后,读出来的还是1000元,这是因为数据库默认的隔离级别就是可重复读!

如果我们在doCash方法的事务注解上加一个isolation属性,如下所示:
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
public void doCash(String isbn,String username){
	int price = bookDao.findPriceByIsbn(isbn);
	System.out.println("price1:"+price);
	bookDao.updateStockByIsbn(isbn);
	bookDao.updateBalance(username, price);
	
	price = bookDao.findPriceByIsbn(isbn);
	System.out.println("price2:"+price);
}


   就会发现如果在两次获取价格之间打了断点,然后修改了值的话就会读出不一样的效果,说明事务的隔离级别设置就生效了!


3.事务根据什么异常不进行回滚

 

默认情况

捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。

设置途经

①注解

@Transactional注解

[1]rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个

[2]noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个

②XML

在Spring2.x事务通知中,可以在<tx:method>元素中指定回滚规则。如果有不止一种异常则用逗号分隔。

 默认情况下,出现异常就回滚,noRollbackFor属性可以设置出现什么异常不进行回滚:

noRollbackFor=ArithmeticException.class

@Transactional(propagation=Propagation.REQUIRES_NEW,
			   isolation=Isolation.READ_COMMITTED,
			   noRollbackFor=ArithmeticException.class)
public void doCash(String isbn,String username){
	int price = bookDao.findPriceByIsbn(isbn);
	System.out.println("price1:"+price);
	bookDao.updateStockByIsbn(isbn);
	bookDao.updateBalance(username, price);
	System.out.println(10/0);//出了异常信息
}



4.事务的超时属性【timeout=3】
[1]数据库事务在执行过程中,会占用数据库资源,所以如果某一个事务执行的时间太长,
   那么就会导致资源被长时间占用,影响其他事务执行。[死循环,网络问题]

   [2]可以通过设置超时属性,将超时没有执行完的事务回滚,相当于对该操作进行撤销。


5.事务的只读属性【readOnly=true】
数据库会对事务进行优化,如果是一个查询操作,那么数据库可以有针对性的进行优化。我们可以通过设置事务属性,
告诉数据库当前操作是一个只读操作,便于数据库进行优化。

@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor=ArithmeticException.class,
timeout=3)
public void doCash(String isbn,String username){
int price = bookDao.findPriceByIsbn(isbn);
System.out.println("price1:"+price);
bookDao.updateStockByIsbn(isbn);
try {
	Thread.sleep(1000*5);
} catch (InterruptedException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}
bookDao.updateBalance(username, price);
}

基于XML的声明式事务

		<!-- 配置基于XML文件的声明式事务 -->
		<aop:config>
			<!-- 配置切入点表达式 -->
			<aop:pointcut expression="execution(* com.neuedu.tx.service.BookService.*(String, String))" id="txPointCut"/>
			<!-- 将事务切入点和事务建议的配置联系起来 -->
			<aop:advisor advice-ref="bookTransaction" pointcut-ref="txPointCut"/>
		</aop:config>
		<!-- 设置事务属性 -->
		<tx:advice id="bookTransaction" transaction-manager="dataSourceTransactionManager">
			<tx:attributes>
				<tx:method name="doCash" 
				propagation="REQUIRED"
				isolation="READ_COMMITTED"
				read-only="false"
				no-rollback-for="java.lang.ArithmeticException"
				timeout="3" />
				  <!-- 将某一类方法统一设置为只读 -->
				<tx:method name="get*" read-only="true"/>
				<tx:method name="find*" read-only="true"/>
				<tx:method name="query*" read-only="true"/>
			</tx:attributes>
		</tx:advice>


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值