Spring中声明式事务管理

本文介绍了一个简单的图书管理系统,该系统使用事务管理确保数据一致性和完整性。通过具体代码示例展示了如何配置事务管理器并应用@Transactional注解解决因异常导致的数据不一致问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、说在前面

1、事务管理是企业级应用程序开发中必不可少的技术,  用来确保数据的完整性和一致性。
2、事务就是一系列的动作, 它们被当做一个单独的工作单元。这些动作要么全部完成, 要么全部不起作用。
3、事务的四个关键属性(ACID)
(1)原子性(atomicity): 事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成要么完全不起作用。
(2)一致性(consistency): 一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中。
(3)隔离性(isolation): 可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏。

(4)持久性(durability): 一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果被写到持久化存储器中。

二、样例如下:

(一)需求

本实例是简单的图书管理系统,关系如下图所示:


(二)数据库表

在数据库中有3张表。分别保存图书库存、图书信息和用户账户信息。数据库表结构如下图所示:
1、account表:


2、book表:


3、book_stock表


(三)代码

1、db.properties数据库配置信息

jdbc.user=root  
jdbc.password=111111  
jdbc.driverClass=com.mysql.jdbc.Driver  
jdbc.jdbcUrl=jdbc:mysql:///spring-5  
  
jdbc.initialPoolSize=5  
jdbc.maxPoolSize=10  
2、applicationContext.xml配置信息

<!-- 配置自动扫描的包 -->  
<context:component-scan base-package="com.at.spring.tx"></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="jdbcUrl" value="${jdbc.jdbcUrl}"></property>  
    <property name="driverClass" value="${jdbc.driverClass}"></property>  
      
    <property name="initialPoolSize" value="${jdbc.initialPoolSize}"></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>  
3、BookShopDao接口

package com.at.spring.tx;  
  
public interface BookShopDao {  
  
    //根据书号获取书的价格  
    public int findBookPriceByIsbn(String isbn);  
      
    //更新书的库存,使书号对应的库存减一  
    public void updateBookStock(String isbn);  
      
    //更新用户的账户余额:使username的余额balance-price,因为这里一次就简单的只能买一本书。  
    public void updateUserAccount(String username,int price);  
}  
4、BookShopDaoImpl实现类

package com.at.spring.tx;  
  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.jdbc.core.JdbcTemplate;  
import org.springframework.stereotype.Repository;  
  
@Repository("bookShopDao")  
public class BookShopDaoImpl implements BookShopDao{  
  
    @Autowired  
    private JdbcTemplate jdbcTemplate;  
      
    @Override  
    public int findBookPriceByIsbn(String isbn) {  
        String sql = "SELECT price FROM book WHERE isbn = ?";  
        return jdbcTemplate.queryForObject(sql,Integer.class,isbn);  
    }  
  
    @Override  
    public void updateBookStock(String 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, new Object[]{price,username});  
    }  
  
}  
5、BookShopService接口

package com.at.spring.tx;  
  
public interface BookShopService {  
  
    public void purchase(String username,String isbn);  
}  

6、BookShopServiceImpl实现类

package com.at.spring.tx;  
  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
import org.springframework.transaction.annotation.Transactional;  
  
@Service("bookShopService")  
public class BookShopServiceImpl implements BookShopService{  
  
    @Autowired  
    private BookShopDao bookShopDao;  
      
    public void purchase(String username, String isbn) {  
        //1、获取书的单价  
        int price = bookShopDao.findBookPriceByIsbn(isbn);  
          
        //2、更新书的库存  
        bookShopDao.updateBookStock(isbn);  
          
        //3、更新用户余额  
        bookShopDao.updateUserAccount(username, price);  
    }  
}  
7、BookStockException异常类,用来表示图书库存不足
package com.at.spring.tx;  
  
public class BookStockException extends RuntimeException{  
  
    private static final long serialVersionUID = 1L;  
  
    public BookStockException() {  
        super();  
        // TODO Auto-generated constructor stub  
    }  
  
    public BookStockException(String message, Throwable cause,  
            boolean enableSuppression, boolean writableStackTrace) {  
        super(message, cause, enableSuppression, writableStackTrace);  
        // TODO Auto-generated constructor stub  
    }  
  
    public BookStockException(String message, Throwable cause) {  
        super(message, cause);  
        // TODO Auto-generated constructor stub  
    }  
  
    public BookStockException(String message) {  
        super(message);  
        // TODO Auto-generated constructor stub  
    }  
  
    public BookStockException(Throwable cause) {  
        super(cause);  
        // TODO Auto-generated constructor stub  
    }  
}  
8、UserAccountException异常类,用户表示用户账户余额不足

package com.at.spring.tx;  
  
public class UserAccountException extends RuntimeException{  
  
    private static final long serialVersionUID = 1L;  
  
    public UserAccountException() {  
        super();  
        // TODO Auto-generated constructor stub  
    }  
  
    public UserAccountException(String message, Throwable cause,  
            boolean enableSuppression, boolean writableStackTrace) {  
        super(message, cause, enableSuppression, writableStackTrace);  
        // TODO Auto-generated constructor stub  
    }  
  
    public UserAccountException(String message, Throwable cause) {  
        super(message, cause);  
        // TODO Auto-generated constructor stub  
    }  
  
    public UserAccountException(String message) {  
        super(message);  
        // TODO Auto-generated constructor stub  
    }  
  
    public UserAccountException(Throwable cause) {  
        super(cause);  
        // TODO Auto-generated constructor stub  
    }  
}  
9、TestTransaction测试类

package com.at.spring.tx;  
  
  
import org.junit.Test;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class TestTransaction {  
  
    private ApplicationContext ct = null;  
    private BookShopDao bookShopDao = null;  
    private BookShopService bookShopService = null;  
      
    {  
        ct = new ClassPathXmlApplicationContext("applicationContext.xml");  
        bookShopDao = ct.getBean(BookShopDao.class);  
        bookShopService = ct.getBean(BookShopService.class);  
    }  
      
    //测试BookShopService的purchase功能  
    @Test  
    public void testBookShopService(){  
        bookShopService.purchase("luoyepiaoxue2014", "1001");  
    }  
      
    //测试用户的账户余额  
    @Test  
    public void testUpdateUserAccount(){  
        bookShopDao.updateUserAccount("luoyepiaoxue2014", 200);  
    }  
      
    //测试通过isbn号码来更新库存数量  
    @Test  
    public void testUpdateBookStock(){  
        bookShopDao.updateBookStock("1001");  
    }  
      
    //测试通过isbn号码查询书的价格  
    @Test  
    public void testFindBookPriceByIsbn() {  
        System.out.println(bookShopDao.findBookPriceByIsbn("1002"));  
    }  
  
}  

  (四)代码问题发现

1、数据库中初始的账户余额是160,isbn为1001的库存为8。
2、第一次执行TestTransaction测试类中的testBookShopService()函数之后,账户余额变成了60,库存变成了7,这种情况是正确的。
3、第二次仍然执行TestTransaction测试类中的testBookShopService()函数,但是这个时候跑出了账户余额不足的异常,查阅数据库发现,用户余额60元没有发生改变,但是库存却变成了6,这种情况是错误的。

(五)解决上述的问题

1、在applicationContext.xml中加入如下的事务配置(另外记得先加入tx命名空间)。

<!-- 配置事务管理器 -->  
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
    <property name="dataSource" ref="dataSource"></property>  
</bean>  
<!-- 使得事务注解生效 -->  
<tx:annotation-driven transaction-manager="transactionManager"/>  
2、在需要使用事务管理的方法前加上@Transactional注解

package com.at.spring.tx;  
  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
import org.springframework.transaction.annotation.Transactional;  
  
@Service("bookShopService")  
public class BookShopServiceImpl implements BookShopService{  
  
    @Autowired  
    private BookShopDao bookShopDao;  
      
    //添加事务注解  
    @Transactional  
    public void purchase(String username, String isbn) {  
        //1、获取书的单价  
        int price = bookShopDao.findBookPriceByIsbn(isbn);  
          
        //2、更新书的库存  
        bookShopDao.updateBookStock(isbn);  
          
        //3、更新用户余额  
        bookShopDao.updateUserAccount(username, price);  
    }  
  
}  

By luoyepiaoxue2014
微博地址: http://weibo.com/luoyepiaoxue2014  点击打开链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值