h2에서 @Transactional(readOnly=true) 가 안먹히는 이유 - Why @Transactional(readOnly=true) is not working in H2? (problem solved)
Table of Contents
1 레퍼런스
2 문서
일단 Transactional 문서를 보자. 그 안에 readOnly 를 설명한 글이 있다.
// Transactional#readOnly public abstract boolean readOnly A boolean flag that can be set to true if the transaction is effectively read-only, allowing for corresponding optimizations at runtime. Defaults to false. This just serves as a hint for the actual transaction subsystem; it will not necessarily cause failure of write access attempts. A transaction manager which cannot interpret the read-only hint will not throw an exception when asked for a read-only transaction but rather silently ignore the hint. See Also: TransactionDefinition.isReadOnly(), TransactionSynchronizationManager.isCurrentTransactionReadOnly() Default: false
it will not necessarily cause failure of write access attempts. readOnly라고 쓰여있다고 무조건 write를 막는 것은 아니다.
See Also: 에 있는 TransactionDefinition.isReadOnly() 을 이용해서 트랜잭션이 수행될 때, 이 메서드가 수행될 거라고 예측된다. (아직 모른다)
도큐먼트 설명을 보자.
default boolean isReadOnly() Return whether to optimize as a read-only transaction. The read-only flag applies to any transaction context, whether backed by an actual resource transaction (PROPAGATION_REQUIRED/ PROPAGATION_REQUIRES_NEW) or operating non-transactionally at the resource level (PROPAGATION_SUPPORTS). In the latter case, the flag will only apply to managed resources within the application, such as a Hibernate Session. This just serves as a hint for the actual transaction subsystem; it will not necessarily cause failure of write access attempts. A transaction manager which cannot interpret the read-only hint will not throw an exception when asked for a read-only transaction. Returns: true if the transaction is to be optimized as read-only (false by default) See Also: TransactionSynchronization.beforeCommit(boolean), TransactionSynchronizationManager.isCurrentTransactionReadOnly()
이곳에도 힌트만 준다고 말한다. 트랜잭션 매니저가 read-only 힌트를 사용하지 않을 수 있다.
3 코드
3.1 TransactionDefinition#isReadOnly
인터페이스며 isReadOnly 는 기본적으로 false 이다.
public interface TransactionDefinition {
// ...
default boolean isReadOnly() {
return false;
}
// ...
}
구현체가 있을 것이다. 찾아보니 구현체가 두개 보였다. 그중 DefaultTransactionDefinition 이것이 기본적으로 실행될 것처럼 보였다.
public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
// ...
@Override
public final boolean isReadOnly() {
return this.readOnly;
}
}
3.2 HibernateJpaDialect#beginTransaction
당연히 트랜잭션이 수행될 때, isReadOnly() 를 사용할 것이다.
package org.springframework.orm.jpa.vendor;
public class HibernateJpaDialect extends DefaultJpaDialect {
// ...
@Override
public Object beginTransaction(EntityManager entityManger, TransactionDefinition definition)
throws PersistenceException, SQLException, TransactionException {
// ...
if (isolationLevelNeeded || definition.isReadOnly()) {
if(this.prepareConnection) {
preparedCon = HibernateConnectionHandle.doGetConnection(session);
previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(preparedCon, definition);
}
}
// ...
}
}
definition을 받은 DataSourceUtils 는 무엇을 하는 걸까?
package org.springframework.jdbc.datasource;
class DataSourceUtils {
@Nullable
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition) {
// Set read-only flag
if (definition != null && definition.isReadOnly()) {
con.setReadOnly(true);
}
}
}
con.setReadOnly(true) 이것은 해당 커넥션에 readOnly 플래그를 주는 것 같다. H2를 사용하고 있다면 이 커넥션은 H2구현체일 것이다.
3.3 org.h2.jdbc.JdbcConnection.java
package org.h2.jdbc;
/**
* <p>
* Represents a connection (session) to a database.
* </p>
* <p>
* Thread safety: the connection is thread-safe, because access is synchronized.
* However, for compatibility with other databases, a connection should only be
* used in one thread at any time.
* </p>
*/
public class JdbcConnection extends TraceObject implements Connection, JdbcConnectionBackwardsCompat,
CastDataProvider {
// ...
/**
* According to the JDBC specs, this setting is only a hint to the database
* to enable optimizations - it does not cause writes to be prohibited.
*
* @param readOnly ignored
* @throws SQLException if the connection is closed
*/
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
try {
if (isDebugEnabled()) {
debugCode("setReadOnly(" + readOnly + ");");
}
checkClosed();
} catch (Exception e) {
throw logAndConvert(e);
}
}
}
여기 @param readOnly ignored 가 보인다.
4 결론
Transactional(readOnly=true)는java.sql.Connection#setReadOnly메소드에true인자로 호출한다.- 문서에 따르면
Connection구현체들은 이것을 꼭 사용할 의무가 없다. - H2의
JdbcConnection은setReadOnly를 무시하는 것으로 구현하였다.