InvalidIsolationLevelException: HibernateJpaDialect Error encountered for read write datasource routing Hibernate

huangapple go评论73阅读模式
英文:

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 = &quot;com.api.repositories&quot;,repositoryBaseClass = BaseJpaRepositoryImpl.class
)
public class DBConfig {
@Autowired
private DbProps dbProps;
@Bean
public DataSource readWriteDataSource() {
LOGGER.info(&quot;DB Props: {}&quot;, 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&lt;Object, Object&gt; dataSourceMap = new HashMap&lt;&gt;();
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(&quot;Proxy-datasource&quot;)
.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(&quot;com.api.models&quot;);
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(&quot;hibernate.dialect&quot;, &quot;org.hibernate.dialect.PostgreSQLDialect&quot;);
//properties.setProperty(&quot;hibernate.connection.release_mode&quot;, &quot;after_transaction&quot;);
properties.setProperty(&quot;hibernate.connection.provider_disables_autocommit&quot;,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 &#39;prepareConnection&#39; 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();
    }

}

GitHub上的演示

英文:

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&lt;Deque&lt;Boolean&gt;&gt; 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&lt;Boolean&gt; getTxStack() {
        return txStack.get();
    }

    static void begin(Boolean readOnly) {
        getTxStack().addLast(readOnly);
    }

    static void complete() {
        getTxStack().removeLast();
    }

}

demo on GitHub

答案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.

huangapple
  • 本文由 发表于 2023年3月7日 23:33:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/75664011.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定