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
를 무시하는 것으로 구현하였다.