SSH笔记-事务管理(通过注解的方式配置事务)

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

猜你喜欢

转载自blog.csdn.net/qq_22778717/article/details/80718915