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.