Spring4.0学习15--事务管理(1)

1.事务简介

事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.
事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用

举个例子:a给b转账1W,a的账户减少1W,b的账户增加1W,这就是一个事务。不能出现a少了1W而b账户得钱没有增加,也不能出现b账户的钱增加,a账户钱没变的情况。

事务的四个关键属性(ACID)

  • 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
  • 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
  • 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
  • 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.

2.Spring 中的事务管理

作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制.
Spring 既支持编程式事务管理, 也支持声明式的事务管理.
编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码.
声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理.

3.Spring 中的事务管理器

Spring 从不同的事务管理 API 中抽象了一整套的事务机制. 开发人员不必了解底层的事务 API, 就可以利用这些事务机制. 有了这些事务机制, 事务管理代码就能独立于特定的事务技术了.
Spring 的核心事务管理抽象是Interface PlatformTransactionManager它为事务管理封装了一组独立于技术的方法. 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的.

4.Spring 中的事务管理器的不同实现

  • Class DataSourceTransactionManager: 在应用程序中只需要处理一个数据源, 而且通过 JDBC 存取
  • Class JtaTransactionManager: 在 JavaEE 应用服务器上用 JTA(Java Transaction API) 进行事务管理
  • Class HibernateTransactionManager: 用 Hibernate 框架存取数据库
  • 事务管理器以普通的 Bean 形式声明在 Spring IOC 容器中

5.用事务通知声明式地管理事务

事务管理是一种横切关注点
为了在 Spring 2.x 中启用声明式事务管理, 可以通过 tx Schema 中定义的 <tx:advice> 元素声明事务通知, 为此必须事先将这个 Schema 定义添加到 <beans> 根元素中去.
声明了事务通知后, 就需要将它与切入点关联起来. 由于事务通知是在 <aop:config> 元素外部声明的, 所以它无法直接与切入点产生关联. 所以必须在 <aop:config> 元素中声明一个增强器通知与切入点关联起来.
由于 Spring AOP 是基于代理的方法, 所以只能增强公共方法. 因此, 只有公有方法才能通过 Spring AOP 进行事务管理.

6.用 @Transactional 注解声明式地管理事务

除了在带有切入点, 通知和增强器的 Bean 配置文件中声明事务外, Spring 还允许简单地用 @Transactional 注解来标注事务方法.
为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解. 根据 Spring AOP 基于代理机制, 只能标注公有方法.
可以在方法或者类级别上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的.
在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了.
如果事务处理器的名称是 transactionManager, 就可以在<tx:annotation-driven> 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器.

7.小练习:买书,数据库内对应书籍的库存减1,账户余额相应减少。库存不能小于0,余额也是。

直接在之前的 springStudy-2 项目中继续开发。新建 com.springtx 包,包内新建 7 个类,修改applicationContext.xml

BookShopDao.java:

1
2
3
4
5
6
7
8
public interface BookShopDao {
// 根据书号获取书的单价
public int findBookPriceByIsbn(String isbn);
// 更新书的库存,使书号对应的库存 - 1
public void updateBookStock(String isbn);
// 更新用户的账户余额:使 username 的 balance - price
public void updateUserAccount(String username, int price);
}

BookShopDaoImpl.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int findBookPriceByIsbn(String isbn) {
// TODO Auto-generated method stub
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 bookStock where isbn = ?";
int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if(stock == 0) {
throw new BookStockException("库存不足");
}
String sql = "update bookStock 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);
}
public BookShopDaoImpl() {
super();
// TODO Auto-generated constructor stub
}
}

BookShopService.java:

1
2
3
public interface BookShopService {
public void purcase(String username, String isbn);
}

BookShopServiceImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
// 添加事务注解
@Transactional
@Override
public void purcase(String username, String isbn) {
// 1.获取书单价格
int price = bookShopDao.findBookPriceByIsbn(isbn);
// 2.更新书的库存
bookShopDao.updateBookStock(isbn);
// 3.更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}

Springtx.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Springtx {
private ApplicationContext ctx = null;
private BookShopDao bookShopDao = null;
private BookShopService bookShopService = null;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
bookShopDao = ctx.getBean(BookShopDao.class);
bookShopService = ctx.getBean(BookShopService.class);
}
@Test
public void testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
@Test
public void testBookShopDaoUpdateBookStock() {
bookShopDao.updateBookStock("1001");
}
@Test
public void testBookShopDaoupdateUserAccount() {
bookShopDao.updateUserAccount("吴彦祖", 100);
}
@Test
public void testBookShopService() {
bookShopService.purcase("吴彦祖", "1001");
}
}

applicationContext.xml新增代码:

1
2
3
4
5
6
7
8
// 自动扫描包
<context:component-scan base-package="com.springtx"></context:component-scan>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager" />

注意:
1.如果不在 applicationContext.xml 内增加 配置事务管理器和启动事务注解,也不在 BookShopServiceImpl 中增加 @Transactional 注解,那么运行时发现如果书的库存足够多,即使提醒账户余额不足,账户余额没有减少的情况下,书的库存依然一次次减少,这明显是不正确的,使用了事务注解则避免了这种情况出现,使之回归合理。
2.运行时我遇到了报错
org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [applicationContext.xml]; nested exception is java.lang.NoClassDefFoundError: org/springframework/context/event/EventListenerFactory...Caused by:java.lang.ClassNotFoundException:org.springframework.context.event.EventListenerFactory


解决:这个坑也浪费了我两个多小时吧,期间百度了很久,倒是看到人家说包没导进来,我看了下导进来了,后来在CSDN上看到个人说版本不一致或缺实包,当时没理解什么意思,就这么短短一句话其实答案已经有了,只可惜没理解他意思,后来还是找了公司祥哥看看,他一来就说你这 spring 包版本太混乱了,你先把他们统一。
然后我照着他说的把spring版本统一了,果然能跑了!

很尴尬,我之前一直以为版本不一致没什么问题的,因为这么久也过来了,但没想到真的是这个原因。要长记性。

Newer Post

Spring4.0学习16--事务管理(2)

事务传播属性 当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为. 需求(继续之前的练习)用户可以同时购买两种书籍,但是 …

继续阅读
Older Post

Spring4.0学习14--使用NamedParameterJdbcTemplate

在 JDBC 模板中使用具名参数在经典的 JDBC 用法中, SQL 参数是用占位符 ? 表示,并且受到位置的限制. 定位参数的问题在于, 一旦参数的顺序发生变化, 就必须改变参数绑定.在 Spring JDBC 框架中, 绑定 SQL 参数的另一种选择是使用具名参数(named parameter …

继续阅读