声明式事务
一、声明式事务简介
-
之前需要通过复杂的编程编写一个事务,现在只需要告诉Spring哪个方法是事务方法,Spring 可以自动进行事务控制
-
事务方法:方法内的操作满足原子性等事务的四个特征 (要么都执行,要么都不执行)
-
Spring中的事务管理器DataSourceTransactionManager可以在目标方法运行时进行事务控制
二、声明式事务使用步骤
- 使用c3p0连接数据库
(1) src目录创建c3p0.properties文件
jdbc.username=root
jdbc.password=root
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/tx?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT
jdbc.driverClass=com.mysql.jdbc.Driver
(2) xml中配置
<context:property-placeholder location="classpath:c3p0.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
</beans>
(3) 测试
@org.junit.Test
public void test() throws SQLException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
DataSource dataSource = (DataSource)ioc.getBean("dataSource");
System.out.println(dataSource.getConnection());
//com.mchange.v2.c3p0.impl.NewProxyConnection@7d68ef40
}
- 使用JdbcTemplate操作数据库
(1) 导包
(2) 写配置
JdbcTemplate中有一个带参构造器JdbcTemplate(DataSource dataSource);
需要创建JdbcTemplate对象操作数据库,为了不去new 对象,将其注册到容器中
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 引用容器中已经创建好的使用c3p0连接数据库的dataSource对象 -->
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
(3) 测试
@org.junit.Test
public void test2() {
//将book表中book_name为book01的书的price修改为50
ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
JdbcTemplate jdbcTemplate = (JdbcTemplate)ioc.getBean("jdbcTemplate");
String sql = "UPDATE book SET price=? WHERE book_name=?";
int update = jdbcTemplate.update(sql, 50, "book01");
System.out.println("受影响的行数:" + update);
//受影响的行数:1
}
- 配置事务管理器(事务切面)
(1) 导包
(2) xml中编写
- 给事务方法加@Transactional注解
(1) 创建BookDao
@Service
public class BookDao {
@Autowired
JdbcTemplate jdbcTemplate;
//修改账户的余额
public void updateBalance(String username, int price) {
String sql = "UPDATE account SET balance=balance-? WHERE username=?";
jdbcTemplate.update(sql, price, username);
}
//修改图书的库存
public void updateStock(int stock, String isbn) {
String sql = "UPDATE book_stock SET stock=? WHERE isbn=?";
jdbcTemplate.update(sql, stock, isbn);
}
}
(2) 创建BookService
@Service
public class BookService {
@Autowired
BookDao bookDao;
//事务方法
@Transactional
public void update(String username, int price, String isbn, int stock) {
//修改余额
bookDao.updateBalance(username, price);
//修改库存
bookDao.updateStock(stock, isbn);
int i = 10 / 0; //出现算术异常、事务方法中的两个操作均无法完成(回滚)
}
}
三、事务细节
以下都是@Transactional注解中的属性
- timeout
超时设置,int类型,事务方法的执行超出指定的时长后若未结束,则终止并回滚,以秒为单位
用法:@Transactional(timeout=3)
- readOnly
设置为只读事务,boolean类型,可以加快查询速度,只有事务方法中是查询操作时可以使用, 若有修改操作,会报错
用法:@Transactional(readOnly=true)
- noRollbackFor / rollbackFor
noRollbackFor:设置某些原本回滚的异常事务可以不回滚
rollbackFor:设置某些原本不回滚的异常事务让其回滚
二者都是class[ ]类型
异常分类:
运行时异常:编译时可以不处理,默认都回滚
编译时异常:编译时必须处理(try - catch、throws),默认不回滚
用法:@Transactional(noRollbackFor={NullPointerException.class, xxx.class})
- isolation
设置事务的隔离级别,Isolation类型
用法:
四、事务传播行为
-
简介
如果有多个事务进行嵌套,也就是事务方法中调用事务方法,若其中一个事务出异常,则嵌 套起来的这些事务哪些随其回滚,哪些不回滚 -
使用方法
- 传播行为分类
如:
(1) 使用了REQUIRED属性的事务方法如果嵌套在一个大的事务方法中,则二者属于同一个 事务线上,一个失败,另一个也跟着回滚
(2) 使用了REQUIRED_NEW属性的事务方法如果嵌套在一个大的事务方法中,则二者的事 务相互独立,一个失败,另一个不受影响(不会回滚)
- 图示传播行为
下图中的事务方法MulTx()中调用了两个事务方法checkout()和updatePrice();
讨论两个被嵌套的事务方法使用不同传播行为的属性的情况:
- 注意点
(1) 被嵌套的事务方法使用@Transactional(timeout=3)等属性不会对整个事务起作用,必须在 嵌套它的大事务中使用这些属性才会对整个事务起作用
(2) 在本类的事务方法中嵌套本类的事务方法,则属于同一个事务
五、使用xml配置的事务控制
- 配置事务管理器(事务切面)
<!--
基于xml配置的事务;依赖tx名称空间和aop名称空间
1)、配置事务管理器(事务切面)
2)、配置出事务方法
3)、告诉Spring哪些方法是事务方法,事务切面按照给定的切入点表达式去切入事务方法
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务切面按照给定的切入点表达式去切入事务方法 -->
<aop:config>
<!-- 指明要切入哪些方法,但这些方法不一定使用事务 -->
<aop:pointcut expression="execution(* com.atguigu.ser*.*.*(..))" id="txPoint"/>
<!-- advice-ref指向配置事务管理器 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!--
配置事务管理器
transaction-manager="transactionManager":指定是配置哪个事务管理器;
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事务属性 -->
<tx:attributes>
<!-- 指明切入点表达式中的哪些方法是事务方法,-->
<tx:method name="*"/> <!-- 所有方法都加事务 -->
<tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>