1、通过注解的方式配置事务的步骤:
①配置事务管理器
②开启注解方式声明事务
③在需要配置的方法上添加@Transaction注解
2、事务传播性:
(1)概念:当事务方法被另一个事务方法调用时,制定事务该如何传播
(例子:有事务方法A和B,A多次调用B,事务传播性就是用于指定A应该在什么时候提交数据,是B的所有调用都完成后就提交,还是完成一次调用就提交一次,还是其他情况)
(2)传播属性:(默认REQUIRED,常用的有REQUIRED和REQUIRED_NEW)
①REQUIRED:事务内所有子事务都成功结束后才提交数据(事务都不抛出异常时提交)
②REQUIRED_NEW:事务内所有子事务都是一个独立事务,执行一个挂起一个,结束一个提交一个的数据(事务完成一个提交一个,抛出异常的不提交并停止整个事务)
③SUPPORTS:如果有事务在运行,则在该事务内运行,否则可不运行在事务中
④NOT_SUPPORTED:当前事务不运行在事务中,如果有运行的事务,则将该事务挂起
⑤MANDATORY:当前方法必须运行在事务中,如果没有正在运行的事务,则抛出异常
⑥NEVER:当前方法不运行在事务中,如果有运行的事务,则抛出异常
⑦nested:如果有事务在运行,当前事务方法嵌套到运行事务中运行,否则开启一个新事务运行
3、逻辑
(1)写一个被调用的事务方法A,再写一个新的事务方B法调用该事务方法A
(2)在事务方法B上配置传播@Transaction,在事务方法A中配置@Transaction的传播属性(即:传播属性是配置在真正有操作逻辑的事务方法上,如操作数据库的事务方法)
(3)调用事务方法B
4、使用步骤
①创建对应的bean
②写数据库连接数据的properties
③编写spring配置文件,导入资源文件
④编写spring配置文件,配置C3P0数据源
⑤编写spring配置文件,配置JdbcTemplate
⑥编写spring配置文件,基于注解 配置事务管理器
⑦编写spring配置文件,开启注解方式声明事务
⑧对事务类用annotation配置注入关系,对事务添加@Transactional注解
⑨编写代码,获取相关配置文件,连接数据库
⑩编写代码,调用事务逻辑代码
5、事务无效的原因排查及解决:
①检查数据库的表的存储引擎,如:mysql的时候,表的存储引擎为MyISAM时,是非事务性表,不支持事务,需要改为InnoDB,可以用SHOW TABLE STATUS LIKE ‘表名’ 来查看存储引擎是什么,使用ALTER TABLE 表名
ENGINE = InnoDB 来修改储存引擎为InnoDB
②事务抛出的异常为Exception而不是RuntimeException时,不会回滚(抛出异常需要这样写:throw new RuntimeException(“xxxxxxxxxxxx”);)
③@Transactional注解放在private方法上,不会回滚,但是不会报错
6、文件
①TestMain.java:测试类
②MyException.java:自定义错误抛出类
③TxDao.java:更新账户信息的方法接口
④TxDaoImpl.java:更新账户信息的实现类
⑤ProductDao.java:更新商品信息的方法接口
⑥ProductDaoImpl.java:更新商品信息的实现类
⑦SummaryPruchase.java:交易接口(根据交易信息,调用账户信息和商品信息的更新方法实现类)
⑧SummaryPruchaseImpl.java:交易接口实现类
⑨db.properties:数据库配置信息
⑩jdbcTemplateContext.xml:配置文件
9、TestMain.java
package com.demo.sshtest;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
/*
通过注解的方式配置事务的步骤:
1、配置事务管理器
2、开启注解方式声明事务
3、在需要配置的方法上添加@Transaction注解
事务传播性:
1、概念:当事务方法被另一个事务方法调用时,制定事务该如何传播
(例子:有事务方法A和B,A多次调用B,事务传播性就是用于指定A应该在什么时候提交数据,是B的所有调用都完成后就提交,还是完成一次调用就提交一次,还是其他情况)
2、传播属性:(默认REQUIRED,常用的有REQUIRED和REQUIRED_NEW)
(1)REQUIRED :事务内所有子事务都成功结束后才提交数据(事务都不抛出异常时提交)
(2)REQUIRED_NEW :事务内所有子事务都是一个独立事务,执行一个挂起一个,结束一个提交一个的数据(事务完成一个提交一个,抛出异常的不提交并停止整个事务)
(3)SUPPORTS :如果有事务在运行,则在该事务内运行,否则可不运行在事务中
(4)NOT_SUPPORTED:当前事务不运行在事务中,如果有运行的事务,则将该事务挂起
(5)MANDATORY :当前方法必须运行在事务中,如果没有正在运行的事务,则抛出异常
(6)NEVER :当前方法不运行在事务中,如果有运行的事务,则抛出异常
(7)nested :如果有事务在运行,当前事务方法嵌套到运行事务中运行,否则开启一个新事务运行
3、步骤:
(1)写一个被调用的事务方法A,再写一个新的事务方B法调用该事务方法A
(2)在事务方法B上配置传播@Transaction,在事务方法A中配置@Transaction的传播属性(即:传播属性是配置在真正有操作逻辑的事务方法上,如操作数据库的事务方法)
(3)调用事务方法B
*/
public class TestMain {
private static ApplicationContext applicationContext = null;
private static JdbcTemplate jdbcTemplate;
private static ProductDao productDao = null;
private static TxDao txDao=null;
private static SummaryPurchase summaryPurchase=null;
public static void main(String[] args) {
try{
testConnection();
//testTX();
testTransactionPropagation();
}catch(Exception e){
System.out.println(e.getMessage());
}
}
//获取相关配置文件,连接数据库
public static void testConnection() throws SQLException{
applicationContext = new ClassPathXmlApplicationContext("jdbcTemplateContext.xml");
DataSource dataSource = applicationContext.getBean(DataSource.class);
System.out.println(dataSource.getConnection());
jdbcTemplate = (JdbcTemplate)applicationContext.getBean("jdbcTemplate");
}
//测试事务
public static void testTX(){
txDao = (TxDao)applicationContext.getBean(TxDao.class);
txDao.pruchase(1, 1, 2);
}
//测试事务传播性
public static void testTransactionPropagation(){
summaryPurchase = (SummaryPurchase)applicationContext.getBean(SummaryPurchase.class);
Map<Integer,Integer> productMap = new HashMap<Integer,Integer>();
productMap.put(1, 1);
productMap.put(2, 1);
productMap.put(3, 1);
summaryPurchase.sPurchase1(1, productMap);
summaryPurchase.sPurchase2(1, productMap);
}
}
10、MyException.java
package com.demo.sshtest;
public class MyException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
public MyException() {
super();
}
public MyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
public MyException(String message) {
super(message);
}
public MyException(Throwable cause) {
super(cause);
}
}
11、TxDao.java
package com.demo.sshtest;
public interface TxDao {
public void pruchase(int buterId,int productId,int quantity);
public void pruchaseREQUIRES_NEW(int buterId, int productId, int quantity);
}
12、TxDaoImpl.java
package com.demo.sshtest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/*
1、使用propagation指定事务传播行为,默认REQUIRED
2、使用isolation指定事务隔离级别,常用READ_COMMITTED
3、默认情况下,Spring的声明事务会对所有运行异常进行回滚
4、使用noRollbackFor={类名.class}来设置对于指定报错类的报错,不阻止抛出异常,但是不回滚
5、使用rollbackFor={类名.class}或者rollbackForClass="全类名"来设置对于指定报错类的报错,,不阻止抛出异常,但是回滚
6、使用readOnly指定事务是否未只读(优化数据库逻辑,读取数据库时候用,更删改不用)
7、使用timeout指定强制回滚之前事务可以占用的时间(以秒做单位)
*/
@Service("txDao")
public class TxDaoImpl implements TxDao{
@Autowired
private ProductDao productDao;
//添加事务注解
@Transactional(propagation=Propagation.REQUIRED,
isolation=Isolation.READ_COMMITTED,
readOnly=false,
timeout=3)
public void pruchase(int buterId, int productId, int quantity) {
try{Thread.sleep(4000);}catch(Exception e){}
//1、获取单价
double price = productDao.findPricebyProductPriceID(productId);
System.out.println("productId:"+productId+" -> product price:"+price);
//2、更新库存
productDao.updateProductQuantity(productId,quantity);
//3、更新账户余额
productDao.AccountPurchase(buterId,price,quantity);
System.out.println("buyer pruchase");
}
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={MyException.class},
readOnly=false)
public void pruchaseREQUIRES_NEW(int buterId, int productId, int quantity) {
//1、获取单价
double price = productDao.findPricebyProductPriceID(productId);
System.out.println("productId:"+productId+" -> product price:"+price);
//2、更新库存
productDao.updateProductQuantity(productId,quantity);
//3、更新账户余额
productDao.AccountPurchase(buterId,price,quantity);
System.out.println("buyer pruchase");
}
13、ProductDao.java
package com.demo.sshtest;
public interface ProductDao {
public double findPricebyProductPriceID(int id);
public void updateProductQuantity(int id,int quantity);
public void AccountPurchase(int id,double price,int quantity);
}
14、ProductDaoImpl.java
package com.demo.sshtest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@Service("productDao")
public class ProductDaoImpl implements ProductDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public double findPricebyProductPriceID(int id) {
String sql = "select price from product where id=?";
return jdbcTemplate.queryForObject(sql,double.class,id);
}
public void updateProductQuantity(int id, int quantity) {
String sqlCheck = "select quantity from product where id=?";
int Check_quantity = jdbcTemplate.queryForObject(sqlCheck,Integer.class,id);
if(Check_quantity<quantity){
throw new MyException("not enough quantity");
}
String sql = "update product set quantity=quantity-? where id=?";
jdbcTemplate.update(sql,quantity,id);
}
public void AccountPurchase(int id, double price, int quantity) {
String sqlCheck = "select balance from account where id=?";
int Check_balance = jdbcTemplate.queryForObject(sqlCheck,Integer.class,id);
if(Check_balance<(price*quantity)){
throw new MyException("not enough balance");
}
String sql = "update account set balance=balance-?*? where id=?";
jdbcTemplate.update(sql,price,quantity,id);
}
}
15、SummaryPruchase.java
package com.demo.sshtest;
import java.util.Map;
public interface SummaryPurchase {
public void sPurchase1(int buyerId,Map<Integer, Integer>productMap);
public void sPurchase2(int buyerId,Map<Integer, Integer>productMap);
}
16、SummaryPruchaseImpl.java
package com.demo.sshtest;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("summaryPurchase")
public class SummaryPurchaseImpl implements SummaryPurchase {
@Autowired
private TxDao txDao;
@Transactional
public void sPurchase1(int buyerId, Map<Integer, Integer> productMap) {
System.out.println("propagation=Propagation.REQUIRED");
for (Map.Entry<Integer, Integer> entry : productMap.entrySet()) {
txDao.pruchase(buyerId, entry.getKey(), entry.getValue());
}
}
@Transactional
public void sPurchase2(int buyerId, Map<Integer, Integer> productMap) {
System.out.println("propagation=Propagation.REQUIRES_NEW");
for (Map.Entry<Integer, Integer> entry : productMap.entrySet()) {
txDao.pruchaseREQUIRES_NEW(buyerId, entry.getKey(), entry.getValue());
}
}
}
17、db.properties
jdbc.user=root
jdbc.password=
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost/SpringTest
jdbc.initialPoolSize=5
jdbc.maxPoolSize=10
18、jdbcTemplateContext.xml
<?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"
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.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:component-scan base-package="com.demo.sshtest"></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>
<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate" 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" />
</bean>
<!-- 开启注解方式声明事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!--
事务无效的原因排查及解决:
1、检查数据库的表的存储引擎,如:mysql的时候,表的存储引擎为MyISAM时,是非事务性表,不支持事务,需要改为InnoDB,可以用SHOW TABLE STATUS LIKE '表名' 来查看存储引擎是什么,使用ALTER TABLE `表名` ENGINE = InnoDB 来修改储存引擎为InnoDB
2、事务抛出的异常为Exception而不是RuntimeException时,不会回滚(抛出异常需要这样写:throw new RuntimeException("xxxxxxxxxxxx");)
3、@Transactional注解放在private方法上,不会回滚,但是不会报错
-->
</beans>
19、项目目录
20、demo
https://download.csdn.net/download/qq_22778717/10483461