原文翻譯自:https://medium.com 今天,我想談談 Spring 提供的@Transactional(readOnly = true)。 之所以聊這個是因為我公司項目的代碼里有很多@Transactional(readOnly = true),用過的同學都說@Transactional ...
原文翻譯自:https://medium.com
今天,我想談談 Spring 提供的@Transactional(readOnly = true)
。
之所以聊這個是因為我公司項目的代碼里有很多@Transactional(readOnly = true)
,用過的同學都說@Transactional(readOnly = true)
提高了性能。先思考以下幾點:
@Transactional(readOnly = true)
是如何工作的,為什麼使用它可以提高性能?- 當我們使用 JPA 時,是否應該總是將
@Transactional(readOnly = true)
添加到服務層的只讀方法?有什麼取捨嗎?
在開始之前,我們使用 Hibernate 來實現 JPA。
推薦一個開源免費的 Spring Boot 實戰項目:
1、@Transactional(readOnly = true)是如何工作的,為什麼使用它可以提高性能?
首先,讓我們看一下事務介面。
/**
* 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;
我們可以看到 readOnly = true
選項允許優化。事務管理器將使用只讀選項作為提示。讓我們看看用於事務管理器的JpaTransactionManager
。
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
// .
// Delegate to JpaDialect for actual transaction begin.
int timeoutToUse = determineTimeout(definition);
Object transactionData = getJpaDialect().beginTransaction(em,
new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder()));
//...
}
在JpaTransactionManager
中,doBegin方法委托JpaDialect來開始實際的事務,併在JpaDialect中調用beginTransaction
。讓我們來看看HibernateJpaDialect
類。
@Override
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
throws PersistenceException, SQLException, TransactionException {
// ...
// Adapt flush mode and store previous isolation level, if any.
FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly());
if (definition instanceof ResourceTransactionDefinition &&
((ResourceTransactionDefinition) definition).isLocalResource()) {
// As of 5.1, we explicitly optimize for a transaction-local EntityManager,
// aligned with native HibernateTransactionManager behavior.
previousFlushMode = null;
if (definition.isReadOnly()) {
session.setDefaultReadOnly(true);
}
}
// ...
}
protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws PersistenceException {
FlushMode flushMode = session.getHibernateFlushMode();
if (readOnly) {
// We should suppress flushing for a read-only transaction.
if (!flushMode.equals(FlushMode.MANUAL)) {
session.setHibernateFlushMode(Flusode.MANUAL);
return flushMode;
}
}
else {
// We need AUTO or COMMIT for a non-read-only transaction.
if (flushMode.lessThan(FlushMode.COMMIT)) {
session.setHibernateFlushMode(FlushMode.AUTO);
return flushMode;
}
}
// No FlushMode change needed...
return null;
}
在JpaDialect中,我們可以看到JpaDialect使用只讀選項準備刷新模式。當 readOnly = true
時, JpaDialect 禁止刷新。此外,您還可以看到,在準備刷新模式後,session.setDefaultReadOnly(true)
將session的readOnly屬性設置為true。
/**
* Change the default for entities and proxies loaded into this session
* from modifiable to read-only mode, or from modifiable to read-only mode.
*
* Read-only entities are not dirty-checked and snapshots of persistent
* state are not maintained. Read-only entities can be modified, but
* changes are not persisted.
*
* When a proxy is initialized, the loaded entity will have the same
* read-only/modifiable setting as the uninitialized
* proxy has, regardless of the session's current setting.
*
* To change the read-only/modifiable setting for a particular entity
* or proxy that is already in this session:
* @see Session#setReadOnly(Object,boolean)
*
* To override this session's read-only/modifiable setting for entities
* and proxies loaded by a Query:
* @see Query#setReadOnly(boolean)
*
* @param readOnly true, the default for loaded entities/proxies is read-only;
* false, the default for loaded entities/proxies is modifiable
*/
void setDefaultReadOnly(boolean readOnly);
在Session介面中,通過將readOnly屬性設置為true,將不會對只讀實體進行臟檢查,也不會維護持久狀態的快照。此外,只讀實體的更改也不會持久化。
總而言之,這些是在 Hibernate 中使用@Transactional(readOnly = true)
所得到的結果
- 性能改進:只讀實體不進行臟檢查
- 節省記憶體:不維護持久狀態的快照
- 數據一致性:只讀實體的更改不會持久化
- 當我們使用主從或讀寫副本集(或集群)時,
@Transactional(readOnly = true)
使我們能夠連接到只讀資料庫
2、當我們使用 JPA 時,是否應該總是將@Transactional(readOnly = true)添加到服務層的只讀方法?有什麼取捨嗎?
我看到,當使用@Transactional(readOnly = true)
時,我們可以有很多優勢。但是,將@Transactional(readOnly = true)
添加到服務層的只讀方法是否合適?以下是我擔心的事情
- 無限制地使用事務可能會導致資料庫死鎖、性能和吞吐量下降。
- 由於一個事務占用一個DB連接,所以
@Transactional(readOnly = true)
添加到Service層的方法可能會導致DB連接饑餓。
推薦一個開源免費的 Spring Boot 實戰項目:
第一個問題很難重現,所以我做了一些測試來檢查第二個問題。
@Transactional(readOnly = true)
public List<UserDto> transactionalReadOnlyOnService(){
List<UserDto> userDtos = userRepository.findAll().stream()
.map(userMapper::toDto)
.toList();
timeSleepAndPrintConnection();
return userDtos;
}
public List<UserDto> transactionalReadOnlyOnRepository(){
List<UserDto> userDtos = userRepository.findAll().stream()
.map(userMapper::toDto)
.toList();
timeSleepAndPrintConnection();
return userDtos;
}
我在服務層測試了兩個方法,一個是@Transactional(readOnly = true)
,另一個是存儲庫層中的@Transactional (readOnly = true)
(在 SimpleJpaRepository
中,它是 Jpa Respitory 的預設實現,在類的頂部有@Transformational(ready Only)
,因此 findAll()
方法在預設情況下有@transactional(read only = True)
)。
我從DB中獲取userInfo並保持線程5秒鐘,然後檢查該方法何時釋放連接。
結果如下:
對於服務層方法中的@Transactional(readOnly = true)
,
activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnService!!
Hibernate:
select
u1_0.id,
u1_0.email,
u1_0.name,
u1_0.profile_file_name
from
users u1_0
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
end transactionalReadOnlyOnService!!
activeConnections:0, IdleConnections:10, TotalConnections:10
對於存儲庫層方法中的@Transactional(readOnly = true)
,
activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnRepository!!
Hibernate:
select
u1_0.id,
u1_0.email,
u1_0.name,
u1_0.profile_file_name
from
users u1_0
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10, TotalConnections:10
end transactionalReadOnlyOnRepository!!
activeConnections:0, IdleConnections:10, TotalConnections:10
正如您所看到的,@Transactional(readOnly = true)
一旦查詢結果到達,存儲庫層就會釋放連接。
然而,@Transactional(readOnly = true)
在服務層的方法中直到服務層的方法結束才釋放連接。
因此,當服務層的方法有需要大量時間的邏輯時要小心,因為它可以長時間持有資料庫連接,這可能會導致資料庫連接匱乏。
3、回顧
很明顯,@Transactional(readOnly = true)
有很多優點。
- 性能改進:只讀實體不進行臟檢查
- 節省記憶體:不維護持久狀態的快照
- 數據一致性:只讀實體的更改不會持久化
- 當我們使用主從或讀寫副本集(或集群)時,
@Transactional(readOnly = true)
使我們能夠連接到只讀資料庫
但是,您還應該記住,@Transactional(readOnly = true)
在服務層的方法中可能會導致資料庫死鎖、性能低下和資料庫連接匱乏!
當您需要將只讀查詢僅僅作為一個事務執行時,請毫不猶豫選擇的在服務層的方法中使用@Transactional(readOnly = true)
,如果你的服務層的方法中有大量其他邏輯方法時,就要做取捨了!
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
覺得不錯,別忘了隨手點贊+轉發哦!