zhaopinboai.com

Efficient Routing of Spring Transactions to Master and Slave Nodes

Written on

Chapter 1: Introduction to Transaction Routing

As projects expand, there may arise a necessity to direct read requests to a secondary (slave) node. Thankfully, this can be accomplished in Spring using the @Transactional annotation along with some configuration.

Section 1.1: Defining Node Types

To start, we need to categorize our nodes. In this context, we will have two types: master and slave. However, this categorization can be expanded if separate connection pools are required for rapid and prolonged queries.

public enum DatabaseNodeType {

MASTER,

SLAVE

}

Next, we need to create a context holder to track which transaction is active in a specific thread. Later, we will configure an interceptor that inspects the parameters of the @Transactional annotation and assigns the correct node type.

public final class DatabaseContextHolder {

private static final ThreadLocal<DatabaseNodeType> CONTEXT = new ThreadLocal<>();

private DatabaseContextHolder() {

throw new UnsupportedOperationException(DatabaseContextHolder.class.getSimpleName() + " cannot be instantiated");

}

public static void set(DatabaseNodeType databaseNodeType) {

CONTEXT.set(databaseNodeType);

}

public static DatabaseNodeType getEnvironment() {

return CONTEXT.get();

}

public static void reset() {

CONTEXT.set(DatabaseNodeType.MASTER);

}

}

Section 1.2: Utilizing AbstractRoutingDataSource

The AbstractRoutingDataSource plays a crucial role in our approach. We need to override the determineCurrentLookupKey method to return a value from our context holder. This will determine which data source is used when a transaction is initiated.

public class MasterSlaveRoutingDataSource extends AbstractRoutingDataSource {

@Override

protected Object determineCurrentLookupKey() {

return DatabaseContextHolder.getEnvironment();

}

}

Following this, we must define our data source beans and set MasterSlaveRoutingDataSource as the primary data source that utilizes the correct node type.

@Configuration

public class JpaRepositoryConfiguration {

public static final String MASTER_DATA_SOURCE = "MASTER_DATA_SOURCE";

public static final String SLAVE_DATA_SOURCE = "SLAVE_DATA_SOURCE";

@Bean

@Primary

public DataSource dataSource() {

MasterSlaveRoutingDataSource masterSlaveRoutingDataSource = new MasterSlaveRoutingDataSource();

Map<DatabaseNodeType, DataSource> targetDataSources = new HashMap<>();

targetDataSources.put(DatabaseNodeType.MASTER, masterDataSource());

targetDataSources.put(DatabaseNodeType.SLAVE, slaveDataSource());

masterSlaveRoutingDataSource.setTargetDataSources(targetDataSources);

masterSlaveRoutingDataSource.setDefaultTargetDataSource(masterDataSource());

return masterSlaveRoutingDataSource;

}

@Bean(MASTER_DATA_SOURCE)

@ConfigurationProperties(prefix = "datasource.master")

public DataSource masterDataSource() {

return DataSourceBuilder.create().build();

}

@Bean(SLAVE_DATA_SOURCE)

@ConfigurationProperties(prefix = "datasource.slave")

public DataSource slaveDataSource() {

return DataSourceBuilder.create().build();

}

}

Chapter 2: Managing Transaction Context

All that remains is to set the appropriate key in the context holder when a transaction begins. There are several methods to achieve this.

Section 2.1: Using Aspect-Oriented Programming (AOP)

The following aspect intercepts invocations marked with @Transactional that are either in the com.mvpotter package or its sub-packages, or annotated with @Repository. It’s essential to reset the context after the method execution to avoid issues with reused threads.

@Aspect

@Order(0)

@Component

public class TransactionAspect {

@Around("(within(com.mvpotter.*) || @annotation(org.springframework.stereotype.Repository)) && @annotation(transactional)")

public Object proceed(ProceedingJoinPoint proceedingJoinPoint, Transactional transactional) throws Throwable {

try {

if (transactional.readOnly()) {

DatabaseContextHolder.set(DatabaseNodeType.SLAVE);

} else {

DatabaseContextHolder.set(DatabaseNodeType.MASTER);

}

return proceedingJoinPoint.proceed();

} finally {

DatabaseContextHolder.reset();

}

}

}

Section 2.2: Creating a Wrapper for PlatformTransactionManager

We can also wrap the PlatformTransactionManager and update the context holder's value when a transaction is initiated.

public class ReplicaAwareTransactionManager implements PlatformTransactionManager {

private final PlatformTransactionManager wrapped;

public ReplicaAwareTransactionManager(final PlatformTransactionManager wrapped) {

this.wrapped = wrapped;

}

@Override

public TransactionStatus getTransaction(TransactionDefinition definition) {

if (definition != null && definition.isReadOnly()) {

DatabaseContextHolder.set(DatabaseNodeType.SLAVE);

} else {

DatabaseContextHolder.set(DatabaseNodeType.MASTER);

}

try {

return wrapped.getTransaction(definition);

} finally {

DatabaseContextHolder.reset();

}

}

@Override

public void commit(TransactionStatus status) throws TransactionException {

wrapped.commit(status);

}

@Override

public void rollback(TransactionStatus status) throws TransactionException {

wrapped.rollback(status);

}

}

Finally, we need to define beans to wrap the transaction manager and designate the wrapper as the primary manager.

@Bean

@Primary

public PlatformTransactionManager transactionManager(@Qualifier("dataSourceTransactionManager") PlatformTransactionManager transactionManager) {

return new ReplicaAwareTransactionManager(transactionManager);

}

@Bean("dataSourceTransactionManager")

public PlatformTransactionManager platformTransactionManager() {

return new DataSourceTransactionManager(dataSource());

}

Chapter 3: Practical Examples

This video provides a real-world example of implementing master-slave routing in Spring Boot using Debezium.

In this video, Vlad Mihalcea discusses high-performance Hibernate strategies that can enhance your application's data handling capabilities.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Navigating the Entrepreneurial Landscape: Insights from a Guide

A comprehensive review of a guide for aspiring entrepreneurs, offering valuable insights and practical steps for business success.

Loop Quantum Gravity: Unraveling the Curvature of Spacetime

Explore Loop Quantum Gravity, a theory aiming to unify Quantum Mechanics and General Relativity by quantizing space and time.

Cyborgs and the Future of Prosthetic Limbs: Merging Biology with Robotics

Explore how advancements in robotics and neuroscience are revolutionizing prosthetic limbs, making them feel and function like natural ones.

Harnessing Competitive Intelligence for Marketing Success

Discover how competitive intelligence can enhance your marketing strategies and give you a competitive edge.

Unlocking the Secrets to Writing Engaging Content

Discover how to create relatable stories that captivate readers and enhance your writing skills through everyday experiences.

Elevate Your Frontend Development with These JS Libraries

Discover top JavaScript libraries to enhance your frontend development and productivity.

Unlock Your Potential: 5 Strategies for New Year's Success

Discover five impactful strategies to ensure your New Year's resolutions succeed and transform your life.

Mastering Email Marketing: Harnessing the Power of Rescheduling

Learn how to effectively use past emails to boost your marketing efforts through strategic rescheduling.