Merge pull request #5166 from bungrudi/BAEL-2081---Guide-to-JavaEE-JTA
Bael 2081 - Guide to Java EE JTA
This commit is contained in:
commit
9ca21b109d
88
jta/pom.xml
Normal file
88
jta/pom.xml
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<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/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>jta-demo</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<description>JEE JTA demo</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.0.4.RELEASE</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-jta-bitronix</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hsqldb</groupId>
|
||||||
|
<artifactId>hsqldb</artifactId>
|
||||||
|
<version>2.4.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>autoconfiguration</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>integration-test</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>test</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/*LiveTest.java</exclude>
|
||||||
|
<exclude>**/*IntegrationTest.java</exclude>
|
||||||
|
<exclude>**/*IntTest.java</exclude>
|
||||||
|
</excludes>
|
||||||
|
<includes>
|
||||||
|
<include>**/AutoconfigurationTest.java</include>
|
||||||
|
</includes>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<test.mime>json</test.mime>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.baeldung.jtademo;
|
||||||
|
|
||||||
|
import org.hsqldb.jdbc.pool.JDBCXADataSource;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.jta.bitronix.BitronixXADataSourceWrapper;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
@EnableTransactionManagement
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan
|
||||||
|
public class JtaDemoApplication {
|
||||||
|
|
||||||
|
@Bean("dataSourceAccount")
|
||||||
|
public DataSource dataSource() throws Exception {
|
||||||
|
return createHsqlXADatasource("jdbc:hsqldb:mem:accountDb");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("dataSourceAudit")
|
||||||
|
public DataSource dataSourceAudit() throws Exception {
|
||||||
|
return createHsqlXADatasource("jdbc:hsqldb:mem:auditDb");
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataSource createHsqlXADatasource(String connectionUrl) throws Exception {
|
||||||
|
JDBCXADataSource dataSource = new JDBCXADataSource();
|
||||||
|
dataSource.setUrl(connectionUrl);
|
||||||
|
dataSource.setUser("sa");
|
||||||
|
BitronixXADataSourceWrapper wrapper = new BitronixXADataSourceWrapper();
|
||||||
|
return wrapper.wrapDataSource(dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("jdbcTemplateAccount")
|
||||||
|
public JdbcTemplate jdbcTemplate(@Qualifier("dataSourceAccount") DataSource dataSource) {
|
||||||
|
return new JdbcTemplate(dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("jdbcTemplateAudit")
|
||||||
|
public JdbcTemplate jdbcTemplateAudit(@Qualifier("dataSourceAudit") DataSource dataSource) {
|
||||||
|
return new JdbcTemplate(dataSource);
|
||||||
|
}
|
||||||
|
}
|
27
jta/src/main/java/com/baeldung/jtademo/dto/TransferLog.java
Normal file
27
jta/src/main/java/com/baeldung/jtademo/dto/TransferLog.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package com.baeldung.jtademo.dto;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
public class TransferLog {
|
||||||
|
private String fromAccountId;
|
||||||
|
private String toAccountId;
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
public TransferLog(String fromAccountId, String toAccountId, BigDecimal amount) {
|
||||||
|
this.fromAccountId = fromAccountId;
|
||||||
|
this.toAccountId = toAccountId;
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFromAccountId() {
|
||||||
|
return fromAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToAccountId() {
|
||||||
|
return toAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.baeldung.jtademo.services;
|
||||||
|
|
||||||
|
import com.baeldung.jtademo.dto.TransferLog;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class AuditService {
|
||||||
|
|
||||||
|
final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public AuditService(@Qualifier("jdbcTemplateAudit") JdbcTemplate jdbcTemplate) {
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void log(String fromAccount, String toAccount, BigDecimal amount) {
|
||||||
|
jdbcTemplate.update("insert into AUDIT_LOG(FROM_ACCOUNT, TO_ACCOUNT, AMOUNT) values ?,?,?", fromAccount, toAccount, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransferLog lastTransferLog() {
|
||||||
|
return jdbcTemplate.query("select FROM_ACCOUNT,TO_ACCOUNT,AMOUNT from AUDIT_LOG order by ID desc", (ResultSetExtractor<TransferLog>) (rs) -> {
|
||||||
|
if (!rs.next())
|
||||||
|
return null;
|
||||||
|
return new TransferLog(rs.getString(1), rs.getString(2), BigDecimal.valueOf(rs.getDouble(3)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.baeldung.jtademo.services;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class BankAccountService {
|
||||||
|
|
||||||
|
final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public BankAccountService(@Qualifier("jdbcTemplateAccount") JdbcTemplate jdbcTemplate) {
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transfer(String fromAccountId, String toAccountId, BigDecimal amount) {
|
||||||
|
jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE-? where ID=?", amount, fromAccountId);
|
||||||
|
jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE+? where ID=?", amount, toAccountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal balanceOf(String accountId) {
|
||||||
|
return jdbcTemplate.query("select BALANCE from ACCOUNT where ID=?", new Object[] { accountId }, (ResultSetExtractor<BigDecimal>) (rs) -> {
|
||||||
|
rs.next();
|
||||||
|
return new BigDecimal(rs.getDouble(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.baeldung.jtademo.services;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
import javax.transaction.UserTransaction;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TellerService {
|
||||||
|
private final BankAccountService bankAccountService;
|
||||||
|
private final AuditService auditService;
|
||||||
|
private final UserTransaction userTransaction;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TellerService(BankAccountService bankAccountService, AuditService auditService, UserTransaction userTransaction) {
|
||||||
|
this.bankAccountService = bankAccountService;
|
||||||
|
this.auditService = auditService;
|
||||||
|
this.userTransaction = userTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void executeTransfer(String fromAccontId, String toAccountId, BigDecimal amount) {
|
||||||
|
bankAccountService.transfer(fromAccontId, toAccountId, amount);
|
||||||
|
auditService.log(fromAccontId, toAccountId, amount);
|
||||||
|
BigDecimal balance = bankAccountService.balanceOf(fromAccontId);
|
||||||
|
if (balance.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new RuntimeException("Insufficient fund.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeTransferProgrammaticTx(String fromAccontId, String toAccountId, BigDecimal amount) throws Exception {
|
||||||
|
userTransaction.begin();
|
||||||
|
bankAccountService.transfer(fromAccontId, toAccountId, amount);
|
||||||
|
auditService.log(fromAccontId, toAccountId, amount);
|
||||||
|
BigDecimal balance = bankAccountService.balanceOf(fromAccontId);
|
||||||
|
if (balance.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
userTransaction.rollback();
|
||||||
|
throw new RuntimeException("Insufficient fund.");
|
||||||
|
} else {
|
||||||
|
userTransaction.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.baeldung.jtademo.services;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.datasource.init.ScriptUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TestHelper {
|
||||||
|
final JdbcTemplate jdbcTemplateAccount;
|
||||||
|
|
||||||
|
final JdbcTemplate jdbcTemplateAudit;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TestHelper(@Qualifier("jdbcTemplateAccount") JdbcTemplate jdbcTemplateAccount, @Qualifier("jdbcTemplateAudit") JdbcTemplate jdbcTemplateAudit) {
|
||||||
|
this.jdbcTemplateAccount = jdbcTemplateAccount;
|
||||||
|
this.jdbcTemplateAudit = jdbcTemplateAudit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runAccountDbInit() throws SQLException {
|
||||||
|
runScript("account.sql", jdbcTemplateAccount.getDataSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runAuditDbInit() throws SQLException {
|
||||||
|
runScript("audit.sql", jdbcTemplateAudit.getDataSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runScript(String scriptName, DataSource dataSouorce) throws SQLException {
|
||||||
|
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||||
|
Resource script = resourceLoader.getResource(scriptName);
|
||||||
|
try (Connection con = dataSouorce.getConnection()) {
|
||||||
|
ScriptUtils.executeSqlScript(con, script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
jta/src/main/resources/account.sql
Normal file
9
jta/src/main/resources/account.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
DROP SCHEMA PUBLIC CASCADE;
|
||||||
|
|
||||||
|
create table ACCOUNT (
|
||||||
|
ID char(8) PRIMARY KEY,
|
||||||
|
BALANCE NUMERIC(28,10)
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into ACCOUNT(ID, BALANCE) values ('a0000001', 1000);
|
||||||
|
insert into ACCOUNT(ID, BALANCE) values ('a0000002', 2000);
|
0
jta/src/main/resources/application.properties
Normal file
0
jta/src/main/resources/application.properties
Normal file
8
jta/src/main/resources/audit.sql
Normal file
8
jta/src/main/resources/audit.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
DROP SCHEMA PUBLIC CASCADE;
|
||||||
|
|
||||||
|
create table AUDIT_LOG (
|
||||||
|
ID INTEGER IDENTITY PRIMARY KEY,
|
||||||
|
FROM_ACCOUNT varchar(8),
|
||||||
|
TO_ACCOUNT varchar(8),
|
||||||
|
AMOUNT numeric(28,10)
|
||||||
|
);
|
91
jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java
Normal file
91
jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package com.baeldung.jtademo;
|
||||||
|
|
||||||
|
import com.baeldung.jtademo.dto.TransferLog;
|
||||||
|
import com.baeldung.jtademo.services.AuditService;
|
||||||
|
import com.baeldung.jtademo.services.BankAccountService;
|
||||||
|
import com.baeldung.jtademo.services.TellerService;
|
||||||
|
import com.baeldung.jtademo.services.TestHelper;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest(classes = JtaDemoApplication.class)
|
||||||
|
public class JtaDemoUnitTest {
|
||||||
|
@Autowired
|
||||||
|
TestHelper testHelper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
TellerService tellerService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
BankAccountService accountService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
AuditService auditService;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeTest() throws Exception {
|
||||||
|
testHelper.runAuditDbInit();
|
||||||
|
testHelper.runAccountDbInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenAnnotationTx_whenNoException_thenAllCommitted() throws Exception {
|
||||||
|
tellerService.executeTransfer("a0000001", "a0000002", BigDecimal.valueOf(500));
|
||||||
|
|
||||||
|
assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(500));
|
||||||
|
assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2500));
|
||||||
|
|
||||||
|
TransferLog lastTransferLog = auditService.lastTransferLog();
|
||||||
|
assertThat(lastTransferLog).isNotNull();
|
||||||
|
assertThat(lastTransferLog.getFromAccountId()).isEqualTo("a0000001");
|
||||||
|
assertThat(lastTransferLog.getToAccountId()).isEqualTo("a0000002");
|
||||||
|
assertThat(lastTransferLog.getAmount()).isEqualByComparingTo(BigDecimal.valueOf(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenAnnotationTx_whenException_thenAllRolledBack() throws Exception {
|
||||||
|
assertThatThrownBy(() -> {
|
||||||
|
tellerService.executeTransfer("a0000002", "a0000001", BigDecimal.valueOf(100000));
|
||||||
|
}).hasMessage("Insufficient fund.");
|
||||||
|
|
||||||
|
assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(1000));
|
||||||
|
assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2000));
|
||||||
|
assertThat(auditService.lastTransferLog()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenProgrammaticTx_whenCommit_thenAllCommitted() throws Exception {
|
||||||
|
tellerService.executeTransferProgrammaticTx("a0000001", "a0000002", BigDecimal.valueOf(500));
|
||||||
|
|
||||||
|
BigDecimal result = accountService.balanceOf("a0000001");
|
||||||
|
assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(500));
|
||||||
|
assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2500));
|
||||||
|
|
||||||
|
TransferLog lastTransferLog = auditService.lastTransferLog();
|
||||||
|
assertThat(lastTransferLog).isNotNull();
|
||||||
|
assertThat(lastTransferLog.getFromAccountId()).isEqualTo("a0000001");
|
||||||
|
assertThat(lastTransferLog.getToAccountId()).isEqualTo("a0000002");
|
||||||
|
assertThat(lastTransferLog.getAmount()).isEqualByComparingTo(BigDecimal.valueOf(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenProgrammaticTx_whenRollback_thenAllRolledBack() throws Exception {
|
||||||
|
assertThatThrownBy(() -> {
|
||||||
|
tellerService.executeTransferProgrammaticTx("a0000002", "a0000001", BigDecimal.valueOf(100000));
|
||||||
|
}).hasMessage("Insufficient fund.");
|
||||||
|
|
||||||
|
assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(1000));
|
||||||
|
assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2000));
|
||||||
|
assertThat(auditService.lastTransferLog()).isNull();
|
||||||
|
}
|
||||||
|
}
|
1
pom.xml
1
pom.xml
@ -602,6 +602,7 @@
|
|||||||
<module>spring-reactive-kotlin</module>
|
<module>spring-reactive-kotlin</module>
|
||||||
<module>jnosql</module>
|
<module>jnosql</module>
|
||||||
<module>spring-boot-angular-ecommerce</module>
|
<module>spring-boot-angular-ecommerce</module>
|
||||||
|
<module>jta</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
</profile>
|
</profile>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user