Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion persistence-modules/spring-data-jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@
<spring-boot.repackage.skip>true</spring-boot.repackage.skip>
</properties>

</project>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.baeldung.rwrouting;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackageClasses = OrderRepository.class,
entityManagerFactoryRef = "routingEntityManagerFactory",
transactionManagerRef = "routingTransactionManager"
)
public class DataSourceConfiguration {

@Bean
@ConfigurationProperties("spring.datasource.readwrite")
public DataSourceProperties readWriteProperties() {
return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("spring.datasource.readonly")
public DataSourceProperties readOnlyProperties() {
return new DataSourceProperties();
}

@Bean
public DataSource readWriteDataSource() {
return readWriteProperties().initializeDataSourceBuilder()
.build();
}

@Bean
public DataSource readOnlyDataSource() {
return readOnlyProperties().initializeDataSourceBuilder()
.build();
}

@Bean
@Primary
public TransactionRoutingDataSource routingDataSource() {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();

Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.READ_WRITE, readWriteDataSource());
dataSourceMap.put(DataSourceType.READ_ONLY, readOnlyDataSource());

routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(readWriteDataSource());

return routingDataSource;
}

@Bean
public DataSourceInitializer readWriteInitializer(@Qualifier("readWriteDataSource") DataSource readWriteDataSource) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(new ClassPathResource("rwrouting-schema.sql"));

DataSourceInitializer init = new DataSourceInitializer();
init.setDataSource(readWriteDataSource);
init.setDatabasePopulator(populator);
return init;
}

@Bean
public DataSourceInitializer readOnlyInitializer(@Qualifier("readOnlyDataSource") DataSource readOnlyDataSource) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(new ClassPathResource("rwrouting-schema.sql"));

DataSourceInitializer init = new DataSourceInitializer();
init.setDataSource(readOnlyDataSource);
init.setDatabasePopulator(populator);
return init;
}

@Bean
public DataSource dataSource() {
return new LazyConnectionDataSourceProxy(routingDataSource());
}

@Bean
public LocalContainerEntityManagerFactoryBean routingEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(dataSource())
.packages(OrderRepository.class)
.build();
}

@Bean
public PlatformTransactionManager routingTransactionManager(LocalContainerEntityManagerFactoryBean routingEntityManagerFactory) {
return new JpaTransactionManager(Objects.requireNonNull(routingEntityManagerFactory.getObject()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.baeldung.rwrouting;

public enum DataSourceType {
READ_WRITE, READ_ONLY
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.baeldung.rwrouting;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "order_table")
public class Order {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;

public Order() {
}

public Order(String description) {
this.description = description;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.baeldung.rwrouting;

import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.baeldung.rwrouting;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

private final OrderRepository orderRepository;

public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}

@Transactional
public Order save(Order order) {
return orderRepository.save(order);
}

@Transactional(readOnly = true)
public List<Order> findAllReadOnly() {
return orderRepository.findAll();
}

@Transactional
public List<Order> findAllReadWrite() {
return orderRepository.findAll();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.baeldung.rwrouting;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RwRoutingApplication {

public static void main(String[] args) {
SpringApplication.run(RwRoutingApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.baeldung.rwrouting;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;

public class TransactionRoutingDataSource extends AbstractRoutingDataSource {

@Override
protected Object determineCurrentLookupKey() {
boolean readOnly = TransactionSynchronizationManager
.isCurrentTransactionReadOnly();

if (readOnly) {
return DataSourceType.READ_ONLY;
}

return DataSourceType.READ_WRITE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
spring.datasource.readwrite.url=jdbc:h2:mem:primary;DB_CLOSE_DELAY=-1
spring.datasource.readwrite.username=sa
spring.datasource.readwrite.password=
spring.datasource.readwrite.driverClassName=org.h2.Driver

spring.datasource.readonly.url=jdbc:h2:mem:replica;DB_CLOSE_DELAY=-1
spring.datasource.readonly.username=sa
spring.datasource.readonly.password=
spring.datasource.readonly.driverClassName=org.h2.Driver

spring.jpa.hibernate.ddl-auto=none
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS order_table (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
description VARCHAR(255)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.baeldung.rwrouting;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@ActiveProfiles("rwrouting")
@SpringBootTest(classes = RwRoutingApplication.class)
class TransactionRoutingIntegrationTest {

@Autowired
OrderService orderService;

@Test
void whenSaveAndReadWithReadWrite_thenFindsOrder() {
Order saved = orderService.save(new Order("laptop"));

List<Order> result = orderService.findAllReadWrite();

assertThat(result).anyMatch(o -> o.getId()
.equals(saved.getId()));
}

@Test
void whenSaveAndReadWithReadOnly_thenOrderNotFound() {
Order saved = orderService.save(new Order("keyboard"));

List<Order> result = orderService.findAllReadOnly();

assertThat(result).noneMatch(o -> o.getId()
.equals(saved.getId()));
}
}