Spring Boot之@Transactional无法生效的问题分析

引言

在Spring Boot中通过在pom.xml文件中的dependency来引入data-jpa的完整依赖,实现dao层的快速实现。
数据库类型: MySQL
应用框架: Spring Boot 2.1.4.RELEASE
JDK: 8

依赖引入

基于data-jpa方式的依赖引入如下:

       
			org.springframework.boot
			spring-boot-starter-data-jpa
		

问题提出

在代码中使用了事务进行管理,但是在异常发生之时,异常发生点之前的数据库操作还是被写入到了数据库,换句话说,数据操作的事务管理并未正确生效。
核心代码如下:

import java.util.Date;
import java.util.List;

import org.bistu.course.dao.entity.ProductEntity;
import org.bistu.course.dao.entity.SaleRecord;
import org.bistu.course.dao.repository.ProductRepository;
import org.bistu.course.dao.repository.SaleRepository;
import org.bistu.course.helper.ProductType;
import org.bistu.course.helper.SaleType;
import org.bistu.course.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class ProductServiceImpl implements ProductService {
    @Autowired
    private SaleRepository saleRepo;
    
    @Autowired
    private ProductRepository productRepo;
    
    @Transactional
	@Override
	public List testTransaction() {
		ProductEntity entity = new ProductEntity();
		entity.setName("Product12");
		entity.setCountNum(12);
		entity.setCreatedTime(new Date());
		entity.setUpdatedTime(new Date());
		entity.setProdcutType(ProductType.Device);
		entity.setProductTypeDesc(ProductType.Device);
 		
		entity = this.productRepo.save(entity);
		
		log.info("Save Product:{}", entity);
		
		if (2 == 2)
   		   throw new RuntimeException("whatever it is");
		
		SaleRecord saleRecord = new SaleRecord();
		saleRecord.setCost(123.0f);
		saleRecord.setCount(123);
		saleRecord.setProductId(112l);
		saleRecord.setCreatedTime(new Date());
		saleRecord.setSaleType(SaleType.Partner);
		saleRecord.setUserId(12l);
		
		this.saleRepo.save(saleRecord);
		log.info("Save SaleRecord:{}", saleRecord);
		
		List products = this.productRepo.findAll();
		
		return products;
	}
}

在代码中进行了两个写入操作,在第一个写入操作之后,跑出异常,第二个写入操作,将无法实现写入操作。通过事务管理的方式实现回滚操作。
但是在实际执行中,却是Product的数据被写入了数据库。

@Transactional使用

在使用@Transactional之时一定要注意,其中存在两个这样的注解:

  • javax.persistence.Transactional
    其中定义了value, dontRollbackOn, rollbackOn三个属性信息

  • org.springframework.transaction.annotation.Transactional
    其中定义了多个属性信息: readOnly,timeout,rollbackFor,noRollbackForClassName,
    noRollbackFor,rollbackForClassName。
    而在代码中实际使用的是Spring Framework提供的@Transactional的实现,大家需要格外注意这个事项。
    关于Spring框架提供的@Transactional的使用,大家可以参阅笔者之前的文章。

  1. Spring中@Transactional用法深度分析之一
  2. Spring中的@Transactional深度分析之二

InnoDB vs MyISAM

MyISAM:默认表类型,它是基于传统的ISAM类型,ISAM是Indexed Sequential Access Method (有索引的顺序访问方法) 的缩写,它是存储记录和文件的标准方法.不是事务安全的,而且不支持外键,如果执行大量的select,insert MyISAM比较适合。

InnoDB:支持事务安全的引擎,支持外键、行锁、事务是他的最大特点。Innodb最初是由innobase Oy公司开发,2006年5月由oracle公司并购,目前innodb采用双授权,一个是GPL授权,一个是商业授权。如果有大量的update和insert,建议使用InnoDB,特别是针对多个并发和QPS较高的情况。

myisam只支持表级锁,用户在操作myisam表时,select,update,delete,insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。也可以通过lock table命令来锁表,这样操作主要是可以模仿事务,但是消耗非常大,一般只在实验演示中使用。
MyISAM不是事务安全的,而且也不支持外键。如果事物回滚将造成不完全回滚,不具有原子性。

InnoDB:Innodb支持事务和行级锁,是innodb的最大特色。事务的ACID属性:atomicity, consistent, isolation, duration.

Spring Boot中的设置

在application.properties中的设置:

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

此时默认使用的MySQL存储引擎为MyISAM.
根据上述说明,其中并不支持事务操作,所以其是无法实现事务管理的。

如何来设置InnoDB?
在application.properties

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

如何来判定当前表的存储类型?

mysql -u root -p
use database_name;
show create table table_name;

MyISAM的表创建语句信息:
Spring Boot之@Transactional无法生效的问题分析_第1张图片
InnoDB的表创建语句信息:
Spring Boot之@Transactional无法生效的问题分析_第2张图片

问题解决

通过将数据库表的存储引擎更新为InnoDB,让其支持事务,实现了数据访问层的回滚操作。

总结

在验证过程中一共碰到了两个问题:

  1. @Transactional使用了错误的类,
  2. 数据库默认的存储引擎MyISAM,不支持事务。

你可能感兴趣的:(Java技术,问题分析,服务化与Spring,Cloud)