[toc]
[toc]
Spring事务管理
1. Spring
事务管理概述
1.1 编程式事务管理
使用原生的JDBC API
实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来
控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。
1.2 声明式事务管理
以前通过复杂的编程来编写一个事务,替换为只需要告诉Spring哪个方法是事务方法即可;
- Spring自动进行事务控制
- 大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
-
事务管理代码的固定模式作为一种横切关注点
,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
1.3 Spring提供的事务管理器
- Spring既支持编程式事务管理,也支持声明式的事务管理。
- Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
- 事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
1.4 事务管理器的主要实现
DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理
HibernateTransactionManager:用Hibernate框架存取数据库
2. 基于注解的事务配置
2.1 测试需要数据表
account
1 2 3 4 5 6 7 8 9 10 11
| DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` ( `username` varchar(50) NOT NULL, `balance` int(11) DEFAULT NULL, PRIMARY KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
INSERT INTO `account` VALUES ('Tom', 1000);
SET FOREIGN_KEY_CHECKS = 1;
|
book
1 2 3 4 5 6 7 8 9 10 11 12
| DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` ( `isbn` varchar(50) NOT NULL, `book_name` varchar(100) DEFAULT NULL, `price` int(11) DEFAULT NULL, PRIMARY KEY (`isbn`) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
INSERT INTO `book` VALUES ('ISBN-001', 'book01', 100);
SET FOREIGN_KEY_CHECKS = 1;
|
book_stock
1 2 3 4 5 6 7 8 9 10 11
| DROP TABLE IF EXISTS `book_stock`;
CREATE TABLE `book_stock` ( `isbn` varchar(50) NOT NULL, `stock` int(11) DEFAULT NULL, PRIMARY KEY (`isbn`) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
INSERT INTO `book_stock` VALUES ('ISBN-001', 1000); SET FOREIGN_KEY_CHECKS = 1;
|
2.2 添加依赖
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 73 74 75 76 77 78
| <dependencies>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.9</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.0.0.RELEASE</version> </dependency>
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.0.0.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.0.0.RELEASE</version> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> <scope>provided</scope> </dependency>
<dependency> <groupId>net.sourceforge.cglib</groupId> <artifactId>com.springsource.net.sf.cglib</artifactId> <version>2.2.0</version> </dependency>
<dependency> <groupId>org.aopalliance</groupId> <artifactId>com.springsource.org.aopalliance</artifactId> <version>1.0.0</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>com.springsource.org.aspectj.weaver</artifactId> <version>1.6.8.RELEASE</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.0.0.RELEASE</version> </dependency> </dependencies>
|
2.3 配置文件
db.properties
1 2 3 4
| jdbc.user=root jdbc.password=****** jdbc.jdbcUrl=jdbc:mysql://cdb-o6r75r3g.bj.tencentcdb.com:10015/tx jdbc.driverClass=com.mysql.jdbc.Driver
|
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
| <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.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd ">
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<context:component-scan base-package="cn.justweb"></context:component-scan>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="url" value="${jdbc.jdbcUrl}"/> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property> </bean>
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
|
2.4 BookDao
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
|
@Repository public class BookDao {
@Autowired private JdbcTemplate jdbcTemplate;
public void updateBalance(String userName,int price){ String sql = "UPDATE account set balance=balance-? where username=?"; jdbcTemplate.update(sql,price,userName); }
public int getPrice(String isbn){ String sql = "select price from book where isbn=?"; Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn); return price; }
public void updateStock(String isbn){ String sql = "update book_stock set stock=stock-1 where isbn=?"; jdbcTemplate.update(sql,isbn); } }
|
2.5 BookService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
@Service public class BookService {
@Autowired private BookDao bookDao;
@Transactional public void checkout(String username,String isbn){ bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username,price); } }
|
2.6 测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public class TxTest { ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test public void test(){ BookService bookService = ioc.getBean(BookService.class); System.out.println("bookService = " + bookService); System.out.println(bookService.getClass().getClassLoader());
bookService.checkout("Tom","ISBN-001");
System.out.println("结账完成!"); } }
|
3. 基于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 73 74 75
| <?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.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="cn.justweb"></context:component-scan>
<context:property-placeholder location="classpath:dbconfig.properties" /> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="url" value="${jdbc.jdbcUrl}"/> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" value="#{dataSource}"></property> </bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
<aop:config> <aop:pointcut expression="execution(* cn.justweb.ser*.*.*(..))" id="txPoint"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/> </aop:config>
<tx:advice id="myAdvice" transaction-manager="transactionManager"> <tx:attributes>
<tx:method name="*"/> <tx:method name="checkout" propagation="REQUIRED" timeout="10"/> <tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice> </beans>
|
4. Spring
事务管理细节
@Transactional
中的属性,比如说隔离级别,事务传播行为,超时时间,是否只读等。
4.1 事务的隔离级别
- isolation 事务的隔离级别
- 数据库事务详解
4.2 事务的传播行为
- 事务传播属性可以在
@Transactional
注解的propagation
属性中定义。
- 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
4.2.1 REQUIRED传播行为
@Transactional(propagation = Propagation.REQUIRED)
required
4.2.2 REQUIRES_NEW传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
requires_new
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
|
|
4.3 事务的超时和只读属性
@Transactional(timeout = 10,readOnly = true)
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
- 超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
- 只读事务属性 : 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
☆