基于注解方式
事务,一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
例如:在银行转账的时候,假设要从账户A转账100元到账户B,只允许两种情况发生:①转账成功,即账户A余额减少100并且账户B余额增加100 ②转账失败,即账户A的余额不变并且账户B的余额不变。 除了这两种情况之外的都绝对不允许发生。
BookShopDao接口
1
2
3
4
5
6
7
8
9
10
11
|
public
interface
BookShopDao {
//根据书号获取书的单价
public
int
findBookPriceByIsbn(String isbn);
//更新书的库存,使书号对应的库存-1
public
void
updateBookStock(String isbn);
//更新用户的账户余额,是username的balance-price
public
void
updateUserAccount(String username,
int
price);
}
|
BookShopDao接口的实现类
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
38
|
@Repository
(
"bookShopDao"
)
public
class
BookShopImpl
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 s =
"select stock from book_stock where isbn = ?"
;
int
stock = jdbcTemplate.queryForObject(s, Integer.
class
, isbn);
//System.out.println("stock:" + stock);
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 s =
"select balance from account where username = ?"
;
int
balance = jdbcTemplate.queryForObject(s, Integer.
class
, username);
if
(balance < price) {
//System.out.println("余额不足");
throw
new
UserAccountException(
"余额不足"
);
}
String sql =
"update account set balance = balance - ? where username = ?"
;
jdbcTemplate.update(sql, price, username);
}
}
|
BookStockException.java和UserAccountException.java为自定义的两个异常类,继承RuntimeException。
BookShopService接口
1
2
3
4
|
public
interface
BookShopService {
public
void
purchase(String username, String isbn);
}
|
BookShopService接口的实现类
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
|
@Service
(
"bookShopService"
)
public
class
BookShopServiceImpl
implements
BookShopService{
@Autowired
private
BookShopDao bookShopDao;
//添加事务注解。
//当用户的账户余额不足并且书的库存足够多的时候,
//在执行findBookPriceByIsbn的时候不会抛出异常,在执行updateBookStock的时候也不会抛出异常。
//而在执行updateUserAccount的时候会抛出“余额不足”的异常。
//显然这次购买是不成功的。如果没有声明式事务,则书的库存会-1.显然这样是有问题的。
//添加了声明式事务之后,下面的三个方法要么都执行成功,否则都不会对数据库进行操作。
//这样就能保证数据的正确性。
@Transactional
@Override
public
void
purchase(String username, String isbn) {
//获取书的单价
int
price = bookShopDao.findBookPriceByIsbn(isbn);
//更新书的库存
bookShopDao.updateBookStock(isbn);
//更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
|
applicationContext.xml配置
1
2
3
4
5
6
7
8
9
10
|
<!-- 配置Spring的JdbcTemplate -->
<
bean
id
=
"jdbcTemplate"
class
=
"org.springframework.jdbc.core.JdbcTemplate"
>
<
property
name
=
"dataSource"
ref
=
"dataSource"
></
property
>
</
bean
>
<!-- 配置事务管理器 -->
<
bean
id
=
"tansactionManager"
class
=
"org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<
property
name
=
"dataSource"
ref
=
"dataSource"
></
property
>
</
bean
>
<!-- 启用事务注解 -->
<
tx:annotation-driven
transaction-manager
=
"tansactionManager"
/>
|
测试
1
2
3
4
5
6
7
8
|
private
BookShopService bookShopService =
null
;
applicationContext =
new
ClassPathXmlApplicationContext(
"applicationContext.xml"
);
bookShopService = applicationContext.getBean(BookShopService.
class
);
@Test
public
void
testBookShopService() {
bookShopService.purchase(
"umgsai"
,
"1001"
);
}
|
事务的传播行为及事务的注解配置
定义接口CashierImpl.java及实现类。实现类如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@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实现类
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
38
39
|
@Service
(
"bookShopService"
)
public
class
BookShopServiceImpl
implements
BookShopService{
@Autowired
private
BookShopDao bookShopDao;
/**添加事务注解。假设A方法调用purchase方法。
*1.使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时
*如何使用事务,默认取值为REQUIRED,即使用A方法的事务。
*REQUIRES_NEW:使用purchase方法自己的事务,A方法的事务会被挂起。
*2.使用isolation指定事务的隔离级别,最常用的取值为READ_COMMITTED。
*3.默认情况下Spring的声明式事务对所有的运行时异常进行回滚。也可以通过对应的属性进行设置。
*noRollbackFor={UserAccountException.class} 指定对于UserAccountException异常不回滚。
*通常情况下回滚属性取默认值即可。
*4.使用readOnly指定事务是否只读。表示这个事务只读取事务不更新事务,帮助数据库引擎优化事务。
*如果是一个只读取数据库数据的方法应设置readOnly=true。
*5.使用timeout指定强制回滚之前事务可以占用的时间。单位为秒。如果超时则会强制回滚。
*/
@Transactional
(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED
)
@Override
public
void
purchase(String username, String isbn) {
/*
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
//获取书的单价
int
price = bookShopDao.findBookPriceByIsbn(isbn);
//更新书的库存
bookShopDao.updateBookStock(isbn);
//更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
|
测试方法
1
2
3
4
|
@Test
public
void
testTransactionalPropagation() {
cashier.checkout(
"umgsai"
, Arrays.asList(
"1001"
,
"1002"
));
}
|
基于配置文件方式
BookShopDao接口
1
2
3
4
5
6
7
8
9
10
11
|
public
interface
BookShopDao {
//根据书号获取书的单价
public
int
findBookPriceByIsbn(String isbn);
//更新书的库存,使书号对应的库存-1
public
void
updateBookStock(String isbn);
//更新用户的账户余额,是username的balance-price
public
void
updateUserAccount(String username,
int
price);
}
|
BookShopDao接口的实现类
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
38
39
|
public
class
BookShopImpl
implements
BookShopDao{
private
JdbcTemplate jdbcTemplate;
public
void
setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this
.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 s =
"select stock from book_stock where isbn = ?"
;
int
stock = jdbcTemplate.queryForObject(s, Integer.
class
, isbn);
//System.out.println("stock:" + stock);
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 s =
"select balance from account where username = ?"
;
int
balance = jdbcTemplate.queryForObject(s, Integer.
class
, username);
if
(balance < price) {
//System.out.println("余额不足");
throw
new
UserAccountException(
"余额不足"
);
}
String sql =
"update account set balance = balance - ? where username = ?"
;
jdbcTemplate.update(sql, price, username);
}
}
|
BookStockException.java和UserAccountException.java为自定义的两个异常类,继承RuntimeException。
BookShopService接口
1
2
3
4
|
public
interface
BookShopService {
public
void
purchase(String username, String isbn);
}
|
BookShopService接口的实现类
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
|
public
class
BookShopServiceImpl
implements
BookShopService{
private
BookShopDao bookShopDao;
public
void
setBookShopDao(BookShopDao bookShopDao) {
this
.bookShopDao = bookShopDao;
}
@Override
public
void
purchase(String username, String isbn) {
/*
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
//获取书的单价
int
price = bookShopDao.findBookPriceByIsbn(isbn);
//更新书的库存
bookShopDao.updateBookStock(isbn);
//更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
|
applicationContext.xml配置
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
beans
xmlns
=
"http://www.springframework.org/schema/beans"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:context
=
"http://www.springframework.org/schema/context"
xmlns:tx
=
"http://www.springframework.org/schema/tx"
xmlns:aop
=
"http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<
context:component-scan
base-package
=
"com.spring"
></
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
=
"driverClass"
value
=
"${jdbc.driverClass}"
></
property
>
<
property
name
=
"jdbcUrl"
value
=
"${jdbc.jdbcUrl}"
></
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
>
<!-- 配置bean -->
<
bean
id
=
"bookShopDao"
class
=
"com.spring.tx.xml.BookShopImpl"
>
<
property
name
=
"jdbcTemplate"
ref
=
"jdbcTemplate"
></
property
>
</
bean
>
<
bean
id
=
"bookShopService"
class
=
"com.spring.tx.xml.BookShopServiceImpl"
>
<
property
name
=
"bookShopDao"
ref
=
"bookShopDao"
></
property
>
</
bean
>
<
bean
id
=
"cashier"
class
=
"com.spring.tx.xml.CashierImpl"
>
<
property
name
=
"bookShopService"
ref
=
"bookShopService"
></
property
>
</
bean
>
<!-- 1.配置事务管理器 -->
<
bean
id
=
"transactionManager"
class
=
"org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<
property
name
=
"dataSource"
ref
=
"dataSource"
></
property
>
</
bean
>
<!-- 2.配置事务属性 -->
<
tx:advice
id
=
"txAdvice"
transaction-manager
=
"transactionManager"
>
<
tx:attributes
>
<!-- 根据方法名指定事务的属性 -->
<
tx:method
name
=
"purchase"
propagation
=
"REQUIRES_NEW"
timeout
=
"3"
/>
<
tx:method
name
=
"*"
/>
<
tx:method
name
=
"get*"
read-only
=
"true"
/>
<
tx:method
name
=
"find*"
read-only
=
"true"
/>
</
tx:attributes
>
</
tx:advice
>
<!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
<
aop:config
>
<
aop:pointcut
expression
=
"execution(* com.spring.tx.xml.BookShopService.*(..))"
id
=
"txPointCut"
/>
<
aop:advisor
advice-ref
=
"txAdvice"
pointcut-ref
=
"txPointCut"
/>
</
aop:config
>
<
aop:config
>
<
aop:pointcut
expression
=
"execution(* com.spring.tx.xml.Cashier.*(..))"
id
=
"cashierPointCut"
/>
<
aop:advisor
advice-ref
=
"txAdvice"
pointcut-ref
=
"cashierPointCut"
/>
</
aop:config
>
</
beans
>
|
测试
1
2
3
4
|
@Test
public
void
testTransactionalPropagation() {
cashier.checkout(
"umgsai"
, Arrays.asList(
"1001"
,
"1002"
));
}
|