spring 事務(wù)管理之只讀事務(wù)@Transactional(readOnly = true)

我們可以使用 @Transactional(readOnly = true) 來設(shè)置只讀事務(wù)

在將事務(wù)設(shè)置成只讀后,當(dāng)前只讀事務(wù)就不能進(jìn)行寫的操作,否則報(bào)錯(cuò)。如下

Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed;

需不需要在只有查詢的方法上加上@Transactional注解?

需要分為兩種情況來看

  • 若一個(gè)事務(wù)里只發(fā)出一條select語句,則沒有必要啟用事務(wù)支持,數(shù)據(jù)庫默認(rèn)支持sql執(zhí)行期間的讀一致性。此時(shí)可以不加@Transactional注解

  • 若一個(gè)事務(wù)里先后發(fā)出了多條select語句。 @Transactional注解表明被申明方法是一個(gè)整體事務(wù)。在mysql的RR隔離級(jí)別下,普通的無鎖select是鏡像讀,多次查詢結(jié)果不會(huì)改變,所以能保證讀一致性(可重復(fù)讀);相反若不加 @Transactional注解,則多條select都是獨(dú)立的事務(wù)在前條select之后,后條select之前,數(shù)據(jù)被其他事務(wù)改變,則該次整體的查詢將會(huì)出現(xiàn)讀數(shù)據(jù)不一致的現(xiàn)象。此時(shí)需要添加@Transactional注解

需不需要在只有查詢的方法上使用@Transactional(readOnly = true)申明為一個(gè)只讀事務(wù)?

需要,因?yàn)榇嬖?code>可能的優(yōu)化

我們可以看一下@Transactional注解的源碼,其中的readOnly 屬性的解釋

    /**
     * A boolean flag that can be set to {@code true} if the transaction is
     * effectively read-only, allowing for corresponding optimizations at runtime.
     * <p>Defaults to {@code false}.
     * <p>This just serves as a hint for the actual transaction subsystem;
     * it will <i>not necessarily</i> cause failure of write access attempts.
     * A transaction manager which cannot interpret the read-only hint will
     * <i>not</i> throw an exception when asked for a read-only transaction
     * but rather silently ignore the hint.
     * @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
     * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
     */
    boolean readOnly() default false;

這段文字的說明如下:

  • 一個(gè)布爾標(biāo)志,如果事務(wù)設(shè)置為只讀,允許在運(yùn)行時(shí)進(jìn)行相應(yīng)的優(yōu)化。默認(rèn)為false;
  • 這只是對(duì)實(shí)際事務(wù)子系統(tǒng)的提示,它不一定會(huì)導(dǎo)致寫入訪問嘗試失敗。無法解析只讀提示的事務(wù)管理器也不會(huì)引發(fā)異常而是默默地忽略這個(gè)屬性。

對(duì)讀一致性的驗(yàn)證

如下單元測(cè)試代碼中testSelect()方法先后查詢2次(沒有加 @Transactional注解),testInsert()方法在2次查詢前插入數(shù)據(jù)

package com.springboot.study.demo1;
import com.springboot.study.demo1.entity.User;
import com.springboot.study.demo1.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 *@program: springboot_study
 *@description:
 *@author: yinkai
 *@create: 2020-02-29 12:03
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@Rollback(value=false)//事務(wù)提交而不是回滾
public class TestTran {


    @Resource
    private UserMapper mapper;


    @Test
    public void testSelect() throws InterruptedException {

        List<User> list1 = mapper.selectList(null);
        System.out.println(list1.size());
        //睡眠5秒給insert事務(wù)提供時(shí)間
        System.out.println("=================睡眠20s==============");
        TimeUnit.SECONDS.sleep(20);
        //再次查詢
        List<User> list2 = mapper.selectList(null);
        System.out.println(list2.size());

    }

    @Test
    @Rollback(value=false)//事務(wù)提交而不是回滾
    @Transactional
    public void testInsert(){
        User user = new User(11L,"YINKAI",25,"YINKAI@ALIYUN.COM",213);
        mapper.insert(user);
    }


}

運(yùn)行測(cè)試方法testSelect(),該方法在第一次查詢之后睡眠20秒(查詢記錄為18條)。然后執(zhí)行testInsert()方法,插入了一條數(shù)據(jù)后testSelect()睡眠時(shí)間到繼續(xù)執(zhí)行,再次查詢記錄為19條。兩次查詢結(jié)果不一致,這也是一種幻讀現(xiàn)象

image.png

進(jìn)行對(duì)比試驗(yàn),在testSelect()方法上加上@Transactional(readOnly = true)注解


    @Test
    @Transactional(readOnly = true)
    public void testSelect() throws InterruptedException {

        List<User> list1 = mapper.selectList(null);
        System.out.println(list1.size());
        //睡眠5秒給insert事務(wù)提供時(shí)間
        System.out.println("=================睡眠20s==============");
        TimeUnit.SECONDS.sleep(20);
        //再次查詢
        List<User> list2 = mapper.selectList(null);
        System.out.println(list2.size());

    }

再次運(yùn)行testSelect()和testInsert()方法,發(fā)現(xiàn)兩次結(jié)果相同。而且只發(fā)出了一條sql語句(這個(gè)優(yōu)化并不是readOnly = true所帶來的,事實(shí)上只要加上@Transactional注解就會(huì)有這種優(yōu)化)

image.png

再次進(jìn)行對(duì)比試驗(yàn),在testSelect()方法上加上@Transactional注解(不包含readOnly 屬性)

    @Test
    @Transactional
    public void testSelect() throws InterruptedException {
        List<User> list1 = mapper.selectList(null);
        System.out.println(list1.size());
        //睡眠5秒給insert事務(wù)提供時(shí)間
        System.out.println("=================睡眠20s==============");
        TimeUnit.SECONDS.sleep(20);
        //再次查詢
        List<User> list2 = mapper.selectList(null);
        System.out.println(list2.size());
    }
image.png

使用@Transactional注解不加readOnly=true 也只是發(fā)出一條sql,這就是一種優(yōu)化。但是此優(yōu)化并非readOnly=true所帶來的;兩次查詢結(jié)果相同,說明單單使用@Transactional注解就能夠帶來 讀一致性(可重復(fù)讀)

大總結(jié)

在開發(fā)中我們需要在只發(fā)出查詢語句的方法上添加@Transactional(readOnly = true)注解,將之申明為只讀事務(wù)。

  • 多條查詢下要使用該注解,能夠防止多次查詢到的數(shù)據(jù)不一致(維持可重復(fù)讀)。而且有一定的優(yōu)化,比如上面的兩次的查詢只發(fā)出一條sql;
  • 盡管在單條查詢下不會(huì)出現(xiàn)數(shù)據(jù)不一致現(xiàn)象,但是使用@Transactional(readOnly = true)注解能夠優(yōu)化查詢,源碼中提到readOnly = true也存在著可能的優(yōu)化

加上@Transactional(readOnly = true)可以保證讀一致性查詢優(yōu)化以及一些可能的優(yōu)化,即使數(shù)據(jù)庫和驅(qū)動(dòng)底層不支持readOnly 屬性,那也不會(huì)報(bào)錯(cuò)。我們何樂而不為呢?

如何證明readOnly = true帶來了優(yōu)化?

事實(shí)上readOnly 和spring沒有任何關(guān)系,spring事務(wù)僅僅是一層封裝,最后都要調(diào)用底層驅(qū)動(dòng)的setReadonly方法來開啟。所以readOnly 是否能到來優(yōu)化還是要看 數(shù)據(jù)庫類型和驅(qū)動(dòng)類型;所以readonly語義不是所有的數(shù)據(jù)庫驅(qū)動(dòng)都支持的

比如Oracle對(duì)于只讀事務(wù),不啟動(dòng)回滾段,不記錄回滾log。又比如在spring+hibernate的條件下,readonly有一些優(yōu)化

spring doc 有如下描述

Read-only status: A read-only transaction can be used when your code
reads but does not modify data. Read-only transactions can be a useful
optimization in some cases, such as when you are using Hibernate.

這句話解釋如下:
只讀狀態(tài):當(dāng)代碼讀取但不修改數(shù)據(jù)時(shí),可以使用只讀事務(wù)。在某些情況下,只讀事務(wù)可能是一種有用的優(yōu)化,例如當(dāng)您使用hibernate時(shí)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容