一文读懂Spring 事务管理(十一)

1. 前言

事务是开发中应用程序最关键的部分之一,最常见的事务类型是基于数据库的操作。在关系型数据库中一个事务可以是一条SQL语句,也可以是一组SQL语句单元。在Java程序中实现事务主要有声明式事务编程式事务管理两种实现方式。由于在开发中我们更关注业务本身逻辑的编程实现,无需在手动编码实现一个完整的事务管理模块,最好是开发人员通过声明方式定义事务需求,而Spring提供了对声明式事务良好的支持。

2. 事务的特性

我们都知道事务具有众所周知的ACID属性(原子性一致性隔离性持久性),日常开发中可以控制事务的传播性和超时,以及可以配置事务是否为只读并指定事务的隔离级别,而对于原子性一致性持久性 是无法进行控制的。Spring将上面对事务操作的属性都封装在TransactionDefinition接口中,而该接口被用于Spring的事务策略核心接口,即PlatformTransactionManager接口,其接口定义定义内容如下所示:
在这里插入图片描述
核心方法为getTransaction ,此方法根据 TransactionDefinition作为参数并返回TransactionStatus接口,此接口用于事务的控制执行,简单来说就是设置事务执行结果并检查事务是否结束或者为一个新的事务。

2.1 TransactionDefinition 接口

该接口定义了如下事务控制属性:

⓵ 隔离性(Isolation):即事务与其他事务的隔离程度,简单来说就是一个事务能否看到其他事务的数据更改,下表列出了事务的隔离级别。

隔离级别解释说明
ISOLATION_DEFAULT采用底层数据的默认隔离级别
ISOLATION_READ_UNCOMMITTED读未提交,这个容易造成脏读,因为它允许一个事务查看其他未提交的事务修改数据
ISOLATION_READ_COMMITTED读已提交,可以解决脏读问题,也是大多数数据库的默认隔离级别
ISOLATION_REPEATABLE_READ可重复读,比读未提交事务隔离程度更加严格
ISOLATION_SERIALIZABLE最严格的事务,所有的事务将进行串行化执行

⓶ 传播性(Propagation):指的是在事务调用时所发生的行为,下表列出了Spring提供的几种事务传播行为。

传播类型解释
PROPAGATION_REQUIRED支持当前的事务,如果当前事务不存在就新建一个事务
PROPAGATION_SUPPORTS支持当前事务,如果事务不存在,将以非事务方式运行
PROPAGATION_MANSATORY支持当前事务,如果事务不存在将抛异常
PROPAGATION_REQUIRES_NEW如果当前事务存在,将当前事务挂起并创建新的事务,如果当前事务不存在就新建一个事务
PROPAGATION_NOT_SUPPORTED不支持当前事务,以非事务的方式运行
PROPAGATION_NEVER不支持当前事务,如果当前事务存在就抛异常
PROPAGATION_NESTED如果当前事务存在,则执行一个内嵌的事务

⓷ 超时(TimeOut):定义了事务的完成时间(单位为秒),如果事务超时将会抛出异常
⓸ 只读(Read-only):如果一个事务中进行查询数据操作而不进行修改数据可以设置只读属性,可以加快程序访问速度。

2.2 TransactionStatus 接口

TransactionStatus接口继承了TransactionExecutionSavepointManagerFlushable 三个接口,其接口方法如下所示:
在这里插入图片描述
上述方法中我们最需要注意的是 setRollbackOnly 方法,此方法会导致事务回滚并结束当前的活动事务,isCompleted 方法表明当前事务是否已经结束。

3. 声明式事务管理案例演示

Spring框架的声明式事务支持可通过AOP代理来启用此支持,并且事务性的Advice由元数据(XML方式或者基于注解方式)。下面简单创建一个声明式事务管理案例。

3.1 导入相关依赖

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.2.5.RELEASE</spring.version>
        <jdbc.version>5.1.22</jdbc.version>
    </properties>

   <!--引入Spring依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring.version}</version>
        </dependency>
      
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
   
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
       <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.10</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.10</version>
        </dependency>
     <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${jdbc.version}</version>
        </dependency>

3.2 定义数据库实体类

⓵ 定义一个账户类

import lombok.Data;

/**
 * 账户表
 *
 * @author CodeGeekGao
 * @version Id: Account.java, v 1.0 2020/6/29 11:29 PM CodeGeekGao
 */
@Data
public class Account {
    private Integer id;
    private String accountName;
    private BigDecimal balance;
}

⓶ 定义一个产品类

import lombok.Data;

/**
 * 产品
 *
 * @author CodeGeekGao
 * @version Id: Product.java, v 1.0 2020/6/29 11:32 PM CodeGeekGao
 */
@Data
public class Product {
    private Integer id;
    private String productName;
    private BigDecimal price;
    private Integer storageId;
}

⓷ 定义一个库存类

import lombok.Data;

/**
 * 库存表
 *
 * @author CodeGeekGao
 * @version Id: Storage.java, v 1.0 2020/6/29 11:34 PM CodeGeekGao
 */
@Data
public class Storage {
    private Integer id;
    private Integer storageNumber;
    private Product product;
}

需要说明的是Product类的storageId 属性对应Storage类的主键id。

3.3 定义服务类

⓵ 定义账户服务接口及实现类

import java.math.BigDecimal;

/**
 * @author CodeGeekGao
 * @version Id: AccountService.java, v 1.0 2020/7/11 10:58 AM CodeGeekGao
 */
public interface AccountService {

    /**
     * 根据账户名称修改余额
     * @param accountName accountName
     * @param price price
     */
    public void updateBalance(String accountName, BigDecimal price);

    /**
     * 根据账户名称查询账户余额
     * @param accountName accountName
     * @return BigDecimal 余额
     */
    BigDecimal findAccountBalance(String accountName);
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
 * @author CodeGeekGao
 * @version Id: AccountServiceImpl.java, v 1.0 2020/7/11 10:59 AM CodeGeekGao
 */
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 根据账户名称修改余额
     * @param accountName accountName
     * @param price price
     */
    public void updateBalance(String accountName, BigDecimal price) {
        String sql= "update t_account set balance=balance-? where account_name=?";
        jdbcTemplate.update(sql,price,accountName);
    }

    @Override
    public BigDecimal findAccountBalance(String accountName) {
        String sql ="select balance from t_account where account_name =?";
        return jdbcTemplate.queryForObject(sql,BigDecimal.class,accountName);
    }
}

⓶ 产品服务接口及实现类


/**
 * @author CodeGeekGao
 * @version Id: ProductService.java, v 1.0 2020/7/11 10:58 AM CodeGeekGao
 */
public interface ProductService {

    /**
     * 根据产品的名称查询价格
     * @param productName productName
     * @return Product
     */
    Product findProductPrice(String productName);
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

/**
 * @author CodeGeekGao
 * @version Id: ProductServiceImpl.java, v 1.0 2020/7/11 11:05 AM CodeGeekGao
 */
@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 根据产品的名称查询价格
     * @param productName productName
     * @return Product
     */
    @Override
    public Product findProductPrice(String productName) {
        String sql ="select * from t_product where product_name=?";
        List<Product> query = jdbcTemplate.query(sql, new RowMapper<Product>() {
            @Override
            public Product mapRow(ResultSet resultSet, int i) throws SQLException {
                Product product = new Product();
                product.setId(resultSet.getInt("id"));
                product.setPrice(resultSet.getBigDecimal("price"));
                product.setProductName(resultSet.getString("product_name"));
                product.setStorageId(resultSet.getInt("t_storage_id"));
                return product;
            }
        }, productName);
        return query.get(0);
    }
}

⓷ 库存接口及实现类

/**
 * @author CodeGeekGao
 * @version Id: StorageService.java, v 1.0 2020/7/11 11:12 AM CodeGeekGao
 */
public interface StorageService {

    /**
     * 减库存
     * @param id id
     * @param number number
     */
    void decreaseStorage(Integer id,Long number);

    /**
     * 查询库存
     * @param id
     * @return
     */
    Integer storageNumberCount(Integer id);
}
import com.codegeek.aop.day5.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

/**
 * @author CodeGeekGao
 * @version Id: StorageServiceImpl.java, v 1.0 2020/7/11 11:14 AM CodeGeekGao
 */
@Service
public class StorageServiceImpl implements StorageService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 减库存
     * @param id id
     * @param number number
     */
    @Override
    public void decreaseStorage(Integer id, Long number) {
        String sql="update t_storage set storage_number=storage_number-? where id=?";
        jdbcTemplate.update(sql,number,id);
    }

    /**
     * 查询库存
     * @param id
     * @return
     */
    @Override
    public Integer storageNumberCount(Integer id) {
        String sql="select storage_number from t_storage where id=?";
        return jdbcTemplate.queryForObject(sql,Integer.class,id);
    }
}

⓸ 下单接口BuyService 及其实现类

/**
 * @author CodeGeekGao
 * @version Id: BuyService.java, v 1.0 2020/7/11 11:17 AM CodeGeekGao
 */
public interface BuyService {

    void buy(String userName,String productName,Long buyCount) throws Exception;
}
@Service
public class BuyServiceImpl  implements BuyService {

    @Autowired
    private AccountService accountService;
    @Autowired
    private ProductService productService;
    @Autowired
    private StorageService storageService;

    public void buy(String userName,String productName,Long buyCount) throws Exception {
        Product productItem = productService.findProductPrice(productName);
        if(productItem==null) throw new RuntimeException("该产品不存在");
        if (storageService.storageNumberCount(productItem.getStorageId())<buyCount) {
            throw new RuntimeException("该产品库存不足");
        }
        // 根据产品名称计算购买总额
        BigDecimal total = productItem.getPrice().multiply(BigDecimal.valueOf(buyCount));

        if (accountService.findAccountBalance(userName).compareTo(total)<0) {
            throw new RuntimeException("用户余额不足,无法购买产品");
        }
        // 更新余额
        accountService.updateBalance(userName,total);
        // 减去库存
        storageService.decreaseStorage(productItem.getStorageId(),buyCount);
    }
}

3.4 数据库表设计

⓵ 创建t_account 表

create table test.t_account
(
	id int auto_increment
		primary key,
	account_name varchar(255) null,
	balance decimal(19,2) null
)

⓶ 创建t_product表

create table test.t_product
(
	id int auto_increment
		primary key,
	price decimal(19,2) null,
	product_name varchar(255) null,
	t_storage_id int null
)
;

create index FKg2h11x128yniyj6yiojohsa04
	on test.t_product (t_storage_id)
;

⓷ 创建t_storage表

create table test.t_storage
(
	id int auto_increment
		primary key,
	storage_number int null
)
;

⓸ 初始化表数据

INSERT INTO test.t_account ( account_name, balance) VALUES ('小明', 10000.00);
INSERT INTO test.t_product (price, product_name, t_storage_id) VALUES (100.00, 'Java编程入门', 1);
INSERT INTO test.t_storage (storage_number) VALUES (1000);

3.5 XML配置文件

如果让程序能正常跑起来,还需要配置数据源、事务管理器、AOP配置等,其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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" 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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
        <!--包扫描路径-->
        <context:component-scan base-package="com.codegeek.aop.day5"/>
        <!--配置连接properties文件-->
        <context:property-placeholder location="classpath:aop/day5/jdbc.properties"/>

        <!--配置连接池-->
        <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
                <property name="driverClassName" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
        </bean>

        <bean class="org.springframework.jdbc.core.JdbcTemplate">
                <property name="dataSource" ref="dataSource"/>
        </bean>

        <!--&lt;!&ndash;() 匹配一个不接受任何参数的方法&ndash;&gt;-->
        <!--&lt;!&ndash;(..) 匹配一个接受任意数量参数的方法&ndash;&gt;-->
        <!--&lt;!&ndash;(*) 匹配了一个接受一个任何类型的参数的方法&ndash;&gt;-->
        <!--&lt;!&ndash;(*,String) 匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是String类型&ndash;&gt;-->
        <!--匹配包含aop包下包含impl包下的任意类的任意方法-->
        <aop:config>
                <aop:pointcut id="pointcut" expression="execution(* *..aop..impl.*.*(..))"/>
                <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
        </aop:config>

        <!--事务驱动-->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
                <tx:attributes>
                        <tx:method name="get*" read-only="true"/>
                        <tx:method name="find*" read-only="true"/>
                        <tx:method name="*" />
                </tx:attributes>
        </tx:advice>

        <!--配置transactionManager事务管理器-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                <property name="dataSource" ref="dataSource"/>
        </bean>
</beans>

数据库连接properties属性(其中连接的属性可根据实际情况进行变更)

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf8&autoReconnect=true
jdbc.username=root
jdbc.password=123456

3.6 编写运行测试方法

主程序类及方法如下所示:

/**
 * @author CodeGeekGao
 * @version Id: TestService.java, v 1.0 2020/7/11 11:29 AM CodeGeekGao
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:/aop/day5/*.xml"})
public class TestService {

    @Autowired
    private ApplicationContext applicationContext;

 @Test
    public void test() {
        BuyService buyService = applicationContext.getBean(BuyService.class);
        try {
            buyService.buy("小明","Java编程入门",10L);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行后可以发现账户余额扣了1000而库存数量减少了10,下单成功。接下来我们将减库存的sql语句中的t_storage 改为 t_storage1,这样就可以使减库存的方法抛异常并检验事务是否生效。
在这里插入图片描述
再次运行测试类方法果然抛出了异常信息如下:
在这里插入图片描述
检查账户余额以及库存都没有进行相应的变化,证明我们的事务已经生效。

4. 使用 @Transactional注解

上面使用的是基于XML的事务配置声明式事务方法之外,还可以使用基于注解的方法来配置事务,例如我们可以将上面service层的方法加上相应的注解如下所示:

  • BuyServiceImpl
    在这里插入图片描述
  • AccountServiceImpl
    在这里插入图片描述
  • StorageServiceImpl
    在这里插入图片描述
  • xml中启用如下注解驱动
    在这里插入图片描述
    这样基于注解进行事务配置与上面的xml配置切面然后事务行为效果是一致的。我们同样运行测试类的方法,在抛出异常后事务进行了回滚。

5. 事务细节探究

接下来让我们看一下 @Transactional这个注解的源码方法如下所示:
在这里插入图片描述
注意: 在Spring代理重要接口实现类中应将 @Transactional 注解标注在 public的方法上,如果在其他例如protected、private以及默认方法上,
下面我们将对其重要方法进行介绍,一般在开发中也会经常使用到它们。

⓵ timeout
我们可以为我们的代码执行单元方法设置事务超时时间(默认时间单位s),在实际那些对执行单元代码有着严格执行时间控制的场景下非常有用,下面我们修改 AccountServiceImplfindAccountBalance方法添加线程休眠时间的代码如下:
在这里插入图片描述
然后在下单接口实现上规定事务的超时时间如下:
在这里插入图片描述
在此运行测试方法不出意外将会报如下TransactionTimedOutException的错误:
在这里插入图片描述
⓶ noRollbackFor
这个通常是规定哪些异常不用进行回滚,一般在实际开发中程序会抛出各种业务上的异常,一般对哪些对主要核心逻辑不会有太大影响的异常将不进行回滚以保证业务的正常异常,例如我们在下单模拟一个程序逻辑即:
当下单成功后,使用kafka发送消息给物流模块以通知订单发货。在这个逻辑中出现异常,按照之前我们的程序逻辑会回滚,然后用户发现自己下单没成功,然后继续下单然后消息中间件又抛出异常程序再一次回滚。用户体验极差,所以在实际中出现这种异常我们不希望进行回滚,然后将相关失败的订单信息进行保存然后在轮询尝试再次发送消息直至成功。下面将演示 noRollbackFor的使用:

  • 自定义一个kafka消息异常
public class MessageException extends RuntimeException {

    public MessageException(String message) {
        super(message);
    }
}
  • 程序中调用kafka发送消息物流发货的逻辑
 public void sendMessage(String userName, String productName) {
        // TODO 伪代码模拟发送消息失败,实际程序逻辑省略.....
        throw new MessageException("kafka中间件发送消息错误");
    }

直接运行测试类不出所料,下单服务整个进行了回滚,我们修改下单方法如下所示:

在这里插入图片描述然后再次运行,余额与库存都进行了减少说明我们配置成功啦~~

⓷ rollbackFor
如果此属性默认不指定的话,默认回滚的是运行时异常(RunTimeException),如果设置了指定回滚异常类型那么事务只要在抛出指定的异常类型后会进行回滚,下面我们演示其使用:
在这里插入图片描述
这样当我们运行的时候会抛FileNotFoundException并进行了事务的回滚。
在这里插入图片描述
⓸ readOnly
可以设置事务为只读属性,一般适用于查询等不进行修改数据的程序单元上,设置了readOnly的属性不可以进行修改数据操作否则则会抛出异常,我们将代码修改如下所示:
在这里插入图片描述
运行测试方法抛出如下异常信息:

在这里插入图片描述
⓹ propagation
Spring中事务管理最重要就是事务的传播机制,下面对常用的三种事务传播属性 Propagation.REQUIRED、Propagation.REQUIRES_NEW、Propagation.NESTED 进行演示:

  • Propagation.REQUIRED

Propagation.REQUIRED 如果当前有事务就加入,如果当前没有事务则就开启一个新事务。例如我们在 BuyServiceImpl.buy方法上加上此属性如下所示:
在这里插入图片描述
然后在AccountServiceImpl的updateBalance方法上也加上此属性如下所示:在这里插入图片描述
当此方法执行的时候前由于BuyServiceImpl.buy 方法已经开启了事务,故此方法就不在开启新的事务而是使用当前存在的事务。这样在BuyServiceImpl.buy 或者AccountServiceImpl的updateBalance任何代码单元出现异常,事务都会进行回滚。

  • Propagation.REQUIRES_NEW

我们对AccountServiceImpl的updateBalance的事务传播属性修改如下所示:

然后修改StorageServiceImpl.decreaseStorage 方法如下所示:
在这里插入图片描述
我们定义了BuyServiceImpl.buy 方法事务传播属性为Propagation.REQUIRED,AccountServiceImpl的updateBalance的事务传播属性为 Propagation.REQUIRES_NEW,StorageServiceImpl.decreaseStorage事务传播属性为Propagation.REQUIRED。那么当执行到,AccountServiceImpl的updateBalance方法时将会开启一个新事务,而当前事务将被挂起直到AccountServiceImpl的updateBalance方法事务完成后才会进行继续执行,如果此时StorageServiceImpl.decreaseStorage方法失败进行回滚,已经事务结束的AccountServiceImpl的updateBalance方法是不会进行回滚的。

  • Propagation.NESTED

Propagation.NESTED 与上面Propagation.REQUIRES_NEW的区别是:后者将会新启用一个新的事务,而且这个事务与父事务相互独立,而前者的事务和父事务是相依的。即当父事务如果进行了回滚,那么前者事务也会进行回滚。但是如果前者事务回滚并不会导致父元素进行回滚。例如我们在StorageServiceImpl.decreaseStorage方法事务传播行为修改如下:
在这里插入图片描述
同时测试类修改代码如下所示:
在这里插入图片描述
然后运行后发现AccountServiceImpl的updateBalance方法的没有进行回滚,而StorageServiceImpl.decreaseStorage方法进行了回滚。

6. 可能遇到的事务问题

在上面遇到配置了切面以及事务驱动,但是事务并没有出现抛异常回滚(例如:当减库存方法失败抛出异常后,修改余额的方法并没有进行回滚依然进行了扣款操作)一般可分为以下几大部分:

⓵ 数据库引擎不对,如果你的mysql表引擎为 MyISAM 就不支持事务操作,将数据库引擎改为InnoDB后就可以支持事务操作了。首先我们查看以下当前数据库表引擎:

mysql> show variables like '%storage_engine%';
+----------------------------------+--------+
| Variable_name                    | Value  |
+----------------------------------+--------+
| default_storage_engine           | InnoDB |
| default_tmp_storage_engine       | InnoDB |
| disabled_storage_engines         |        |
| internal_tmp_disk_storage_engine | InnoDB |
+----------------------------------+--------+
4 rows in set (0.00 sec)

我们可以看到默认的存储引擎(default_storage_engine)为 InnoDB,这样你手动建表的默认引擎就是 InnoDB,如果是MyISAM,这样就需要将表的引擎进行修改,我们查看之前建立的表引擎如下:

show create table t_account;

查询效果如下所示:
在这里插入图片描述
如上图我们的表使用的引擎为InnoDB,若为MyISAM则需要将表引擎改为 *InnoDB 其操作如下:

alter table t_account ENGINE =InnoDB;

这样就可以将一张表引擎进行修改,但是这个有个弊端就是如果表默认引擎为MyISAM,每次新建立的表引擎都为 MyISAM ,每次都需要进行一次表的引擎修改非常麻烦。最方便就是直接一劳永逸将数据库默认引擎改为InnoDB即可。修改方式如下:

第一步编辑my.cnf文件位置

sudo vim /etc/my.cnf

第二步在配置文件my.cnf中的 [mysqld] 下面加入default-storage-engine=INNODB 保存如下所示:

第三步重启mysql,在Centos7系统下输入命令**systemctl restart mysqld**,在macos系统下找到 系统偏好设置最下角重启mysql即可。

⓶ Spring默认是只对运行时异常(RuntimeException 及其子类)进行回滚,若想对所有的异常进行回滚可在xml配置回滚为 Exception 如下所示:
在这里插入图片描述
⓷ 普通类无事务的方法调用了本类的事务方法,因为spring的事务实现原理为AOP,只有通过代理对象调用方法才能被拦截,事务才能生效。而类本身自身方法调用事务方法是通过 this 关键字进行调用的,这一点可使用Spring提供的AopUtils类的isAopProxy方法检查对象是否为Spring的代理对象,若不是Spring代理的对象调用的方法则事务不会生效。
在这里插入图片描述
⓸ 在业务层手工捕捉并处理了异常(try…catch)等于把异常“吃”掉了,这样Spring代理的对象调用方法就无法感知到异常从而使事务没有生效。最正确的方法就是在service层catch掉异常后,在thorw一个自定义异常然后在controller层进行捕获即可。
⓹ 被Spring代理对象调用的方法必须为 public 的,其他如protectedprivate 或者是默认的方法,事务都不会进行生效。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值