-
Notifications
You must be signed in to change notification settings - Fork 349
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add multi pooled datasources with 2PC example
- Loading branch information
1 parent
f1fcd42
commit cc1648b
Showing
12 changed files
with
657 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
transaction-logs | ||
test-transactions-logs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!-- | ||
Licensed to the Apache Software Foundation (ASF) under one or more | ||
contributor license agreements. See the NOTICE file distributed with | ||
this work for additional information regarding copyright ownership. | ||
The ASF licenses this file to You under the Apache License, Version 2.0 | ||
(the "License"); you may not use this file except in compliance with | ||
the License. You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
--> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
|
||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>org.apache.camel.springboot.example</groupId> | ||
<artifactId>examples</artifactId> | ||
<version>4.8.0-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>camel-example-spring-boot-muti-datasources-2pc</artifactId> | ||
<name>Camel SB Examples :: Multiple pooled datasources with two-phase commit</name> | ||
<description>An example showing how to work with Camel and Spring Boot using multiple pooled datasources with two-phase commit</description> | ||
|
||
<properties> | ||
<category>Database</category> | ||
|
||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||
</properties> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<!-- Camel BOM --> | ||
<dependency> | ||
<groupId>org.apache.camel.springboot</groupId> | ||
<artifactId>camel-spring-boot-bom</artifactId> | ||
<version>${project.version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
<!-- Spring Boot BOM --> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-dependencies</artifactId> | ||
<version>${spring-boot-version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
|
||
<!-- Spring Boot --> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-web</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-actuator</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-jdbc</artifactId> | ||
</dependency> | ||
|
||
<!-- Camel --> | ||
<dependency> | ||
<groupId>org.apache.camel.springboot</groupId> | ||
<artifactId>camel-spring-boot-starter</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.camel.springboot</groupId> | ||
<artifactId>camel-stream-starter</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.camel.springboot</groupId> | ||
<artifactId>camel-spring-jdbc-starter</artifactId> | ||
</dependency> | ||
|
||
<!-- Datasources --> | ||
<dependency> | ||
<groupId>dev.snowdrop</groupId> | ||
<artifactId>narayana-spring-boot-starter</artifactId> | ||
<version>3.2.0</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.agroal</groupId> | ||
<artifactId>agroal-spring-boot-starter</artifactId> | ||
<version>2.5</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.postgresql</groupId> | ||
<artifactId>postgresql</artifactId> | ||
</dependency> | ||
|
||
<!-- test --> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-test</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.camel</groupId> | ||
<artifactId>camel-test-spring-junit5</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-testcontainers</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.testcontainers</groupId> | ||
<artifactId>postgresql</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.testcontainers</groupId> | ||
<artifactId>junit-jupiter</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-maven-plugin</artifactId> | ||
<version>${spring-boot-version}</version> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>repackage</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
== Camel Spring Boot with multiple pooled datasources and 2PC | ||
|
||
This example shows how to set up multiple pooled datasources with Camel Spring Boot and have them working with two-phase commit. | ||
|
||
One of the databases has a non-unique `name` column, while the other has a unique one. When trying to insert repeated names in both databases at the same time, the second `prepare` should fail making the transaction rollback for both databases. | ||
|
||
=== Classes and resources | ||
|
||
* `MyCamelRouter`: where the camel routes are defined | ||
* `DataSourcesConfig`: where the datasources' beans are created | ||
* `MyCamelApplication`: the Spring Boot main class | ||
* `application.properties`: configuration for the datasources and others | ||
* `schema-ds1.sql` and `schema-ds2.sql`: initializers for the databases | ||
|
||
=== How to run | ||
|
||
. Run the database containers. Notice the commands have extra arguments to enable initialization and two-phase commit. | ||
+ | ||
[source,console] | ||
---- | ||
podman run --rm --name db1 -e POSTGRES_PASSWORD=password -p 5432:5432 -v ./src/main/resources/schema-ds1.sql:/docker-entrypoint-initdb.d/init.sql:Z docker.io/library/postgres:latest -c max_prepared_transactions=10 | ||
---- | ||
+ | ||
[source,console] | ||
---- | ||
podman run --rm --name db2 -e POSTGRES_PASSWORD=password -p 5433:5432 -v ./src/main/resources/schema-ds2.sql:/docker-entrypoint-initdb.d/init.sql:Z docker.io/library/postgres:latest -c max_prepared_transactions=10 | ||
---- | ||
|
||
. Then run the example using | ||
[source,console] | ||
mvn spring-boot:run | ||
|
||
. Every few seconds you can see in the logs that a new insert is created, but when it tries to insert a non-unique name then the transaction rollbacks and the names are not inserted in any of the databases. | ||
+ | ||
[source,log] | ||
---- | ||
[read #8 - Delay] info : Exchange[ExchangePattern: InOnly, BodyType: String, Body: There are 4 names in the ds2 database.] | ||
[timer://runOnce] info : Exchange[Id: 0B8F2317CCE5D8D-000000000000000D, RouteGroup: null, RouteId: route2, ExchangePattern: InOnly, Properties: {CamelAggregationStrategy={split1=UseOriginalAggregationStrategy}, CamelCorrelationId=0B8F2317CCE5D8D-0000000000000000, CamelSplitComplete=false, CamelSplitIndex=4, CamelSplitSize=6, CamelStreamCacheUnitOfWork=DefaultUnitOfWork, CamelToEndpoint=log://info?showAll=true}, Headers: {}, BodyType: String, Body: Maria] | ||
[timer://runOnce] route2 : insert into the first database (non-unique) | ||
[timer://runOnce] route2 : insert into the second database (unique) | ||
[timer://runOnce] com.arjuna.ats.jta : ARJUNA016041: prepare on < formatId=131077, gtrid_length=29, bqual_length=36, tx_uid=0:ffff0a057e34:aedb:66cc8122:39, node_name=1, branch_uid=0:ffff0a057e34:aedb:66cc8122:3f, subordinatenodename=null, eis_name=java:comp/env/jdbc/ds2 > (io.agroal.narayana.BaseXAResource@65fecc5) failed with exception XAException.XA_RBINTEGRITY | ||
... | ||
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "names_name_key" | ||
... | ||
[timer://runOnce] com.arjuna.ats.arjuna : ARJUNA012073: BasicAction.End() - prepare phase of action-id 0:ffff0a057e34:aedb:66cc8122:39 failed. | ||
[timer://runOnce] com.arjuna.ats.arjuna : ARJUNA012075: Action Aborting | ||
---- | ||
|
||
=== Cleanup | ||
|
||
. Clear Narayana transaction logs: | ||
[source,console] | ||
rm -rf transaction-logs | ||
|
||
. Stop the running containers | ||
|
||
=== Running the tests | ||
|
||
. Run the tests using | ||
[source,console] | ||
mvn clean test | ||
|
||
=== Extra details | ||
|
||
The `name` column in the second database is set up with `UNIQUE DEFERRABLE INITIALLY DEFERRED`. This configuration delays the constraint check to only be evaluated in the commit phase. Without this argument there would be an exception thrown immediately during the `INSERT` command, causing the transaction to be rolled-back immediately. This is only set up this way to make the example clearer. | ||
|
||
=== Help and contributions | ||
|
||
If you hit any problem using Camel or have some feedback, then please | ||
https://camel.apache.org/support.html[let us know]. | ||
|
||
We also love contributors, so | ||
https://camel.apache.org/contributing.html[get involved] :-) | ||
|
||
The Camel riders! |
84 changes: 84 additions & 0 deletions
84
multi-datasource-2pc/src/main/java/sample/camel/DataSourcesConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package sample.camel; | ||
|
||
import io.agroal.springframework.boot.AgroalDataSource; | ||
import io.agroal.springframework.boot.AgroalDataSourceAutoConfiguration; | ||
import io.agroal.springframework.boot.jndi.AgroalDataSourceJndiBinder; | ||
import org.jboss.tm.XAResourceRecoveryRegistry; | ||
import org.springframework.beans.BeansException; | ||
import org.springframework.beans.factory.ObjectProvider; | ||
import org.springframework.beans.factory.annotation.Qualifier; | ||
import org.springframework.beans.factory.config.BeanPostProcessor; | ||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; | ||
import org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer; | ||
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration; | ||
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.boot.sql.init.DatabaseInitializationSettings; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.jdbc.core.JdbcTemplate; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.transaction.jta.JtaTransactionManager; | ||
|
||
import javax.sql.DataSource; | ||
|
||
@Configuration | ||
public class DataSourcesConfig { | ||
|
||
// -- first data source config | ||
|
||
@Bean("ds1properties") | ||
@ConfigurationProperties("app.datasource.ds1") | ||
public DataSourceProperties firstDataSourceProperties() { | ||
return new DataSourceProperties(); | ||
} | ||
|
||
@Bean("ds1") | ||
@ConfigurationProperties("app.datasource.ds1.agroal") | ||
public AgroalDataSource firstDataSource( | ||
@Qualifier("ds1properties") DataSourceProperties properties, | ||
JtaTransactionManager jtaPlatform, | ||
XAResourceRecoveryRegistry xaResourceRecoveryRegistry, | ||
ObjectProvider<AgroalDataSourceJndiBinder> jndiBinder) { | ||
|
||
return new AgroalDataSourceAutoConfiguration(jtaPlatform, xaResourceRecoveryRegistry) | ||
.dataSource(properties, false, false, jndiBinder); | ||
} | ||
|
||
@Bean("ds1jdbc") | ||
public JdbcTemplate firstDataSourceJdbcTemplate(@Qualifier("ds1") DataSource dataSource) { | ||
return new JdbcTemplate(dataSource); | ||
} | ||
|
||
// -- second data source config | ||
|
||
@Bean("ds2properties") | ||
@ConfigurationProperties("app.datasource.ds2") | ||
public DataSourceProperties secondDataSourceProperties() { | ||
return new DataSourceProperties(); | ||
} | ||
|
||
@Bean("ds2init") | ||
@ConfigurationProperties("app.datasource.ds2.sql.init") | ||
public SqlInitializationProperties secondDataSourceInit() { | ||
return new SqlInitializationProperties(); | ||
} | ||
|
||
@Bean("ds2") | ||
@ConfigurationProperties("app.datasource.ds2.agroal") | ||
public AgroalDataSource secondDataSource( | ||
@Qualifier("ds2properties") DataSourceProperties properties, | ||
JtaTransactionManager jtaPlatform, | ||
XAResourceRecoveryRegistry xaResourceRecoveryRegistry, | ||
ObjectProvider<AgroalDataSourceJndiBinder> jndiBinder) { | ||
|
||
return new AgroalDataSourceAutoConfiguration(jtaPlatform, xaResourceRecoveryRegistry) | ||
.dataSource(properties, false, false, jndiBinder); | ||
} | ||
|
||
@Bean("ds2jdbc") | ||
public JdbcTemplate secondDataSourceJdbcTemplate(@Qualifier("ds2") DataSource dataSource) { | ||
return new JdbcTemplate(dataSource); | ||
} | ||
|
||
} |
37 changes: 37 additions & 0 deletions
37
multi-datasource-2pc/src/main/java/sample/camel/MyCamelApplication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one or more | ||
* contributor license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright ownership. | ||
* The ASF licenses this file to You under the Apache License, Version 2.0 | ||
* (the "License"); you may not use this file except in compliance with | ||
* the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package sample.camel; | ||
|
||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
||
//CHECKSTYLE:OFF | ||
/** | ||
* A sample Spring Boot application that starts the Camel routes. | ||
*/ | ||
@SpringBootApplication | ||
public class MyCamelApplication { | ||
|
||
/** | ||
* A main method to start this application. | ||
*/ | ||
public static void main(String[] args) { | ||
SpringApplication.run(MyCamelApplication.class, args); | ||
} | ||
|
||
} | ||
//CHECKSTYLE:ON |
Oops, something went wrong.