diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml
index 7be71ad215..23d29de1e8 100644
--- a/persistence-modules/pom.xml
+++ b/persistence-modules/pom.xml
@@ -52,6 +52,7 @@
persistence-libraries
querydsl
r2dbc
+ read-only-transactions
redis
solr
diff --git a/persistence-modules/read-only-transactions/README.md b/persistence-modules/read-only-transactions/README.md
new file mode 100644
index 0000000000..90a86e551e
--- /dev/null
+++ b/persistence-modules/read-only-transactions/README.md
@@ -0,0 +1,8 @@
+### Relevant Articles:
+-
+
+### Instructions
+To run the `com.baeldung.read_only_transactions.TransactionSetupIntegrationTest` first follow the steps described next:
+- run the command `docker-compose -f docker-compose-mysql.yml up`
+- Open a SQL client of your preference and execute the `create.sql` script.
+- You can check the mysql logs using `tail -f mysql/${name of de log file created}.log`
\ No newline at end of file
diff --git a/persistence-modules/read-only-transactions/create.sql b/persistence-modules/read-only-transactions/create.sql
new file mode 100644
index 0000000000..0ba60be150
--- /dev/null
+++ b/persistence-modules/read-only-transactions/create.sql
@@ -0,0 +1,43 @@
+create table book (
+ id bigint(20) AUTO_INCREMENT primary key,
+ name varchar(255) not null,
+ uuid varchar(40)
+);
+
+
+DELIMITER ;;
+DROP PROCEDURE IF EXISTS populate;
+create procedure populate()
+BEGIN
+ SET @name1='Josh purchase';
+ SET @name2='Henry purchase';
+ SET @name3='Betty purchase';
+ SET @name4='Kate purchase';
+ SET @name5='Mari purchase';
+ SET @name='';
+ SET @counter=0;
+
+ START TRANSACTION;
+
+ while @counter < 1000000 do
+ SET @name = case
+ when MOD(@counter, 5) = 0 THEN @name5
+ when MOD(@counter, 3) = 0 THEN @name3
+ when MOD(@counter, 4) = 0 THEN @name4
+ when MOD(@counter, 2) = 0 THEN @name2
+ else @name1
+ end;
+
+ insert into book(name, uuid) values(@name, uuid());
+ SET @counter=@counter+1;
+ end while;
+
+ COMMIT;
+
+END;;
+
+DELIMITER ;
+
+CALL populate();
+
+
diff --git a/persistence-modules/read-only-transactions/docker-compose-mysql.yml b/persistence-modules/read-only-transactions/docker-compose-mysql.yml
new file mode 100644
index 0000000000..a922fabbd8
--- /dev/null
+++ b/persistence-modules/read-only-transactions/docker-compose-mysql.yml
@@ -0,0 +1,15 @@
+version: "3.9" # optional since v1.27.0
+services:
+ mysql:
+ build: .
+ restart: always
+ ports:
+ - "3306:3306"
+ environment:
+ MYSQL_PASSWORD: "baeldung"
+ MYSQL_USER: "baeldung"
+ MYSQL_DATABASE: "baeldung"
+ MYSQL_ROOT_PASSWORD: "baeldung"
+ command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --innodb_buffer_pool_size=3G --innodb_adaptive_hash_index=off --query_cache_size=0 --query_cache_type=0 --log_output=FILE --general_log=1
+ volumes:
+ - ./mysql:/var/lib/mysql/
\ No newline at end of file
diff --git a/persistence-modules/read-only-transactions/pom.xml b/persistence-modules/read-only-transactions/pom.xml
new file mode 100644
index 0000000000..d3822f8e58
--- /dev/null
+++ b/persistence-modules/read-only-transactions/pom.xml
@@ -0,0 +1,89 @@
+
+
+
+ 4.0.0
+ read-only-transactions
+ 0.0.1-SNAPSHOT
+ read-only-transactions
+
+
+ com.baeldung
+ persistence-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+ ${spring-boot.version}
+
+
+ com.zaxxer
+ HikariCP
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring-boot.version}
+ test
+
+
+
+ org.springframework
+ spring-test
+ ${spring-test.version}
+ test
+
+
+
+ mysql
+ mysql-connector-java
+ ${mysql.version}
+
+
+
+ org.hibernate
+ hibernate-core
+ ${hibernate.version}
+
+
+
+ com.zaxxer
+ HikariCP
+ ${hikari.version}
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter.version}
+ test
+
+
+
+ com.h2database
+ h2
+ ${h2.version}
+ runtime
+
+
+
+
+
+
+ 8.0.21
+ 4.0.3
+ 5.6.1.Final
+ 2.6.1
+ 5.3.13
+ 5.8.2
+ 1.4.200
+
+
\ No newline at end of file
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/Book.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/Book.java
new file mode 100644
index 0000000000..000859a201
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/Book.java
@@ -0,0 +1,44 @@
+package com.baeldung.readonlytransactions.h2;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "book")
+public class Book {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+
+ private String uuid;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/BookService.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/BookService.java
new file mode 100644
index 0000000000..0ecbc4a0d4
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/BookService.java
@@ -0,0 +1,24 @@
+package com.baeldung.readonlytransactions.h2;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManagerFactory;
+
+@Service
+public class BookService {
+
+ private EntityManagerFactory entityManagerFactory;
+
+ public BookService(@Autowired @Qualifier("h2EntityManagerFactory") EntityManagerFactory entityManagerFactory) {
+ this.entityManagerFactory = entityManagerFactory;
+ }
+
+ @Transactional(readOnly = true)
+ public Book getBookById(long id) {
+ return entityManagerFactory.createEntityManager()
+ .find(Book.class, id);
+ }
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/Config.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/Config.java
new file mode 100644
index 0000000000..241dad417b
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/Config.java
@@ -0,0 +1,54 @@
+package com.baeldung.readonlytransactions.h2;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import java.util.Properties;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+@Configuration
+public class Config {
+
+ @Bean("h2DataSource")
+ public DataSource dataSource() {
+ HikariConfig config = new HikariConfig();
+ config.setJdbcUrl("jdbc:h2:mem:mydb");
+ config.setUsername("sa");
+ config.setPassword("");
+ config.setDriverClassName("org.h2.Driver");
+ return new HikariDataSource(config);
+ }
+
+ @Bean("h2EntityManagerFactory")
+ public EntityManagerFactory entityManagerFactory(@Qualifier("h2DataSource") DataSource dataSource) {
+ HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+ vendorAdapter.setGenerateDdl(false);
+
+ LocalContainerEntityManagerFactoryBean managerFactoryBean = new LocalContainerEntityManagerFactoryBean();
+ managerFactoryBean.setJpaVendorAdapter(vendorAdapter);
+ managerFactoryBean.setPackagesToScan(Config.class.getPackage()
+ .getName());
+ managerFactoryBean.setDataSource(dataSource);
+
+ Properties properties = new Properties();
+
+ properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
+ properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
+
+ properties.setProperty("hibernate.show_sql", "true");
+ properties.setProperty("hibernate.format_sql", "true");
+
+ managerFactoryBean.setJpaProperties(properties);
+ managerFactoryBean.afterPropertiesSet();
+
+ return managerFactoryBean.getObject();
+ }
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/TransactionConfig.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/TransactionConfig.java
new file mode 100644
index 0000000000..62abb85edd
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/h2/TransactionConfig.java
@@ -0,0 +1,21 @@
+package com.baeldung.readonlytransactions.h2;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.persistence.EntityManagerFactory;
+
+@Configuration
+@EnableTransactionManagement
+public class TransactionConfig {
+
+ @Bean
+ public PlatformTransactionManager transactionManager(@Qualifier("h2EntityManagerFactory") EntityManagerFactory entityManagerFactory) {
+ return new JpaTransactionManager(entityManagerFactory);
+ }
+
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/BaseRepo.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/BaseRepo.java
new file mode 100644
index 0000000000..81ff78efb2
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/BaseRepo.java
@@ -0,0 +1,29 @@
+package com.baeldung.readonlytransactions.mysql.dao;
+
+import com.baeldung.readonlytransactions.utils.ExecutorUtils;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+public abstract class BaseRepo {
+
+ protected long execQuery(Consumer function) {
+ AtomicLong count = new AtomicLong(0);
+
+ ExecutorService executor = ExecutorUtils.createExecutor(10, 10);
+ ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ scheduler.schedule(executor::shutdownNow, 5L, TimeUnit.SECONDS);
+ scheduler.shutdown();
+
+ while (!executor.isShutdown()) {
+ executor.execute(() -> function.accept(count));
+ }
+
+ return count.get();
+ }
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/MyRepoJPA.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/MyRepoJPA.java
new file mode 100644
index 0000000000..727e88219f
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/MyRepoJPA.java
@@ -0,0 +1,36 @@
+package com.baeldung.readonlytransactions.mysql.dao;
+
+import org.hibernate.Session;
+
+import com.baeldung.readonlytransactions.mysql.entities.Book;
+
+import java.util.SplittableRandom;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+public class MyRepoJPA extends BaseRepo {
+
+ private EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpa-unit");
+ private SplittableRandom random = new SplittableRandom();
+
+ public long runQuery() {
+ return execQuery(this::runSql);
+ }
+
+ private void runSql(AtomicLong count) {
+ if (Thread.interrupted()) {
+ return;
+ }
+
+ EntityManager entityManager = entityManagerFactory.createEntityManager();
+ Session session = entityManager.unwrap(Session.class);
+ session.setDefaultReadOnly(true);
+ entityManager.find(Book.class, 1L + random.nextLong(0, 1000000));
+ count.incrementAndGet();
+ entityManager.clear();
+ }
+
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/MyRepoJdbc.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/MyRepoJdbc.java
new file mode 100644
index 0000000000..306bfd5f0c
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/MyRepoJdbc.java
@@ -0,0 +1,71 @@
+package com.baeldung.readonlytransactions.mysql.dao;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.SplittableRandom;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MyRepoJdbc extends BaseRepo {
+
+ static {
+ try {
+ Class.forName("com.mysql.cj.jdbc.Driver");
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private HikariDataSource ds;
+ private SplittableRandom random = new SplittableRandom();
+
+ public MyRepoJdbc(boolean readOnly, boolean autocommit) {
+ HikariConfig config = new HikariConfig();
+ config.setJdbcUrl("jdbc:mysql://localhost/baeldung?useUnicode=true&characterEncoding=UTF-8");
+ config.setUsername("baeldung");
+ config.setPassword("baeldung");
+ config.setReadOnly(readOnly);
+ config.setAutoCommit(autocommit);
+ ds = new HikariDataSource(config);
+ }
+
+ private Connection getConnection() throws SQLException {
+ return ds.getConnection();
+ }
+
+ public long runQuery(Boolean autoCommit, Boolean readOnly) {
+ try {
+ return execQuery(count -> runSql(count, autoCommit, readOnly));
+ } finally {
+ ds.close();
+ }
+ }
+
+ private void runSql(AtomicLong count, Boolean autoCommit, Boolean readOnly) {
+ if (Thread.interrupted()) {
+ return;
+ }
+
+ try (Connection connect = getConnection(); PreparedStatement statement = connect.prepareStatement("select * from transactions where id = ?")) {
+ if (autoCommit != null)
+ connect.setAutoCommit(autoCommit);
+
+ if (readOnly != null)
+ connect.setReadOnly(readOnly);
+
+ statement.setLong(1, 1L + random.nextLong(0, 100000));
+ ResultSet resultSet = statement.executeQuery();
+
+ if (autoCommit != null && !autoCommit)
+ connect.commit();
+
+ count.incrementAndGet();
+ resultSet.close();
+ } catch (Exception ignored) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/MyRepoSpring.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/MyRepoSpring.java
new file mode 100644
index 0000000000..387f5ec722
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/dao/MyRepoSpring.java
@@ -0,0 +1,22 @@
+package com.baeldung.readonlytransactions.mysql.dao;
+
+import com.baeldung.readonlytransactions.mysql.spring.repositories.BookRepository;
+
+import java.util.SplittableRandom;
+
+public class MyRepoSpring extends BaseRepo {
+
+ private SplittableRandom random = new SplittableRandom();
+ private BookRepository repository;
+
+ public MyRepoSpring(BookRepository repository) {
+ this.repository = repository;
+ }
+
+ public long runQuery() {
+ return execQuery(count -> {
+ repository.get(1L + random.nextLong(0, 1000000));
+ count.incrementAndGet();
+ });
+ }
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/entities/Book.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/entities/Book.java
new file mode 100644
index 0000000000..405cc9c2c2
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/entities/Book.java
@@ -0,0 +1,44 @@
+package com.baeldung.readonlytransactions.mysql.entities;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "book")
+public class Book {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+
+ private String uuid;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/Config.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/Config.java
new file mode 100644
index 0000000000..abaa63b197
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/Config.java
@@ -0,0 +1,77 @@
+package com.baeldung.readonlytransactions.mysql.spring;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import com.baeldung.readonlytransactions.mysql.dao.MyRepoSpring;
+import com.baeldung.readonlytransactions.mysql.spring.entities.BookEntity;
+import com.baeldung.readonlytransactions.mysql.spring.repositories.BookRepository;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import java.util.Properties;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+@Configuration
+@EnableJpaRepositories(basePackageClasses = Config.class, enableDefaultTransactions = false)
+@EnableTransactionManagement
+@EnableAspectJAutoProxy
+public class Config {
+
+ @Bean
+ public MyRepoSpring repoSpring(BookRepository repository) {
+ return new MyRepoSpring(repository);
+ }
+
+ private DataSource dataSource(boolean readOnly, boolean isAutoCommit) {
+ HikariConfig config = new HikariConfig();
+ config.setJdbcUrl("jdbc:mysql://localhost/baeldung?useUnicode=true&characterEncoding=UTF-8");
+ config.setUsername("baeldung");
+ config.setPassword("baeldung");
+ config.setReadOnly(readOnly);
+ config.setAutoCommit(isAutoCommit);
+ return new HikariDataSource(config);
+ }
+
+ @Bean
+ public DataSource dataSource() {
+ return new RoutingDS(dataSource(false, false), dataSource(true, true));
+ }
+
+ @Bean
+ public EntityManagerFactory entityManagerFactory(DataSource dataSource) {
+ HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+ vendorAdapter.setGenerateDdl(false);
+
+ LocalContainerEntityManagerFactoryBean managerFactoryBean = new LocalContainerEntityManagerFactoryBean();
+ managerFactoryBean.setJpaVendorAdapter(vendorAdapter);
+ managerFactoryBean.setPackagesToScan(BookEntity.class.getPackage()
+ .getName());
+ managerFactoryBean.setDataSource(dataSource);
+
+ Properties properties = new Properties();
+
+ properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
+ properties.setProperty("hibernate.hbm2ddl.auto", "validate");
+
+ managerFactoryBean.setJpaProperties(properties);
+ managerFactoryBean.afterPropertiesSet();
+
+ return managerFactoryBean.getObject();
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
+ return new JpaTransactionManager(entityManagerFactory);
+ }
+
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/ReadOnlyContext.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/ReadOnlyContext.java
new file mode 100644
index 0000000000..2933c8c4a9
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/ReadOnlyContext.java
@@ -0,0 +1,26 @@
+package com.baeldung.readonlytransactions.mysql.spring;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ReadOnlyContext {
+
+ private static final ThreadLocal READ_ONLY_LEVEL = ThreadLocal.withInitial(() -> new AtomicInteger(0));
+
+ private ReadOnlyContext() {
+ }
+
+ public static boolean isReadOnly() {
+ return READ_ONLY_LEVEL.get()
+ .get() > 0;
+ }
+
+ public static void enter() {
+ READ_ONLY_LEVEL.get()
+ .incrementAndGet();
+ }
+
+ public static void exit() {
+ READ_ONLY_LEVEL.get()
+ .decrementAndGet();
+ }
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/ReadOnlyInterception.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/ReadOnlyInterception.java
new file mode 100644
index 0000000000..30488970a8
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/ReadOnlyInterception.java
@@ -0,0 +1,29 @@
+package com.baeldung.readonlytransactions.mysql.spring;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class ReadOnlyInterception {
+
+ private static final Logger logger = LoggerFactory.getLogger(ReadOnlyInterception.class);
+
+ @Around("@annotation(com.baeldung.readonlytransactions.mysql.spring.ReaderDS)")
+ public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
+ try {
+ ReadOnlyContext.enter();
+ //Debug data source switch
+ logger.debug("-----------------------------Entering read only zone-----------------------------");
+ return joinPoint.proceed();
+ } finally {
+ logger.debug("-----------------------------Leaving read only zone------------------------------");
+ ReadOnlyContext.exit();
+ }
+ }
+
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/ReaderDS.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/ReaderDS.java
new file mode 100644
index 0000000000..312c65b3cf
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/ReaderDS.java
@@ -0,0 +1,10 @@
+package com.baeldung.readonlytransactions.mysql.spring;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ReaderDS {
+}
diff --git a/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/RoutingDS.java b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/RoutingDS.java
new file mode 100644
index 0000000000..36ce8a16e6
--- /dev/null
+++ b/persistence-modules/read-only-transactions/src/main/java/com/baeldung/readonlytransactions/mysql/spring/RoutingDS.java
@@ -0,0 +1,35 @@
+package com.baeldung.readonlytransactions.mysql.spring;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+public class RoutingDS extends AbstractRoutingDataSource {
+
+ private static final Logger logger = LoggerFactory.getLogger(RoutingDS.class);
+
+ RoutingDS(DataSource writer, DataSource reader) {
+
+ Map