Java for Web学习笔记(一三七)篇外之数据库的ACID和JPA(1)原子性

ACID大家都听过,看似也了解,但是在实际的项目中,发现不是所有人都正确理解。所以想谈一下当中容易忽略或者错误理解的地方。现在的开发语言和开发工具都很丰富,如果这要一一了解,也真是耗不起,但是有些是工具,知道怎么用就行,有些是基础知识,需要掌握。ACID就是基础知识,只有真正了解,才能在代码中进行合适的选择,特别是在异常处理和并发处理。

在这个小系列中,基于下面的表格

CREATE TABLE `test_acid`(
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `score` int(5) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SQL的事务

通过SQL语句演示一个事务的回滚

DELIMITER $$
CREATE PROCEDURE `mytest_acid_rb`()
BEGIN
    DECLARE `_rollback` INTEGER DEFAULT 0;
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET `_rollback` = 1;
    START TRANSACTION;
    SELECT @A:= score FROM test_acid WHERE id=1;
    UPDATE test_acid SET score=@A+1 WHERE id=1;
    UPDATE test_acid SET score=@A-30 WHERE id=1; -- 由于score是非负整数,这里可能会抛出异常
    IF `_rollback` THEN
        ROLLBACK;
    ELSE
        COMMIT;
    END IF;
    SELECT * FROM test_acid WHERE id=1; -- 查看结果
END$$
DELIMITER ;

使用Java代码

下面Java代码采用Spring Data,基于Hibernate。

(1)SQL语句出错的情况

@Transactional
public void acidTest(){
	TestAcidEntity entity1 = testAcidRepository.findOne(1L);
	TestAcidEntity entity2 = testAcidRepository.findOne(2L);
		
	entity1.setScore(entity1.getScore() + 1);
	testAcidRepository.save(entity1);

	// 出错,抛出一个org.hibernate.exception.DataException的异常,其继承了RuntimeException
	entity2.setScore(-1); 
	testAcidRepository.save(entity2);
}

我们跟踪执行的SQL,可以看mysql的log,也可以用抓包工具(将jdbc url中参数设置:useSSL=false)

SELECT @@session.tx_isolation -- response:READ-COMMITTED
-- SET autocommit=0可以视为是transaction的开始。没有跟踪到START TRANSACTION
SET autocommit=0
select testaciden0_.id as id1_13_0_, testaciden0_.score as score2_13_0_ from test_acid testaciden0_ where testaciden0_.id=1
select testaciden0_.id as id1_13_0_, testaciden0_.score as score2_13_0_ from test_acid testaciden0_ where testaciden0_.id=2
update test_acid set score=6 where id=1
-- 下一句将response:Error 1264 Out of range value for column 'score' at row 1
update test_acid set score=-1 where id=2 
rollback
SET autocommit=1

(2)非SQL语句的其他Java代码出错

对于Exception,分为checked exception,这类是需要在代码中明确进行异常捕获或抛出给调用者,在编译的时候进行检查;另一类是 RuntimeException ,在运行是出现,不需要在代码中显示进行异常捕获或者抛出给调用者。对于JPA的transaction而言,出现了RuntimeException会触发rollback;而对于需要抛出的异常Checked Exception,将在结束方法抛出异常之前进行commit,也就是说之前的写操作会发送出去并且执行。

提供一个测试小例子:

@Transactional
public void acidTest() throws Exception{
    TestAcidEntity entity1 = testAcidRepository.findOne(1L); 
    TestAcidEntity entity2 = testAcidRepository.findOne(2L); 


    entity1.setScore(6);
    testAcidRepository.save(entity1); 
    
    // 【另一个小测试】再读一次,并将结果打印出来。
    // 会发现实际上并没有真的发送SQL,当id相同的时候,JPA只读取一次;
    // 如果代码进行了修改(虽然没有实际发送),则为修改后的值。这是JPA内部的实现。
    TestAcidEntity entityCheck = testAcidRepository.findOne(1L); 
    log.info("score1 = {}",entityCheck.getScore()); //发现score为6
    
    doSomething();  //抛出Exception,这是checked exception,在方法声明中抛出
    // 抛出异常后面的代码就不执行了,那么在方法结束之前,将之前相关的写操作执行。
    // update test_acid set score=6 where id=1(签名的save)
    // commit

    entity2.setScore(2);
    testAcidRepository.save(entity2);
}

private void doSomething() throws Exception{
    throw new Exception("Just for test");
}

上面的小例子并没有出现回滚,执行了第一个update,不执行第二个update。在抛出异常时commit,后面的代码不执行。

我们再看看RunnableException的情况,这是会出发回滚的。

@Transactional
public void acidTest(){
   ...... 同上,只是不在方法声明中抛出异常
}

private void doSomething() {
    throw new RunnableException("Just for test");
}

相关链接:我的Professional Java for Web Applications相关文章

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值