Spring学习笔记----事务管理
2016-07-15 15:47
661 查看
Spring声明事务
了解spring使用事务之前,首先让我们看看如何手动写一个事务需求:一个book shop的管理实现
dataSource.properties (数据源信息)
jdbc.user = root jdbc.password =123456 jdbc.driverClass = com.mysql.jdbc.Driver jdbc.jdbcUrl = jdbc:mysql:///spring2 jdbc.initPoolSize=5 jdbc.maxPoolSize=10
applicationContext.xml (Spring的配置文件)
<?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" 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"> <!--自动扫描--> <context:component-scan base-package="cn.limbo.spring"></context:component-scan> <!--导入资源文件--> <context:property-placeholder location="dataSource.properties"></context:property-placeholder> <!--配置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="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <!--配置Spring的JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参的构造器,所以要为构造器指定参数--> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg ref="dataSource"></constructor-arg> </bean> </beans>BookShopDao.java (书店数据库操作接口)
package cn.limbo.spring.tx; /** * Created by Limbo on 16/7/15. */ public interface BookShopDao { //根据书号获取书的单价 public int findBookPriceByIsbn(int isbn); //更新书的库存,是书号对应的库存 - 1 public void updateBookStock(int isbn); //更新用户的账户余额:是userName的balance - price public void updateUserAccount(String userName,int price); }
BookShopDaoImpl.java (书店数据库操作实现)
package cn.limbo.spring.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; /** * Created by Limbo on 16/7/15. */ @Repository("bookShopDao") public class BookShopDaoImpl implements BookShopDao{ @Autowired private JdbcTemplate jdbcTemplate; @Override public int findBookPriceByIsbn(int isbn) { String sql = "SELECT price FROM book WHERE isbn = ?"; return jdbcTemplate.queryForObject(sql,Integer.class,isbn); } @Override public void updateBookStock(int isbn) { //检查书的库存是否足够,若不够,则跑出异常 String sql2 = "SELECT stock FROM book_stock WHERE isbn = ? "; int stock = jdbcTemplate.queryForObject(sql2,Integer.class,isbn); if(stock == 0) { throw new BookStockException("库存不足!"); } String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?"; jdbcTemplate.update(sql,isbn); } @Override public void updateUserAccount(String userName, int price) { //验证余额是否足够,若不够则抛出异常 String sql2 = "SELECT balance FROM account WHERE username = ? "; int balance = jdbcTemplate.queryForObject(sql2,Integer.class,userName); if(balance < price) { throw new UserAccountException("余额不足!"); } String sql = "UPDATE account SET balance = balance - ? WHERE username = ?"; jdbcTemplate.update(sql,price,userName); } }
BookShopService.java (书店服务接口)
package cn.limbo.spring.tx; /** * Created by Limbo on 16/7/15. */ public interface BookShopService { public void purchase(String userName,int price); }BookShopServiceImpl.java (书店服务实现)
package cn.limbo.spring.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * Created by Limbo on 16/7/15. */ @Service("bookShopService") public class BookShopServiceImpl implements BookShopService{ @Autowired private BookShopDao bookShopDao; @Override public void purchase(String userName, int isbn) { //1.获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2.更新库存 bookShopDao.updateBookStock(isbn); //3.更新用户余额 bookShopDao.updateUserAccount(userName,price); } }
BookStockException.java (书库存异常)
package cn.limbo.spring.tx; /** * Created by Limbo on 16/7/15. */ public class BookStockException extends RuntimeException { public BookStockException() { super(); } public BookStockException(String message) { super(message); } public BookStockException(String message, Throwable cause) { super(message, cause); } public BookStockException(Throwable cause) { super(cause); } protected BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } }UserAccountException.java (用户余额异常)
package cn.limbo.spring.tx; /** * Created by Limbo on 16/7/15. */ public class UserAccountException extends RuntimeException { public UserAccountException() { super(); } public UserAccountException(String message) { super(message); } public UserAccountException(String message, Throwable cause) { super(message, cause); } public UserAccountException(Throwable cause) { super(cause); } protected UserAccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } }SpringTransactionTest.java (测试方法)
package cn.limbo.spring.tx; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by Limbo on 16/7/15. */ public class SpringTransactionTest { private static ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); private static BookShopDao bookShopDao = (BookShopDao) ctx.getBean("bookShopDao"); private static BookShopService bookShopService = (BookShopService) ctx.getBean("bookShopService"); public static void testBookShopDaoFindPriceByIsbn(int isbn) { System.out.println(bookShopDao.findBookPriceByIsbn(isbn)); } public static void testBookShopDaoUpdateStock(int isbn) { bookShopDao.updateBookStock(isbn); } public static void testBookShopUpdateUserAccount(String name,int price) { bookShopDao.updateUserAccount(name,price); } public static void testBookShopService(String userName,int isbn) { bookShopService.purchase(userName,isbn); } public static void main(String[] args) { testBookShopService("AA",1001); } }
上面的testBookShopService这个方法是我们执行事务的方法,但是我们可以发现,这个方法并不满足事务的ACID原则,如果余额不足的情况下,书的储藏数量还是会减少,这时候我们就该用上spring的事务管理器了
为applicationContext.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" 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/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--自动扫描--> <context:component-scan base-package="cn.limbo.spring"></context:component-scan> <!--导入资源文件--> <context:property-placeholder location="dataSource.properties"></context:property-placeholder> <!--配置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="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <!--配置Spring的JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参的构造器,所以要为构造器指定参数--> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg ref="dataSource"></constructor-arg> </bean> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--启用事务注解--> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>
注意,tx的命名空间不要导错了,刚开始我就是导错了然后没有出现transaction-manager这玩意,搞得我烦死了
然后在实现事务的方法上加上@Transactional注解就好了,重写后的BookShopServiceImpl.java如下
package cn.limbo.spring.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * Created by Limbo on 16/7/15. */ @Service("bookShopService") public class BookShopServiceImpl implements BookShopService{ @Autowired private BookShopDao bookShopDao; @Transactional //添加事务注解 @Override public void purchase(String userName, int isbn) { //1.获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2.更新库存 bookShopDao.updateBookStock(isbn); //3.更新用户余额 bookShopDao.updateUserAccount(userName,price); } }至此,我们就实现了Spring声明式事务的方法
事务的传播行为
指事务方法调用另外一个事务方法时,表现出的属性。打个比方,你去饭店吃饭,遇到熟人,然后熟人招呼你吃饭,这个时候你有两个选择,你可以选择和熟人一起吃,也可以自己吃。在上述的表述中,新事务就是我,旧事务是熟人,和熟人一起吃饭就是沿用原先事务,自己吃饭就是创建新的事务,下面看代码新建一个接口和一个类
Cashier.java
package cn.limbo.spring.tx; import java.util.List; /** * Created by Limbo on 16/7/16. */ public interface Cashier { public void checkOut(String userName,List<Integer> isbns); }CashierImpl.java
package cn.limbo.spring.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; /** * Created by Limbo on 16/7/16. */ @Service("cashier") public class CashierImpl implements Cashier{ @Autowired private BookShopService bookShopService; @Transactional @Override //现在,我们要买一堆书 public void checkOut(String userName, List<Integer> isbns) { for(Integer isbn : isbns) { bookShopService.purchase(userName,isbn); } } }
Test.java
public static void testTransactionalPropagation(String userName, List<Integer> isbns) { cashier.checkOut(userName,isbns); } public static void main(String[] args) { testTransactionalPropagation("AA", Arrays.asList(1001,1002)); }
这时候,我们发现bookShopService.purchase是一个事务,但是checkOut也是一个事务,这时候执行会有什么样的结果呢(此时bookShopService.purchase()还是和上面一样)?
此时,我们发现,在余额足够买1001但是无法买1002时,事务会自动回滚,回滚到两本书都不买的情况,因为这个时候checkOut使用的是原先的事务,没有任何变化,如果我们要实现在余额只够买1001但是不够买1002时仍然可以买1001的话,只要把bookShopService.purchase上的@Transactional改成@Transactional(propagation = Propagation.REQUIRES_NEW)即可,修改后的booShopService.java如下
package cn.limbo.spring.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * Created by Limbo on 16/7/15. */ @Service("bookShopService") public class BookShopServiceImpl implements BookShopService{ @Autowired private BookShopDao bookShopDao; //添加事务注解 //使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用的时候 //如何使用事务,默认取值为REQUIRED,即使用调用方法的事务 //还有一个取值是REQUIRES_NEW:新建一个自己的事务,调用的事务方法被挂起 @Transactional(propagation = Propagation.REQUIRES_NEW) @Override public void purchase(String userName, int isbn) { //1.获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2.更新库存 bookShopDao.updateBookStock(isbn); //3.更新用户余额 bookShopDao.updateUserAccount(userName,price); } }附上propagation所有的属性:
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
前两个最为常用
事务的隔离,回滚,只读,过期
一言不合,直接贴代码,要点写注释上咯package cn.limbo.spring.tx; 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; /** * Created by Limbo on 16/7/15. */ @Service("bookShopService") public class BookShopServiceImpl implements BookShopService{ @Autowired private BookShopDao bookShopDao; //添加事务注解 //1.使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用的时候 // 如何使用事务,默认取值为REQUIRED,即使用调用方法的事务 // 还有一个取值是REQUIRES_NEW:新建一个自己的事务,调用的事务方法被挂起 //2.使用isolation指定事务的隔离级别,最常用的是Isolation.READ_COMMITTED //3.默认情况下,Spring的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置 // 通常情况下,取默认值即可 // noRollbackFor = {UserAccountException.class}指的是对事务发生这一类的异常时,不要回滚了,继续执行 //4.使用readOnly指定事务是否为只读,表示这个事务只读取数据但不更新数据,这样可以帮助数据库优化事务 // 若真的是一个只读取数据库的值的方法,应该设置readOnly=true, 默认值为false,表示可读写 //5.使用timeout指定强制回滚之前事务可以占用的时间,如果指定timeout=3,则事务执行3秒后强制回滚,不管是否执行成功 // @Transactional(propagation = Propagation.REQUIRES_NEW , // isolation = Isolation.READ_COMMITTED , // noRollbackFor = {UserAccountException.class}) @Transactional(propagation = Propagation.REQUIRES_NEW , isolation = Isolation.READ_COMMITTED , readOnly = false , timeout = 3) //timeout的单位是秒 @Override public void purchase(String userName, int isbn) { //1.获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2.更新库存 bookShopDao.updateBookStock(isbn); //3.更新用户余额 bookShopDao.updateUserAccount(userName,price); } }
事务的xml配置方法
1.配置普通的 controller,service ,dao 的bean.<!-- 配置 dao ,service --> <bean id="bookShopDao" class="com.liujl.spring.tx.xml.BookShopDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <bean id="bookShopService" class="com.liujl.spring.tx.xml.serivice.impl.BookShopServiceImpl"> <property name="bookShopDao" ref="bookShopDao"></property> </bean> <bean id="cashier" class="com.liujl.spring.tx.xml.serivice.impl.CashierImpl"> <property name="bookShopService" ref="bookShopService"></property> </bean>2.配置事务管理器bean
<!-- 配置事务管理器 hibernate、jpa都是类似的这样配 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>3.引入
tx 命名空间
xmlns:tx="http://www.springframework.org/schema/tx"4.配置事务各个属性(方法名可以使用通配符*)
<!-- 配置事务属性 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="purchase" propagation="REQUIRED"/> <tx:method name="checkout" propagation="REQUIRED"/> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> </tx:attributes> </tx:advice>5.引入aop命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
<!-- 配置事务的切点,并把事务切点和事务属性不关联起来 --> <aop:config> <aop:pointcut expression="execution(* com.liujl.spring.tx.xml.serivice.*.*(..))" id="txPointCut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config>
相关文章推荐
- jdbc中的Statement和PreparedStatement接口对象
- 一个jar包里的网站
- 一个jar包里的网站之文件上传
- 一个jar包里的网站之返回对媒体类型
- Spring和ThreadLocal
- Spring Boot 开发微服务
- Spring AOP动态代理-切面
- Spring整合Quartz(JobDetailBean方式)
- Spring整合Quartz(JobDetailBean方式)
- SQL Server误区30日谈 第1天 正在运行的事务在服务器故障转移后继续执行
- 浅析SQL Server中包含事务的存储过程
- Mysql中的事务是什么如何使用
- MySql的事务使用与示例详解
- C#分布式事务的超时处理实例分析
- C#中的事务用法实例分析
- SQL Server的事务操作隔离模式介绍
- MySQL中事务概念的简洁学习教程
- JDBC 数据库常用连接 链接字符串
- C#处理Access中事务的方法