事务的特性/概念
事务:一组操作要么都成功要么失败;
事务的四个关键属性(ACID):
- 原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
- 一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
- 隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
- 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。
jar包
c3p0-0.9.1.2.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.3.jar
mysql-connector-java-5.1.7-bin.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
基本配置:
<!--1、配置连接池 --> <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"></property> <property name="password" value="123456"></property> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/tx"></property> </bean> <!--2、数据库的增删改查 JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> <!--配置事务切面;控制住连接池 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> <!--开启基于注解的事务功能;依赖tx名称空间 transaction-manager:指定事务管理器是哪个 --> <tx:annotation-driven/>
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; /** * [1]根据isbn的值查询书的价格, public int getPrice(String isbn); [2]根据isbn的值减少书的库存,假设每次都只买1本书, public void updateStock(String isbn); [3]根据用户名减少用户账户中的余额,减少的额度就是书的价格 public void updateBalance(int price,String user); * @author lfy * */ @Repository public class BookDao { @Autowired JdbcTemplate jdbcTemplate; /** * [1]根据isbn的值查询书的价格, * public int getPrice(String isbn); * 操作:book */ public int getPrice(String isbn){ String sql = "select price from book where isbn=?"; Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn); return price; }; /** * [2]根据isbn的值减少书的库存,假设每次都只买1本书, * public void updateStock(String isbn); * 操作:book_stock */ public void updateStock(String isbn){ String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?"; jdbcTemplate.update(sql, isbn); } /** * [3]根据用户名减少用户账户中的余额,减少的额度就是书的价格 * public void updateBalance(int price,String user); * 给用户减余额;account * String user:要减余额的用户名 * int price:要减掉多少(减少的额度就是书的价格) */ public void updateBalance(int price,String user){ String sql = "update account set balance = balance-? where username=?"; jdbcTemplate.update(sql, price,user); } public void updatePrice(String isbn){ String sql = "update book set price = 999 WHERE isbn=?"; jdbcTemplate.update(sql, isbn); } }
事务注解主要添加在Service层,通过Aop来实现事务操作:
import java.io.FileInputStream; import java.io.FileNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.atguigu.dao.BookDao; @Service public class BookService { @Autowired BookDao bookDao; @Transactional(noRollbackFor=ArithmeticException.class, timeout=3,propagation=Propagation.REQUIRES_NEW) public void checkout(String username,String isbn){ //0、查出图书价格 int price = bookDao.getPrice(isbn); //1、减用户余额 bookDao.updateBalance(price, username); //2、减图书的库存 bookDao.updateStock(isbn); //Thread.sleep(3000); //new FileInputStream("D://ahahahah//aa.txt"); System.out.println("结账完成...."); } @Transactional(propagation=Propagation.REQUIRED,timeout=3) public void updatePrice(String isbn){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } bookDao.updatePrice(isbn); } }
Spring事务管理器
事务控制的属性:
propagation:事务的传播行为;我们可以通过控制指定大事务和小事务的关系;
传播(共用一个事务的情况下大事务的属性配置会传播给小事务)+行为(是否共用一个事务)。
isolation:事务的隔离级别,隔离各个事务的;
isolation=Isolation.READ_UNCOMMITTED;
根据实际的业务逻辑;
不适用事务的话会出现可能会出现如下问题:
- 脏读:同一事务期间,数据还没提交就给回滚了。
- 不可重复读:同一事务期间,多次读取数据内容不一样。
- 幻读:同一事务期间,多次读取数据,发现记录变少或者变多了。
本质上一个再读,一个在改;
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
①读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
②读已提交:READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。
③可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
④串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
⑤各个隔离级别解决并发问题的能力见下表
⑥各种数据库产品对事务隔离级别的支持程度
在Spring中指定事务隔离级别
注解
用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别
XML
在Spring 2.x事务通知中,可以在<tx:method>元素中指定隔离级别
触发事务回滚的异常
默认情况
捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。
设置途经
注解
@Transactional 注解
- rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个
- noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个
XML
在Spring 2.x事务通知中,可以在<tx:method>元素中指定回滚规则。如果有不止一种异常则用逗号分隔。
事务的超时和只读属性
简介
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
设置
注解
@Transaction注解
XML
在Spring 2.x事务通知中,超时和只读属性可以在<tx:method>元素中进行指定
举个比较完整的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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" 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-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"> <context:component-scan base-package="com.atguigu"></context:component-scan> <!--1、配置连接池 --> <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"></property> <property name="password" value="123456"></property> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/tx"></property> </bean> <!--2、数据库的增删改查 JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> <!--3、配置事务切面;控制住连接池 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> <!--4、开启基于注解的事务功能;依赖tx名称空间 transaction-manager:指定事务管理器是哪个 --> <!-- <tx:annotation-driven/> --> <!--基于xml的事务配置;需要aop和tx名称空间 --> <!-- transactionManager事务切面 --> <aop:config> <!--指定事务管理器要切入哪些方法进行事务控制 --> <aop:pointcut expression="execution(* com.soyoungboy.service.*.*(..))" id="txPoint"/> <!-- aop:advisor:建议; pointcut-ref:使用指定的切入点表达式切入事务 --> <aop:advisor advice-ref="myTxAdvice" pointcut-ref="txPoint"/> </aop:config> <!-- 使用tx名称空间和配置(事务建议、事务属性、事务增强);事务方法怎么执行 id="myTxAdvice":随便起,别人要引用<aop:advisor advice-ref="myTxAdvice" transaction-manager="transactionManager":指定配置哪个事务管理器 --> <tx:advice id="myTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 指定事务方法:代表所有方法是事务方法 --> <tx:method name="*"/> <tx:method name="checkout" rollback-for="java.lang.Exception"/> <tx:method name="updatePrice" propagation="REQUIRES_NEW"/> <!--以get开头的,通过只读来进行优化--> <tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice> <!-- 基于xml配置的事务控制更多一点 --> </beans>