Spring学习笔记----事务管理

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的配置文件)



    
    
    
    
    
    
        
        
        
        
        
        
    
    
    
        
    

    
    
        
    
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添加一个事务管理器,添加完之后如下所示



    
    
    
    
    
    
        
        
        
        
        
        
    
    
    
        
    

    
    
        
    

    
    
        
    

    
    

注意,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 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 isbns) {
        for(Integer isbn : isbns)
        {
            bookShopService.purchase(userName,isbn);
        }
    }
}
Test.java
public static void testTransactionalPropagation(String userName, List 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.

    
        
    
    
    
        
    
    
    
        
    
2.配置事务管理器bean

    
        
    
3.引入 tx 命名空间
xmlns:tx="http://www.springframework.org/schema/tx"
4.配置事务各个属性(方法名可以使用通配符*)

    
        
            
            
                        
                        
        
    
5.引入aop命名空间
xmlns:aop="http://www.springframework.org/schema/aop"

    
        
        
    












你可能感兴趣的:(Spring)