您的位置:首页 > 编程语言 > Java开发

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 事务 spring