英文:
InvalidIsolationLevelException: HibernateJpaDialect Error encountered for read write datasource routing Hibernate
问题
我正在实现一个读写数据源路由,使用Vlad Mihlcea的帖子中的方法。
以下是我的配置类:
@Configuration
@EnableTransactionManagement
@EnableAspectJAutoProxy
@EnableJpaRepositories(
value = "com.api.repositories",
repositoryBaseClass = BaseJpaRepositoryImpl.class
)
public class DBConfig {
@Autowired
private DbProps dbProps;
@Bean
public DataSource readWriteDataSource() {
LOGGER.info("DB Props: {}", dbProps.toString());
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setUrl(dbProps.getDbUrl());
dataSource.setUser(dbProps.getDbUser());
dataSource.setPassword(dbProps.getDbPassword());
dataSource.setLogUnclosedConnections(true);
return connectionPoolDataSource(dataSource);
}
@Bean
public DataSource readOnlyDataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setUrl(dbProps.getReplicaDbUrl());
dataSource.setUser(dbProps.getDbUser());
dataSource.setPassword(dbProps.getDbPassword());
dataSource.setLogUnclosedConnections(true);
return connectionPoolDataSource(dataSource);
}
@Bean
@Primary
public TransactionRoutingDataSource actualDataSource() {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.READ_WRITE, readWriteDataSource());
dataSourceMap.put(DataSourceType.READ_ONLY, readOnlyDataSource());
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(readOnlyDataSource());
return routingDataSource;
}
// 其余部分未翻译
}
我在手动更改隔离级别时遇到问题,例如:
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class)
我收到以下异常:
org.springframework.transaction.InvalidIsolationLevelException: HibernateJpaDialect is not allowed to support custom isolation levels: make sure that its 'prepareConnection' flag is on (the default) and that the Hibernate connection release mode is set to ON_CLOSE
当我按照错误消息中的建议将jpaDialect.setPrepareConnection
属性保留为默认值时,它可以正常工作,但我会失去路由功能,所有查询,包括读和写操作,都将发送到主实例。如何在仍能够路由查询的情况下启用更改隔离级别?
英文:
I am implementing a read write datasource routing using Vlad Mihlceas's post
Below is my config class
@Configuration
@EnableTransactionManagement
@EnableAspectJAutoProxy
@EnableJpaRepositories(
value = "com.api.repositories",repositoryBaseClass = BaseJpaRepositoryImpl.class
)
public class DBConfig {
@Autowired
private DbProps dbProps;
@Bean
public DataSource readWriteDataSource() {
LOGGER.info("DB Props: {}", dbProps.toString());
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setUrl(dbProps.getDbUrl());
dataSource.setUser(dbProps.getDbUser());
dataSource.setPassword(dbProps.getDbPassword());
dataSource.setLogUnclosedConnections(true);
return connectionPoolDataSource(dataSource);
}
@Bean
public DataSource readOnlyDataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setUrl(dbProps.getReplicaDbUrl());
dataSource.setUser(dbProps.getDbUser());
dataSource.setPassword(dbProps.getDbPassword());
dataSource.setLogUnclosedConnections(true);
return connectionPoolDataSource(dataSource);
}
@Bean
@Primary
public TransactionRoutingDataSource actualDataSource() {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.READ_WRITE, readWriteDataSource());
dataSourceMap.put(DataSourceType.READ_ONLY, readOnlyDataSource());
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(readOnlyDataSource());
return routingDataSource;
}
protected HikariConfig hikariConfig(DataSource dataSource) {
HikariConfig hikariConfig = new HikariConfig();
int cpuCores = Runtime.getRuntime().availableProcessors();
hikariConfig.setMaximumPoolSize(cpuCores * 4);
hikariConfig.setDataSource(dataSource);
hikariConfig.setIdleTimeout(600000);
hikariConfig.setLeakDetectionThreshold(30000);
hikariConfig.setConnectionTimeout(30000);
hikariConfig.setMaxLifetime(1800000);
hikariConfig.setAutoCommit(false);
return hikariConfig;
}
protected HikariDataSource connectionPoolDataSource(DataSource actualDataSource) {
return new HikariDataSource(hikariConfig(actualDataSource));
}
private DataSource dataSource() {
return ProxyDataSourceBuilder
.create(actualDataSource())
.name("Proxy-datasource")
.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPersistenceUnitName(getClass().getSimpleName());
entityManagerFactoryBean.setPersistenceProvider(new HibernatePersistenceProvider());
entityManagerFactoryBean.setDataSource(dataSource());
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
HibernateJpaDialect jpaDialect = vendorAdapter.getJpaDialect();
jpaDialect.setPrepareConnection(false);
entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
entityManagerFactoryBean.setJpaProperties(additionalProperties());
entityManagerFactoryBean.setPackagesToScan("com.api.models");
return entityManagerFactoryBean;
}
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
@Bean
public TransactionTemplate transactionTemplate(EntityManagerFactory entityManagerFactory) {
return new TransactionTemplate(transactionManager(entityManagerFactory));
}
protected Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
//properties.setProperty("hibernate.connection.release_mode", "after_transaction");
properties.setProperty("hibernate.connection.provider_disables_autocommit",Boolean.TRUE.toString());
return properties;
}
}
I'm having issues doing writes where I manually change Isolation level e.g
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class)
I get the following exception
org.springframework.transaction.InvalidIsolationLevelException: HibernateJpaDialect is not allowed to support custom isolation levels: make sure that its 'prepareConnection' flag is on (the default) and that the Hibernate connection release mode is set to ON_CLOSE
when I leave the jpaDialect.setPrepareConnection property as default as advised in the error message it works but I lose the routing functionality and all queries both reads and writes are sent to the primary instance. How do enable change of isolation level while still being able to route the the queries?
答案1
得分: 0
我建议不要通过确定事务是否只读来执行数据库路由的想法 - 这在某些博客文章中看起来很吸引人,但在许多边缘情况下,它可能不会按预期工作。
顺便说一下,我认为你可以通过实现自己的PlatformTransactionManager
来解决你的问题,类似于:
public class JpaTransactionManagerExposingReadOnlyStatus extends JpaTransactionManager {
@Override
protected Object doSuspend(Object transaction) {
TxReadOnlyStatus.begin(null);
boolean success = false;
try {
Object result = super.doSuspend(transaction);
success = true;
return result;
} finally {
if (!success) {
TxReadOnlyStatus.complete();
}
}
}
@Override
protected void doResume(Object transaction, Object suspendedResources) {
try {
super.doResume(transaction, suspendedResources);
} finally {
TxReadOnlyStatus.complete();
}
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
TxReadOnlyStatus.begin(definition.isReadOnly());
boolean success = false;
try {
super.doBegin(transaction, definition);
success = true;
} finally {
if (!success) {
TxReadOnlyStatus.complete();
}
}
}
@Override
protected void doCleanupAfterCompletion(Object transaction) {
try {
super.doCleanupAfterCompletion(transaction);
} finally {
TxReadOnlyStatus.complete();
}
}
}
public class TxReadOnlyStatus {
private static final ThreadLocal<Deque<Boolean>> txStack = ThreadLocal.withInitial(LinkedList::new);
public static boolean isReadOnly() {
Boolean readOnly = getTxStack().peekLast();
if (readOnly != null) {
return readOnly;
}
if (isSynchronizationActive()) {
// active synchronization
return isCurrentTransactionReadOnly();
}
return true;
}
private static Deque<Boolean> getTxStack() {
return txStack.get();
}
static void begin(Boolean readOnly) {
getTxStack().addLast(readOnly);
}
static void complete() {
getTxStack().removeLast();
}
}
英文:
I would suggest to stay clear of the idea to perform DB routing via determining whether tx is readonly or not - it looks attractive in someone's blogpost, however there are to many edge cases when it might not work as expected.
By the way, I do believe you may overcome your issue via implementing your own PlatformTransactionManager
, something like:
public class JpaTransactionManagerExposingReadOnlyStatus extends JpaTransactionManager {
@Override
protected Object doSuspend(Object transaction) {
TxReadOnlyStatus.begin(null);
boolean success = false;
try {
Object result = super.doSuspend(transaction);
success = true;
return result;
} finally {
if (!success) {
TxReadOnlyStatus.complete();
}
}
}
@Override
protected void doResume(Object transaction, Object suspendedResources) {
try {
super.doResume(transaction, suspendedResources);
} finally {
TxReadOnlyStatus.complete();
}
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
TxReadOnlyStatus.begin(definition.isReadOnly());
boolean success = false;
try {
super.doBegin(transaction, definition);
success = true;
} finally {
if (!success) {
TxReadOnlyStatus.complete();
}
}
}
@Override
protected void doCleanupAfterCompletion(Object transaction) {
try {
super.doCleanupAfterCompletion(transaction);
} finally {
TxReadOnlyStatus.complete();
}
}
}
public class TxReadOnlyStatus {
private static final ThreadLocal<Deque<Boolean>> txStack = ThreadLocal.withInitial(LinkedList::new);
public static boolean isReadOnly() {
Boolean readOnly = getTxStack().peekLast();
if (readOnly != null) {
return readOnly;
}
if (isSynchronizationActive()) {
// active synchronization
return isCurrentTransactionReadOnly();
}
return true;
}
private static Deque<Boolean> getTxStack() {
return txStack.get();
}
static void begin(Boolean readOnly) {
getTxStack().addLast(readOnly);
}
static void complete() {
getTxStack().removeLast();
}
}
答案2
得分: 0
我已经根据一些研究和考虑到安德烈的答案,通过使用PGPool2路由我的流量来解决了我的问题。然而,建议在Spring Boot中禁用连接池并使用PGPool中的连接池。
这个解决方案也很好,因为其他使用不同编程语言编写的应用程序也可以受益于路由。
英文:
After a bit of research and also taking into consideration Andrey's answer I have sorted my issue by routing my traffic using PGPool2. However it is advisable to disable connection pooling ins spring boot and use the connection pool in PGPool.
This solution is also good since even other applications done in other languages can also take advantage of the routing.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论