Why does JpaTransactionManager reset the read-only flag on commit?

1.9k views Asked by At

I'm trying to set up a simple Spring Boot project with a JPA repository on top of a read-only data source, and I would like to propagate the read-only flag as hint to the underlying JDBC driver for performance optimizations, as per Spring Data JPA Reference.

The repository implementation looks as follows:

public interface MyReadOnlyRepository extends JpaRepository<String, String> {}

This is the configuration class:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
  entityManagerFactoryRef = "readOnlyEntityManagerFactory",
  transactionManagerRef = "readOnlyTransactionManager",
  basePackageClasses = ReadOnlyDbConfig.class)
public class ReadOnlyDbConfig {

  @Bean(name = "readOnlyDataSource")
  @ConfigurationProperties(prefix = "spring.datasource.ro")
  public DataSource dataSource() {

    return DataSourceBuilder.create().build();
  }

  @Bean(name = "readOnlyEntityManagerFactory")
  public LocalContainerEntityManagerFactoryBean entityManagerFactory(
    EntityManagerFactoryBuilder builder,
    @Qualifier("readOnlyDataSource") DataSource dataSource) {

    return builder
            .dataSource(dataSource)
            .packages(MyEntity.class)
            .persistenceUnit("readOnly")
            .build();
  }

  @Bean(name = "readOnlyTransactionManager")
  public PlatformTransactionManager transactionManager(
        @Qualifier("readOnlyEntityManagerFactory") EntityManagerFactory entityManagerFactory) {

    return new JpaTransactionManager(entityManagerFactory);
  }

}

and the relevant YML properties:

spring:
  datasource:
    ro:
      url: jdbc:postgresql://...
      username: ...
      password: ...
      driver-class-name: org.postgresql.Driver
      default-read-only: true

To test it, I have written a simple test which runs against a dockerised PostgresSQL instance:

@SpringBootTest(classes = TestApplication.class)
@RunWith(SpringRunner.class)
@ContextConfiguration(initializers = { PostgresInDockerInitializer.class })
public class MyReadOnlyStoreTest {

  @Inject
  private MyReadOnlyRepository repo;

  @Test(expected = RuntimeException.class)
  public void testIsReadOnly() {

    repo.save("test");
  }

}

I have called the save just for the sake of testing whether the flag is set: I appreciate the read-only flag is only meant as a hint for the JDBC driver, not as a protection mechanism against write operations. The test throws an exception, as expected ("cannot execute INSERT in a read-only transaction").

My problem is that, as soon as a transaction completes successfully, the JPA transaction manager resets the connection read-only flag to false. So, for example, this test fails:

@Test(expected = RuntimeException.class)
public void testIsReadOnly() {

  repo.findAll(); // <-- on commit, the transaction manager resets the read-only flag to false
  repo.save("test"); 
}

Note that the same happens if the repository is annotated with @Transactional(readOnly=true) as per the Spring Data JPA Reference.

Could somebody please explain why the transaction manager does so? Is there an easy/better way to avoid the flag being reset, other than setting the transaction status to rollback-only?

Thanks for your help.

0

There are 0 answers