一、说在前面
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 点击打开链接