This is an automated email from the ASF dual-hosted git repository. namelchev pushed a commit to branch ignite-spring-tx-ext-1.0.0 in repository https://gitbox.apache.org/repos/asf/ignite-extensions.git
commit aa22e7ec8af60a195d0bdb3695272409a7a456bd Author: Mikhail Petrov <[email protected]> AuthorDate: Tue Mar 30 09:41:03 2021 +0300 IGNITE-14434 Adds examples of using thin client with spring-tx-ext. (#50) (cherry picked from commit ae950fd4735173f122758e045611034747b4056b) --- .../examples/IgniteClientTransactionalService.java | 135 +++++++++++++++++++++ .../spring/examples/SpringTransactionExample.java | 128 +++++++++++++++++++ modules/spring-tx-ext/pom.xml | 48 ++++++++ 3 files changed, 311 insertions(+) diff --git a/modules/spring-tx-ext/examples/main/java/org/apache/ignite/transactions/spring/examples/IgniteClientTransactionalService.java b/modules/spring-tx-ext/examples/main/java/org/apache/ignite/transactions/spring/examples/IgniteClientTransactionalService.java new file mode 100644 index 0000000..1d37301 --- /dev/null +++ b/modules/spring-tx-ext/examples/main/java/org/apache/ignite/transactions/spring/examples/IgniteClientTransactionalService.java @@ -0,0 +1,135 @@ +/* + * 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 org.apache.ignite.transactions.spring.examples; + +import java.io.Serializable; +import java.util.Objects; +import org.apache.ignite.client.ClientCache; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.springframework.transaction.annotation.Isolation.READ_COMMITTED; +import static org.springframework.transaction.annotation.Isolation.REPEATABLE_READ; + +/** + * Represents Spring Service that uses Ignite thin client to access Ignite cluster and perform cache transactional + * operations. + */ +@Service +public class IgniteClientTransactionalService { + /** Ignite cache representation that uses thin client to communicate with Ignite cluster. */ + private ClientCache<String, Account> cache; + + /** + * The emitters transfer the specified funds to the broker. When both funds are received, they are transferred to + * the recipient, excluding the fee which the broker keeps for himself. If an error occurs at any step of this + * operation, it is rolled back. + */ + @Transactional(isolation = REPEATABLE_READ) + public void transferFundsWithBroker( + String firstEmitter, + String secondEmitter, + String recipient, + String broker, + int funds, + int fee + ) { + transferFunds(firstEmitter, broker, funds); + + transferFunds(secondEmitter, broker, funds); + + transferFunds(broker, recipient, funds * 2 - fee); + } + + /** Transfers funds between two accounts that belong to users with the specified names. */ + @Transactional(isolation = REPEATABLE_READ) + public void transferFunds(String emitter, String recipient, int funds) { + Account emitterAcc = cache.get(emitter); + Account recipientAcc = cache.get(recipient); + + if (emitterAcc.balance < funds) + throw new RuntimeException("Insufficient funds in " + emitter + "'s account"); + + emitterAcc.balance -= funds; + recipientAcc.balance += funds; + + saveAccount(emitterAcc); + saveAccount(recipientAcc); + + System.out.println(">>> " + emitter + " transfers " + funds + " coins to " + recipient); + } + + /** Gets current balance of the account with the specified name.*/ + @Transactional(isolation = READ_COMMITTED) + public int getBalance(String login) { + return cache.get(login).balance; + } + + /** Creates account with the specified user login and balance. */ + public Account createAccount(String login, int balance) { + Account acc = new Account(login); + + acc.balance = balance; + + cache.put(login, acc); + + return acc; + } + + /** Sets an Ignite cache representation that uses thin client to communicate with the Ignite cluster. */ + public void setCache(ClientCache<String, Account> cache) { + this.cache = cache; + } + + /** Puts the specified account into the cache. */ + private void saveAccount(Account acc) { + cache.put(acc.login, acc); + } + + /** Represents the user account. */ + private static class Account implements Serializable { + /** Account owner login. */ + private final String login; + + /** Balance. */ + private int balance; + + /** Creates an account with the specified owner name. */ + public Account(String login) { + this.login = login; + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object other) { + if (this == other) + return true; + + if (other == null || getClass() != other.getClass()) + return false; + + Account acc = (Account)other; + + return balance == acc.balance && Objects.equals(login, acc.login); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + return Objects.hash(login, balance); + } + } +} diff --git a/modules/spring-tx-ext/examples/main/java/org/apache/ignite/transactions/spring/examples/SpringTransactionExample.java b/modules/spring-tx-ext/examples/main/java/org/apache/ignite/transactions/spring/examples/SpringTransactionExample.java new file mode 100644 index 0000000..e534334 --- /dev/null +++ b/modules/spring-tx-ext/examples/main/java/org/apache/ignite/transactions/spring/examples/SpringTransactionExample.java @@ -0,0 +1,128 @@ +/* + * 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 org.apache.ignite.transactions.spring.examples; + +import org.apache.ignite.Ignite; +import org.apache.ignite.Ignition; +import org.apache.ignite.client.ClientCacheConfiguration; +import org.apache.ignite.client.IgniteClient; +import org.apache.ignite.configuration.ClientConfiguration; +import org.apache.ignite.transactions.spring.IgniteClientSpringTransactionManager; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL; +import static org.apache.ignite.configuration.ClientConnectorConfiguration.DFLT_PORT; +import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC; + +/** Represents example of using Ignite Spring Transactions integration with the thin client. */ +public class SpringTransactionExample { + /** Ignite cache name. */ + public static final String ACCOUNT_CACHE_NAME = "example-account-cache"; + + /** */ + public static void main(String[] args) { + try ( + Ignite ignored = Ignition.start(); // Starts an Ignite cluster consisting of one server node. + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext() + ) { + ctx.register(SpringApplicationConfiguration.class); + ctx.refresh(); + + IgniteClientTransactionalService svc = ctx.getBean(IgniteClientTransactionalService.class); + + svc.createAccount("Bob", 1000); + svc.createAccount("Alice", 100); + svc.createAccount("Eve", 0); + svc.createAccount("Dave", 0); + + doFundTransferWithBroker(svc, "Bob", "Alice", "Eve", "Dave", 1000, 10); + + doFundTransferWithBroker(svc, "Bob", "Alice", "Eve", "Dave", 100, 10); + } + } + + /** Delegates funds transfer operation to {@link IgniteClientTransactionalService} and logs the result. */ + private static void doFundTransferWithBroker( + IgniteClientTransactionalService svc, + String firstEmitter, + String secondEmitter, + String recipient, + String broker, + int cash, + int fee + ) { + System.out.println("+--------------Fund transfer operation--------------+"); + + try { + svc.transferFundsWithBroker(firstEmitter, secondEmitter, recipient, broker, cash, fee); + + System.out.println(">>> Operation completed successfully"); + } + catch (RuntimeException e) { + System.out.println(">>> Operation was rolled back [error = " + e.getMessage() + ']'); + } + + System.out.println("\n>>> Account statuses:"); + + System.out.println(">>> " + firstEmitter + " balance: " + svc.getBalance(firstEmitter)); + System.out.println(">>> " + secondEmitter + " balance: " + svc.getBalance(secondEmitter)); + System.out.println(">>> " + recipient + " balance: " + svc.getBalance(recipient)); + System.out.println(">>> " + broker + " balance: " + svc.getBalance(broker)); + System.out.println("+---------------------------------------------------+"); + } + + /** Spring application configuration. */ + @Configuration + @EnableTransactionManagement + public static class SpringApplicationConfiguration { + /** + * Ignite thin client instance that will be used to both initialize + * {@link IgniteClientSpringTransactionManager} and perform transactional cache operations. + */ + @Bean + public IgniteClient igniteClient() { + return Ignition.startClient(new ClientConfiguration().setAddresses("127.0.0.1:" + DFLT_PORT)); + } + + /** Ignite implementation of the Spring Transactions manager interface. */ + @Bean + public IgniteClientSpringTransactionManager transactionManager(IgniteClient cli) { + IgniteClientSpringTransactionManager mgr = new IgniteClientSpringTransactionManager(); + + mgr.setClientInstance(cli); + mgr.setTransactionConcurrency(PESSIMISTIC); + + return mgr; + } + + /** Service instance that uses declarative transaction management when working with the Ignite cache. */ + @Bean + public IgniteClientTransactionalService transactionalService(IgniteClient cli) { + IgniteClientTransactionalService svc = new IgniteClientTransactionalService(); + + svc.setCache(cli.getOrCreateCache(new ClientCacheConfiguration() + .setName(ACCOUNT_CACHE_NAME) + .setAtomicityMode(TRANSACTIONAL))); + + return svc; + } + } +} diff --git a/modules/spring-tx-ext/pom.xml b/modules/spring-tx-ext/pom.xml index adfe9ea..6bc4195 100644 --- a/modules/spring-tx-ext/pom.xml +++ b/modules/spring-tx-ext/pom.xml @@ -88,4 +88,52 @@ </testResource> </testResources> </build> + + <profiles> + <profile> + <id>examples</id> + + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <phase>generate-sources</phase> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>examples/main/java</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-spring</artifactId> + <version>${ignite.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-core</artifactId> + <version>${ignite.version}</version> + </dependency> + + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-tx</artifactId> + <version>${spring.version}</version> + </dependency> + </dependencies> + </profile> + </profiles> </project>
