[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:
parent
973c276d7b
commit
ac5dbbc5da
|
@ -52,6 +52,7 @@
|
||||||
<module>persistence-libraries</module>
|
<module>persistence-libraries</module>
|
||||||
<module>querydsl</module>
|
<module>querydsl</module>
|
||||||
<module>r2dbc</module>
|
<module>r2dbc</module>
|
||||||
|
<module>read-only-transactions</module>
|
||||||
<module>redis</module>
|
<module>redis</module>
|
||||||
<!-- <module>sirix</module> --> <!-- We haven't upgraded to java 11. Fixing in BAEL-10841 -->
|
<!-- <module>sirix</module> --> <!-- We haven't upgraded to java 11. Fixing in BAEL-10841 -->
|
||||||
<module>solr</module>
|
<module>solr</module>
|
||||||
|
|
|
@ -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`
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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/
|
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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&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>
|
|
@ -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>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue