java 数据库 事务 只读_使用Spring实现访问主从数据库的读写和只读事务/事物的分离路由 -Vlad Mihalcea...

由于单主数据库复制体系结构不仅提供了容错能力和更高的可用性,而且使我们能够通过添加更多从节点来扩展读取操作,由此形成对主数据库进行写入操作,而对复制主数据库的从数据库进行只读操作。

Spring @Transactional

在Spring应用程序中,Web @Controller调用一种@Service方法,该方法使用注释进行@Transactional注释。

默认情况下,Spring事务是可读写的,但是您可以通过注释的read-only属性将它们显式配置为在只读上下文中执行。

例如,以下ForumServiceImpl组件定义了两种服务方法:

newPost,这需要在数据库的“主”节点上执行的读写事务,以及

findAllPostsByTitle,它需要可以在数据库副本节点上执行的只读事务,因此减少了主节点上的负载

@Service

public class ForumServiceImpl

implements ForumService {

@PersistenceContext

private EntityManager entityManager;

@Override

@Transactional

public Post newPost(String title, String... tags) {

Post post = new Post();

post.setTitle(title);

post.getTags().addAll(

entityManager.createQuery("""

select t

from Tag t

where t.name in :tags""", Tag.class)

.setParameter("tags", Arrays.asList(tags))

.getResultList()

);

entityManager.persist(post);

return post;

}

@Override

@Transactional(readOnly = true)

public List findAllPostsByTitle(String title) {

return entityManager.createQuery("""

select p

from Post p

where p.title = :title""", Post.class)

.setParameter("title", title)

.getResultList();

}

}

由于@Transactional注释的readOnly属性默认设置为false,因此该newPost方法使用读写事务上下文。

Spring事务路由

目标:将读写事务路由到主节点数据库,将只读事务路由到副本从节点数据库。

我们可以定义一个ReadWriteDataSource连接主节点和ReadOnlyDataSource连接副本节点的。

读写事务路由由Spring AbstractRoutingDataSource抽象完成,由Spring 实现TransactionRoutingDatasource,如下图所示:

6cf107c536c22217e0af69de78707c0c.png

TransactionRoutingDataSource实现非常简单,如下所示:

public class TransactionRoutingDataSource

extends AbstractRoutingDataSource {

@Nullable

@Override

protected Object determineCurrentLookupKey() {

return TransactionSynchronizationManager

.isCurrentTransactionReadOnly() ?

DataSourceType.READ_ONLY :

DataSourceType.READ_WRITE;

}

}

基本上,我们检查TransactionSynchronizationManager存储当前事务上下文的Spring 类,以检查当前运行的Spring事务是否为只读。

该determineCurrentLookupKey方法返回鉴别符值,该鉴别符值将用于选择读写JDBC或只读JDBC DataSource。

DataSourceType仅仅是一个基本的Java枚举定义我们的事物路由选项:

public enum  DataSourceType {

READ_WRITE,

READ_ONLY

}

Spring读写和只读JDBC DataSource配置

@Configuration

@ComponentScan(

basePackages = "com.vladmihalcea.book.hpjp.util.spring.routing")

@PropertySource("/META-INF/jdbc-postgresql-replication.properties")

public class TransactionRoutingConfiguration

extends AbstractJPAConfiguration {

@Value("${jdbc.url.primary}")

private String primaryUrl;

@Value("${jdbc.url.replica}")

private String replicaUrl;

@Value("${jdbc.username}")

private String username;

@Value("${jdbc.password}")

private String password;

@Bean

public DataSource readWriteDataSource() {

PGSimpleDataSource dataSource = new PGSimpleDataSource();

dataSource.setURL(primaryUrl);

dataSource.setUser(username);

dataSource.setPassword(password);

return connectionPoolDataSource(dataSource);

}

@Bean

public DataSource readOnlyDataSource() {

PGSimpleDataSource dataSource = new PGSimpleDataSource();

dataSource.setURL(replicaUrl);

dataSource.setUser(username);

dataSource.setPassword(password);

return connectionPoolDataSource(dataSource);

}

@Bean

public TransactionRoutingDataSource actualDataSource() {

TransactionRoutingDataSource routingDataSource =

new TransactionRoutingDataSource();

Map dataSourceMap = new HashMap<>();

dataSourceMap.put(

DataSourceType.READ_WRITE,

readWriteDataSource()

);

dataSourceMap.put(

DataSourceType.READ_ONLY,

readOnlyDataSource()

);

routingDataSource.setTargetDataSources(dataSourceMap);

return routingDataSource;

}

@Override

protected Properties additionalProperties() {

Properties properties = super.additionalProperties();

properties.setProperty("hibernate.connection.provider_disables_autocommit",

Boolean.TRUE.toString()

);

return properties;

}

@Override

protected String[] packagesToScan() {

return new String[]{"com.vladmihalcea.book.hpjp.hibernate.transaction.forum"};

}

@Override

protected String databaseType() {

return Database.POSTGRESQL.name().toLowerCase();

}

protected HikariConfig hikariConfig(

DataSource dataSource) {

HikariConfig hikariConfig = new HikariConfig();

int cpuCores = Runtime.getRuntime().availableProcessors();

hikariConfig.setMaximumPoolSize(cpuCores * 4);

hikariConfig.setDataSource(dataSource);

hikariConfig.setAutoCommit(false);

return hikariConfig;

}

protected HikariDataSource connectionPoolDataSource(

DataSource dataSource) {

return new HikariDataSource(hikariConfig(dataSource));

}

}

/META-INF/jdbc-postgresql-replication.properties资源文件提供了配置的读写和只读JDBC DataSource组件:

hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect

jdbc.url.primary=jdbc:postgresql://localhost:5432/high_performance_java_persistencejdbc.url.replica=jdbc:postgresql://localhost:5432/high_performance_java_persistence_replicajdbc.username=postgres

jdbc.password=admin

jdbc.url.primary属性定义主节点的URL,而jdbc.url.replica定义副本节点的URL。

readWriteDataSource限定读写JDBC DataSource,而readOnlyDataSource部件限定只读JDBC DataSource。

请注意,读写数据源和只读数据源均使用HikariCP进行连接池。有关使用数据库连接池的好处的更多详细信息,请参阅本文。

这些actualDataSource充当可读写和只读数据源的外观,并使用该TransactionRoutingDataSource实用程序来实现。

在readWriteDataSource使用DataSourceType.READ_WRITE作为key注册,readOnlyDataSource使用的DataSourceType.READ_ONLY作为key注册。

因此,当执行读写@Transactional方法时,readWriteDataSource将使用,而当执行@Transactional(readOnly = true)方法时,readOnlyDataSource将使用。

请注意,该additionalProperties方法定义了hibernate.connection.provider_disables_autocommitHibernate属性,我将其添加到Hibernate中以延迟RESOURCE_LOCAL JPA事务的数据库获取。

不仅hibernate.connection.provider_disables_autocommit使您可以更好地利用数据库连接,而且这是我们使本示例工作的唯一方法,因为如果没有此配置,则必须在调用determineCurrentLookupKeymethod 之前获取连接TransactionRoutingDataSource。

有关hibernate.connection.provider_disables_autocommit配置的更多详细信息,请参阅[url=https://vladmihalcea.com/why-you-should-always-use-hibernate-connection-provider_disables_autocommit-for-resource-local-jpa-transactions/]本文[/url]。

构建JPA所需的其余Spring组件EntityManagerFactory由AbstractJPAConfiguration基类定义。

基本上,actualDataSource进一步由DataSource-Proxy包装,并提供给JPA ENtityManagerFactory。您可以在GitHub上查看源代码以获取更多详细信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值