我們可以使用 @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)象

進(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)化)

再次進(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());
}

使用@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í)