使用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<Post> 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,如下圖所示:
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<Object, Object> 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_persistence jdbc.url.replica=jdbc:postgresql://localhost:5432/high_performance_java_persistence_replica jdbc.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上檢視原始碼以獲取更多詳細資訊。
相關文章
- 資料庫讀寫分離,主從同步實現方法資料庫主從同步
- Spring Aop實現資料庫讀寫分離Spring資料庫
- (7)資料庫讀寫分離,主從同步實現方法(資料庫設定)資料庫主從同步
- 搭建MySQL主從實現Django讀寫分離MySqlDjango
- discuz 配置讀寫分離(主寫從讀)
- 資料庫讀寫分離資料庫
- MySQL怎麼實現主從同步和Django實現MySQL讀寫分離MySql主從同步Django
- [Mysql]主從複製和讀寫分離MySql
- ShardingSphere(七) 讀寫分離配置,實現分庫讀寫操作
- MySQL從庫卡主了--讀寫分離也不能亂讀MySql
- 搭建Redis“主-從-從”模式叢集並使用 RedisTemplate 實現讀寫分離Redis模式
- Mycat中介軟體實現Mysql主從讀寫分離MySql
- Spring的事務管理(一) Spring事務管理的實現,事務的屬性(隔離級別,傳播行為,只讀)Spring
- springboot多資料來源配合docker部署mysql主從實現讀寫分離Spring BootDockerMySql
- 適合用於資料庫主鍵的最佳UUID工具庫 - Vlad Mihalcea資料庫UI
- 做資料庫分離讀寫時,sqlServer資料庫資料同步的問題:資料庫SQLServer
- springboot+mybatis+druid實現mysql主從讀寫分離(五)Spring BootMyBatisUIMySql
- 使用proxysql 1.4.14中介軟體實現mysql 5.7.26主從的讀寫分離MySql
- 大資料資料庫讀寫分離分庫分表大資料資料庫
- Mycat讀寫分離、主從切換、分庫分表的操作記錄
- 什麼是單主資料庫複製? -Vlad Mihalcea資料庫
- MYSQL 主從 + ATLAS 讀寫分離 搭建MySql
- 配置\清除 MySQL 主從 讀寫分離MySql
- MySQL主從複製讀寫分離MySql
- Spring Boot + Mybatis 多資料來源配置實現讀寫分離Spring BootMyBatis
- 搭建Redis簡易叢集實現主從複製和讀寫分離Redis
- 直播賣貨系統,如何實現mysql資料庫的讀寫分離MySql資料庫
- 深度:微服務化的資料庫設計與讀寫分離微服務資料庫
- Docker實現Mariadb分庫分表、讀寫分離Docker
- 【Mongo】Mongo讀寫分離的實現Go
- Mycat2+Mysql一主一從實現讀寫分離配置MySql
- 資料庫中介軟體sharding-jdbc實現讀寫分離資料庫JDBC
- 資料庫讀寫分離Master-Slave資料庫AST
- 使用ShardingSphere-JDBC完成Mysql的分庫分表和讀寫分離JDBCMySql
- 資料讀寫壓力大,讀寫分離
- 使用 Spring Transactional 註釋的最佳方式 - Vlad MihalceaSpring
- 帶貨直播系統,透過主從同步實現讀寫分離主從同步
- Kubernetes 中實現 MySQL 的讀寫分離MySql