1.事务的传播属性
1.当事务方法被另一个事务方法调用时,必須指定应该如何传播,例如:方法可能继续在现有事务中运行,也可能开启一个新事物,并在自己的事务中运行
2.事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为
7种 Transactional(Propagation)
1.Requerd:如果有事务在运行,当前的方法就在这个事务内运行,否则,就开启一个新的事务,并在自己的事务内运行
2.requerd_NEW:当前的方法必须启动新事物,并在他自己的事务内运行,如果有事务正在运行,应该将他挂起
3.Supports:如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
4.Not_Supported:当前的方法不应该运行在事务中,如果有运行的事务,将他挂起
5.MANDATORY:当前方法必须运行在事物内部,如果没有正在运行的事务,就抛出异常
6.NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
7.NESTED:如果有事务在运行,当前的方法就应该在这个事物的嵌套事务内运行,否则就启动一个新的事物,并在他自己的事务内运行
如:表示客户买书结账操作
所需的钱只够支付第一本书,不够支付第二本
1.REQUERD传播行为
1.当bookService(自定义)的purchase()(自定义)被另一个事务方法checkout()(自定义)调用时,它默认会在现有的事务内运行,这个默认的传播行为就是REQUIRED。因此在checkout()方法的开始和终止边界内只有一个事务,这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了
2.事务传播属性可以在@Transactional注解的propagation属性中定义
tx1开始事务 checkout(purchase()购买操作1;purchase()购买操作2),如果发现其中一个操作失败则checkout()检查回滚 tx1事务结束
2.requerd_NEW传播行为
另一种常见的传播行为是requerd_NEW。他表示该方法必须启动一个新事务,并在自己的事务内运行,如果有事务在运行,就应该先挂起它
@Transactional(propagation=Propagation.REQUIRES_NEW)
checkout(tx1事务开启 tx2事务开启时(tx1挂起 执行perchase())tx2事务结束 tx1继续;tx3事务开启时(tx1挂起 执行perchase())tx3事务结束 tx1继续 tx1结束)
在Spring2.x事务通知中配置传播属性
在Spring2.x事务通知中,可以在<tx:method>元素中设定传播事务属性
3.事务所导致的问题
1. 当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多于外的问题
2.并发事务所导致的问题可以分为下面三种类型
2.1脏读:对于两个事务T1,T1 读取了已经被T2更新但还没有被提交的字段,若T2回滚,T1读取的内容就是临时且无效的。
2.2不可重复读:对于两个事务T1,T2。T1读取了一个字段,然后T2更新了字段,T1再次读取同一个字段,值就不同了。
2.3幻读:对于两个事务T1,T2。T1从一个表中读取一个字段,然后T2在该表中插入了一些新的行。之后,如果T1再读取同一个表,就会多出几行
2.事务隔离级别
1.从理论上来说,事务应该彼此完全隔离,以避免并发事务所导致的问题,然而,那样会对性能产生极大的影响,因为事务必须按顺序运行(越高 效率越低 Transactional(Isolation))
2.在实际开发中,为了提升性能,事务会议较低的隔离级别运行
3.事务的隔离级别可以通过隔离事务属性指定
Spring支持的事务隔离级别
1.DEFAULT:使用底层数据库的默认隔离级别 ,对于大多数数据库来说默认都是read_commited(防止脏读)
2.read_UNcommitted:允许事务读取来被其他事务提交的变更,脏读,不可重复读和幻读的问题都会出现
3.read_commited:只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读,幻读问题依然可能出现
4.repeatable-read:确保事务可以多次从一个字段中读取相同的值 ,在这个事务持续期间,禁止其他事务对这个字段进行更新。可以避免脏读和不可重复读,但幻读问题依然存在
5.serializable:确保事务可以从一个表读取相同的行,在这个事务持续期间禁止其他事务对该表进行插入,更新和删除操作所有并发问题都可以避免,但性能十分低下
4.事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持
5.oracle支持的两种事务隔离级别:read_commited和serializable
6.mysql支持四种事务隔离级别
3.设置隔离事务属性
1.用@Transaction注解声明式的管理事务时可以在@Transaction的isolation属性中设置隔离级别
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
2.在Spring2.x事务通知中,可以在<tx:method>元素中指定隔离级别
4.设置回滚事务属性
1.默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚,而受检查异常不会
2.事务的回滚规则可以通过@Transaction注解的rollbackFor和noRollbackFor属性来定义。这两个属性被声明为Class[]类型的,因此可以为这两个属性指定多个异常类
1.rollbackFor:遇到时必须进行回滚
2.noRollbackFor:一组异常类,遇到时必须不回滚
@Transactional (propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
rollbackFor={IOException.class,SQLException.class},
noRollbackFor=ArithmrticException.class)
3.在Spring2.x事务通知中,可以在<tx:method>元素中指定回滚规则,如果有不止一种异常,用逗号分隔
5.超时和只读属性
1.由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响
2.如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化
3.超市事务属性:事务在强制回滚之前可以保持多久,这样可以防止长期运行的事务占用资源
4.只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务
设置超时和只读事务属性
1.超市和只读属性可以在@Transaction注解中定义,超时属性以秒为单位来计算
@Transactional (propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
rollbackFor={IOException.class,SQLException.class},
noRollbackFor=ArithmrticException.class
readOnly=true,timeout=30)
2.在Spring2.x事务通知中,超时和只读属性可以在<tx:method>元素中指定
eg:
1.准备数据库表
account表:username(用户名),balance(账户余额)
book表:isbn(书号),price(价格)
book_stock表:isbn(书号),stock(库存)
2.创建接口BookShopDao.java
public interface BookShopDao {
public int findBookPriceByIsBn(String isbn);
public void updateBookStock(String isbn);
public void updateUserAccount(String username,int price);
}
3.实现类BookShopDaoImpl Dao层方法
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jbdcTemplate;
@Override
public int findBookPriceByIsBn(String isbn) {
// TODO Auto-generated method stub
String sql = "SELECT price from book WHERE isbn = ?";
return jbdcTemplate.queryForObject(sql, Integer.class,isbn);
}
@Override
public void updateBookStock(String isbn) {
// TODO Auto-generated method stub
//检索库存
String sql2 = "SELECT stock from book_stock WHERE isbn = ?";
int stock = jbdcTemplate.queryForObject(sql2, Integer.class,isbn);
if(stock == 0) {
throw new RuntimeException("库存不足!");
}
//更新库存
String sql = "UPDATE book_stock SET stock = stock - 1 where isbn = ?";
jbdcTemplate.update(sql,isbn);
}
@Override
public void updateUserAccount(String username, int price) {
// TODO Auto-generated method stub
· //查询账户余额
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = jbdcTemplate.queryForObject(sql2, Integer.class,username);
if(balance < price) {
throw new RuntimeException("余额不足!");
}
//更新余额
String sql = "UPDATE account set balance = balance - ? WHERE username = ?";
jbdcTemplate.update(sql,price,username);
}
}
4.创建Service层接口 BookShopService.java
public interface BookShopService {
//有人要买书
public void purchase(String username,String isbn);
}
5.创建BookShopServiceImpl.java实现类
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
@Transactional
@Override
public void purchase(String username, String isbn) {
// TODO Auto-generated method stub
//获取单价
int price = bookShopDao.findBookPriceByIsBn(isbn);
//更新库存
bookShopDao.updateBookStock(isbn);
//更新余额
bookShopDao.updateUserAccount(username, price);
}
}
6.新建配置文件
<?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"
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.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:component-scan base-package="org.spring.tx.dao,org.spring.tx.serivce"></context:component-scan>
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0的数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
</bean>
<!-- 配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
7.测试类
public class Text1 {
private ApplicationContext ctx;
private BookShopDao bookShopDao;
private BookShopService bookShopService;
{
ctx = new ClassPathXmlApplicationContext("applicationCoutext-tx.xml");
bookShopDao = (BookShopDao) ctx.getBean("bookShopDao");
bookShopService = (BookShopService) ctx.getBean("bookShopService");
}
@Test//lisi买编号为1001的书
public void testPurchase() {
bookShopService.purchase("lisi", "1001");
}
@Test//根据用户更新账户余额
public void testUpdateUserAccount() {
bookShopDao.updateUserAccount("lisi", 10);
}
@Test//根据id更新库存
public void testUpdateBookStock() {
bookShopDao.updateBookStock("1001");
}
@Test//根据id查询价格
public void testFindBookPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsBn("1001"));
}
}
eg:如果同时买两本书
1.准备数据库表
account表:username(用户名),balance(账户余额)
book表:isbn(书号),price(价格)
book_stock表:isbn(书号),stock(库存)
2.创建接口BookShopDao.java
public interface BookShopDao {
public int findBookPriceByIsBn(String isbn);
public void updateBookStock(String isbn);
public void updateUserAccount(String username,int price);
}
3.实现类BookShopDaoImpl Dao层方法
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jbdcTemplate;
@Override
public int findBookPriceByIsBn(String isbn) {
// TODO Auto-generated method stub
String sql = "SELECT price from book WHERE isbn = ?";
return jbdcTemplate.queryForObject(sql, Integer.class,isbn);
}
@Override
public void updateBookStock(String isbn) {
// TODO Auto-generated method stub
//检索库存
String sql2 = "SELECT stock from book_stock WHERE isbn = ?";
int stock = jbdcTemplate.queryForObject(sql2, Integer.class,isbn);
if(stock == 0) {
throw new RuntimeException("库存不足!");
}
//更新库存
String sql = "UPDATE book_stock SET stock = stock - 1 where isbn = ?";
jbdcTemplate.update(sql,isbn);
}
@Override
public void updateUserAccount(String username, int price) {
// TODO Auto-generated method stub
· //查询账户余额
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = jbdcTemplate.queryForObject(sql2, Integer.class,username);
if(balance < price) {
throw new RuntimeException("余额不足!");
}
//更新余额
String sql = "UPDATE account set balance = balance - ? WHERE username = ?";
jbdcTemplate.update(sql,price,username);
}
}
4.创建Service层接口 BookShopService.java
public interface BookShopService {
//有人要买书
public void purchase(String username,String isbn);
}
5.创建BookShopServiceImpl.java实现类
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
@Transactional
@Override
public void purchase(String username, String isbn) {
// TODO Auto-generated method stub
//获取单价
int price = bookShopDao.findBookPriceByIsBn(isbn);
//更新库存
bookShopDao.updateBookStock(isbn);
//更新余额
bookShopDao.updateUserAccount(username, price);
}
}
6.创建Cashier接口
public interface Cashier {
//买多本书
public void checkout(String username,List<String> isbns);
}
7.创建CashierImpl.java实现类
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
@Override
public void checkout(String username, List<String> isbns) {
// TODO Auto-generated method stub
for(String isbn : isbns) {
bookShopService.purchase(username, isbn);
}
}
}
8.新建配置文件
<?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"
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.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:component-scan base-package="org.spring.tx.dao,org.spring.tx.serivce"></context:component-scan>
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0的数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
</bean>
<!-- 配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
9.测试类
public class Text1 {
private ApplicationContext ctx;
private BookShopDao bookShopDao;
private BookShopService bookShopService;
private Cashier cashier;
{
ctx = new ClassPathXmlApplicationContext("applicationCoutext-tx.xml");
bookShopDao = (BookShopDao) ctx.getBean("bookShopDao");
bookShopService = (BookShopService) ctx.getBean("bookShopService");
cashier = (Cashier) ctx.getBean("cashier");
}
@Test//买多本书:如果余额不足只够买一本,则买一本
public void testCheckout() {
cashier.checkout("lisi", Arrays.asList("1001","1002"));
}
@Test//lisi买编号为1001的书
public void testPurchase() {
bookShopService.purchase("lisi", "1001");
}
@Test//根据用户更新账户余额
public void testUpdateUserAccount() {
bookShopDao.updateUserAccount("lisi", 10);
}
@Test//根据id更新库存
public void testUpdateBookStock() {
bookShopDao.updateBookStock("1001");
}
@Test//根据id查询价格
public void testFindBookPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsBn("1001"));
}
}