[BAEL-4120] Read-only transactions samples (#11841)

* [BAEL-4120] Read-only transactions samples

* Extract query runner jobs logic

* Apply test name standards

* Fix code standards

* Use pom properties to define versions

* Rename main package

* Rename generics

* Code clean up

* Code clean up on lambdas

* Add debug logs

* Fix format issues

* Rename entity
This commit is contained in:
Thiago dos Santos Hora 2022-05-23 08:09:09 +02:00 committed by GitHub
parent 973c276d7b
commit ac5dbbc5da
27 changed files with 1056 additions and 0 deletions

View File

@ -52,6 +52,7 @@
<module>persistence-libraries</module>
<module>querydsl</module>
<module>r2dbc</module>
<module>read-only-transactions</module>
<module>redis</module>
<!-- <module>sirix</module> --> <!-- We haven't upgraded to java 11. Fixing in BAEL-10841 -->
<module>solr</module>

View File

@ -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`

View File

@ -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();

View File

@ -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/

View File

@ -0,0 +1,89 @@
<?xml version="1.0"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>read-only-transactions</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>read-only-transactions</name>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>persistence-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring-boot.version}</version>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-test.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikari.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<properties>
<mysql.version>8.0.21</mysql.version>
<hikari.version>4.0.3</hikari.version>
<hibernate.version>5.6.1.Final</hibernate.version>
<spring-boot.version>2.6.1</spring-boot.version>
<spring-test.version>5.3.13</spring-test.version>
<junit-jupiter.version>5.8.2</junit-jupiter.version>
<h2.version>1.4.200</h2.version>
</properties>
</project>

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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<AtomicLong> 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();
}
}

View File

@ -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();
}
}

View File

@ -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) {
}
}
}

View File

@ -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();
});
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,26 @@
package com.baeldung.readonlytransactions.mysql.spring;
import java.util.concurrent.atomic.AtomicInteger;
public class ReadOnlyContext {
private static final ThreadLocal<AtomicInteger> 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();
}
}

View File

@ -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();
}
}
}

View File

@ -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 {
}

View File

@ -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<Object, Object> dataSources = new HashMap<>();
dataSources.put("writer", writer);
dataSources.put("reader", reader);
setTargetDataSources(dataSources);
}
@Override
protected Object determineCurrentLookupKey() {
String dataSourceMode = ReadOnlyContext.isReadOnly() ? "reader" : "writer";
// Testing data source switch
logger.debug("-----------------------------Datasource: {} ---------------------------------", dataSourceMode);
return dataSourceMode;
}
}

View File

@ -0,0 +1,45 @@
package com.baeldung.readonlytransactions.mysql.spring.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 BookEntity {
@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;
}
}

View File

@ -0,0 +1,21 @@
package com.baeldung.readonlytransactions.mysql.spring.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.baeldung.readonlytransactions.mysql.spring.ReaderDS;
import com.baeldung.readonlytransactions.mysql.spring.entities.BookEntity;
import javax.transaction.Transactional;
public interface BookRepository extends JpaRepository<BookEntity, Long> {
@ReaderDS
@Query("Select 1 from BookEntity t where t.id = ?1")
Long get(Long id);
@Transactional
default BookEntity persist(BookEntity book) {
return this.save(book);
}
}

View File

@ -0,0 +1,16 @@
package com.baeldung.readonlytransactions.utils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ExecutorUtils {
private ExecutorUtils() {
}
public static ExecutorService createExecutor(int corePoolSize, int maximumPoolSize) {
return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.DiscardOldestPolicy());
}
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="jpa-unit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>com.baeldung.readonlytransactions.mysql.entities.Book</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/baeldung?useUnicode=true&amp;characterEncoding=UTF-8"/>
<property name="javax.persistence.jdbc.user" value="baeldung"/>
<property name="javax.persistence.jdbc.password" value="baeldung"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="validate"/>
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.temp.use_jdbc_metadata_defaults" value="true"/>
</properties>
</persistence-unit>
</persistence>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -
%msg%n
</pattern>
</encoder>
</appender>
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="org.hibernate.engine.transaction.internal.TransactionImpl" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@ -0,0 +1,77 @@
package com.baeldung.readonlytransactions;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.hibernate.Session;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.baeldung.readonlytransactions.h2.Config;
import com.baeldung.readonlytransactions.h2.Book;
import com.baeldung.readonlytransactions.mysql.spring.ReadOnlyInterception;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, initializers = JPATransactionIntegrationTest.TestConfig.class, classes = { ReadOnlyInterception.class })
class JPATransactionIntegrationTest {
static class TestConfig implements ApplicationContextInitializer<GenericApplicationContext> {
@Override
public void initialize(GenericApplicationContext applicationContext) {
new AnnotatedBeanDefinitionReader(applicationContext).register(Config.class);
}
}
@Autowired
@Qualifier("h2EntityManagerFactory")
private EntityManagerFactory entityManagerFactory;
@BeforeEach
void setUp() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction()
.begin();
entityManager.createQuery("DELETE FROM Book")
.executeUpdate();
Book book = new Book();
book.setName("Test 1");
book.setUuid(UUID.randomUUID()
.toString());
entityManager.merge(book);
entityManager.getTransaction()
.commit();
}
@Test
void givenAEntityManagerDefinedAsReadOnly_whenCreatingATransaction_thenAReadOnlyTransactionShouldBeCreated() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.unwrap(Session.class)
.setDefaultReadOnly(true);
entityManager.getTransaction()
.begin();
Book book = entityManager.find(Book.class, 1L);
entityManager.getTransaction()
.commit();
entityManager.unwrap(Session.class)
.setDefaultReadOnly(false);
assertNotNull(book);
}
}

View File

@ -0,0 +1,72 @@
package com.baeldung.readonlytransactions;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.baeldung.readonlytransactions.h2.Config;
import com.baeldung.readonlytransactions.h2.Book;
import com.baeldung.readonlytransactions.h2.TransactionConfig;
import com.baeldung.readonlytransactions.h2.BookService;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, initializers = SpringTransactionReadOnlyIntegrationTest.TestConfig.class, classes = { BookService.class })
class SpringTransactionReadOnlyIntegrationTest {
static class TestConfig implements ApplicationContextInitializer<GenericApplicationContext> {
@Override
public void initialize(GenericApplicationContext applicationContext) {
AnnotatedBeanDefinitionReader beanDefinitionReader = new AnnotatedBeanDefinitionReader(applicationContext);
beanDefinitionReader.register(Config.class);
beanDefinitionReader.register(TransactionConfig.class);
}
}
@Autowired
@Qualifier("h2EntityManagerFactory")
private EntityManagerFactory entityManagerFactory;
@Autowired
private BookService service;
@BeforeEach
void setUp() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction()
.begin();
entityManager.createQuery("DELETE FROM Book")
.executeUpdate();
Book book = new Book();
book.setName("Test 1");
book.setUuid(UUID.randomUUID()
.toString());
entityManager.merge(book);
entityManager.getTransaction()
.commit();
}
@Test
void givenThatSpringTransactionManagementIsEnabled_whenAMethodIsAnnotatedAsTransactionalReadOnly_thenSpringShouldTakeCareOfTheTransaction() {
Book book = service.getBookById(1L);
assertNotNull(book);
}
}

View File

@ -0,0 +1,106 @@
package com.baeldung.readonlytransactions;
import static java.util.stream.Collectors.toList;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.baeldung.readonlytransactions.mysql.dao.MyRepoJPA;
import com.baeldung.readonlytransactions.mysql.dao.MyRepoJdbc;
import com.baeldung.readonlytransactions.mysql.dao.MyRepoSpring;
import com.baeldung.readonlytransactions.mysql.spring.Config;
import com.baeldung.readonlytransactions.mysql.spring.ReadOnlyInterception;
import com.baeldung.readonlytransactions.mysql.spring.entities.BookEntity;
import com.baeldung.readonlytransactions.mysql.spring.repositories.BookRepository;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Stream;
// Needs to be run with Docker look at the readme file.
@Disabled
@ExtendWith(SpringExtension.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, initializers = TransactionSetupIntegrationTest.TestConfig.class, classes = { ReadOnlyInterception.class })
class TransactionSetupIntegrationTest {
static class TestConfig implements ApplicationContextInitializer<GenericApplicationContext> {
@Override
public void initialize(GenericApplicationContext applicationContext) {
new AnnotatedBeanDefinitionReader(applicationContext).register(Config.class);
}
}
private static final Logger logger = LoggerFactory.getLogger(TransactionSetupIntegrationTest.class);
@Autowired
private MyRepoSpring repoSpring;
@Autowired
private BookRepository repository;
@Test
void givenTheDifferentTransactionSetup_whenRunningAThroughputTest_thenWeCanObserveTheSystem() {
Map<String, Supplier<Long>> jdbcConfigurations = new LinkedHashMap<>();
jdbcConfigurations.put("JPA: Session read only true and autocommit disabled", () -> new MyRepoJPA().runQuery());
jdbcConfigurations.put("Spring: Session read only and autocommit true", () -> repoSpring.runQuery());
jdbcConfigurations.put("JDBC: Global read only and autocommit enabled", () -> new MyRepoJdbc(true, true).runQuery(null, null));
jdbcConfigurations.put("JDBC: Global read only false and autocommit enabled", () -> new MyRepoJdbc(false, true).runQuery(null, null));
jdbcConfigurations.put("JDBC: Global read only true and autocommit disabled", () -> new MyRepoJdbc(true, false).runQuery(null, null));
jdbcConfigurations.put("JDBC: Session read only and autocommit disabled", () -> new MyRepoJdbc(false, false).runQuery(false, false));
jdbcConfigurations.put("JDBC: Session read only and autocommit enabled", () -> new MyRepoJdbc(false, false).runQuery(true, true));
jdbcConfigurations.put("JDBC: Session read only false and autocommit enabled", () -> new MyRepoJdbc(false, false).runQuery(false, true));
jdbcConfigurations.put("JDBC: Session read only true and autocommit disabled", () -> new MyRepoJdbc(false, false).runQuery(true, false));
jdbcConfigurations.entrySet()
.stream()
.flatMap(entry -> {
Stream.Builder<String> builder = Stream.builder();
return builder.add(entry.getKey() + " Total: " + entry.getValue()
.get())
.add(entry.getKey() + " Total: " + entry.getValue()
.get())
.add(entry.getKey() + " Total: " + entry.getValue()
.get())
.build();
})
.collect(toList())
.stream()
.peek(o -> logger.info("--------------------------------------------------"))
.forEach(logger::info);
}
@Test
void givenThatSpringTransactionManagementIsEnabled_whenAMethodIsAnnotatedAsTransactionalReadOnly_thenSpringShouldTakeCareOfTheTransaction() {
Long id = repository.get(2L);
assertNotNull(id);
}
@Test
void givenThatSpringTransactionManagementIsEnabled_whenAMethodIsAnnotatedAsTransactional_thenSpringShouldTakeCareOfTheTransaction() {
BookEntity book = new BookEntity();
book.setName("Persistence test");
book.setUuid(UUID.randomUUID()
.toString());
book = repository.persist(book);
assertNotNull(book.getId());
}
}