1、通过注解的方式配置事务
创建以下3张表
create table book(
isbn varchar(50) primary key,
book_name varchar(100),
price int
) ;
create table book_stock(
isbn varchar(50) primary key,
stock int,
check(stock > 0)
);
create table account(
username varchar(50) primary key,
balance int,
check(balance > 0)
);
配置事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
创建BookShopDao接口
public interface BookShopDao {
//根据书号获取书的单价
int findBookPriceByIsbn(String isbn);
//更新书的库存,书书号对应的书的库存减一
void updateBookStock(String isbn);
//更新用户的账户余额,使username的balance减对应的书的price
void updateUserAccount(String username,int price);
}
创建BookShopDao接口实现类BookShopDaoImpl
@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 count = jdbctemplate.queryForObject(sql2, Integer.class, isbn);
if(count <=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
public interface BookShopService {
//用户购买一本书
void purchase(String username,String isbn);
}
创建BookShopService
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao;
//添加事务注解
@Transactional
@Override
public void purchase(String username, String isbn) {
//查询书的价格
int price = bookShopDao.findBookPriceByIsbn(isbn);
//更新书的库存
bookShopDao.updateBookStock(isbn);
//更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
测试
public class TestTx {
ApplicationContext ctx = null;
BookShopDao bookShopDao = null;
BookShopService bookShopService = null;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
bookShopDao = (BookShopDao) ctx.getBean("bookShopDao");
bookShopService = (BookShopService) ctx.getBean("bookShopService");
}
@Test
public void testFindBookPriceByIsbn(){
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
@Test
public void testUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@Test
public void testUpdateUserAccount(){
bookShopDao.updateUserAccount("AA", bookShopDao.findBookPriceByIsbn("1001"));
}
@Test
public void testPurchase(){
bookShopService.purchase("AA", "1001");
}
}
当执行testPurchase会发现如果没加声明事务管理器,当余额不足够买一本书时,书的库存量会减一但用户的余额不会减,声明了事务之后,当余额不足够买一本书时,书的库存和用户的余额都不会减少
事务的传播行为
使用propagation指定事务的传播行为,即当前的事务方法被另一个事务方法调用时,如何使用事务
默认取值为REQUIRED,即使用调用方法的事务
REQUIRED:如果有事务运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
REQUIRED_NEW:当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起
SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTED:当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
MANDATORY:当前的方法不应该运行在事务内部,如果没有正在运行的事务,就抛出异常
NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED:如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行
在以上测试事务的基础上添加如下代码
Cashier接口,添加一个同时购买多本书的方法
public interface Cashier {
void checkout(String username,List<String> isbns);
}
Cashier接口的实现类CashierImpl
@Service("cashier")
public class CashierImpl implements Cashier{
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for (String isbn : isbns) {
bookShopService.purchase(username, isbn);
}
}
}
在BookShopServiceImpl的purchase方法上指定事务的传播行为
/**
* 添加事务注解
* 使用propagation指定事务的传播行为,即当前的事务方法被另一个事务方法调用时,如何使用事务
* 默认取值为REQUIRED,即使用调用方法的事务
* REQUIRES_NEW:新开一个事务,即使用自己的事务,调用的事方法的事务被挂起
*/
@Transactional(propagation=Propagation.REQUIRED)
@Override
public void purchase(String username, String isbn) {
//查询书的价格
int price = bookShopDao.findBookPriceByIsbn(isbn);
//更新书的库存
bookShopDao.updateBookStock(isbn);
//更新用户余额
bookShopDao.updateUserAccount(username, price);
}
@Test
public void testTransactionPropagation(){
cashier.checkout("AA", Arrays.asList("1001","1002"));
}
当事务的传播行为为REQUIRED时,若用户余额不足,则所购买的所有书籍库存和用户余额都不会减少
当事务的传播行为为REQUIRES_NEW时,若用户余额不够支付所购买的所有书籍,则会购买前面能够购买的书籍,相应书籍库存和用户余额会减少
事务的隔离级别、回滚、只读、过期属性
/**
* 添加事务注解
* 1、使用propagation指定事务的传播行为,即当前的事务方法被另一个事务方法调用时,如何使用事务
* 默认取值为REQUIRED,即使用调用方法的事务
* REQUIRES_NEW:新开一个事务,即使用自己的事务,调用的事方法的事务被挂起
* 2、使用isolation指定事务的隔离级别,最常用的取值为READ_COMMITTED
* 3、默认情况下Spring的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置,
* 通常情况下取默认值
* 4、使用readOnly指定事务是否为只读,表示这个事务只读数据但不更新数据,
* 这样可以帮助数据库引擎优化事务,若真的是一个只读取数据库的方法,应设置readOnly=true
* 5、使用timeout指定强制回滚之前事务可以占用的时间,单位秒
*/
@Transactional(propagation=Propagation.REQUIRED,
isolation=Isolation.READ_COMMITTED,
//noRollbackFor={UserAccountException.class},
readOnly=false,
timeout=3
)
@Override
public void purchase(String username, String isbn) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//查询书的价格
int price = bookShopDao.findBookPriceByIsbn(isbn);
//更新书的库存
bookShopDao.updateBookStock(isbn);
//更新用户余额
bookShopDao.updateUserAccount(username, price);
}
2、基于配置文件的方式配置事务
<!-- 配置bean -->
<bean id="bookShopDao" class="beans.tx.xml.BookShopDaoImpl">
<property name="jdbctemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="bookShopService" class="beans.tx.xml.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<bean id="cashier" class="beans.tx.xml.CashierImpl">
<property name="bookShopService" ref="bookShopService"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切入点,以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* beans.tx.xml.BookShopService.*(..))
|| execution(* beans.tx.xml.Cashier.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
以上内容均为学习笔记,可能不全面,推荐大家去百度传课学习尚硅谷的免费网课,讲的很好