-
1. Re: How to share transactions between multiple threads
zhurlik Feb 18, 2019 7:03 AM (in response to vata2999)Hi,
I guess that UserTransaction should be injected or added as JNDI resource via look-up.
Have you read
- Getting Started Developing Applications Guide
- Chapter 11. Java Transaction API (JTA) - Red Hat Customer Portal
Thanks,
Vlad
-
2. Re: How to share transactions between multiple threads
ochaloup Feb 18, 2019 7:18 AM (in response to vata2999)Hi vata2999 ,
it depends on what you want to achieve. Do the two methods (starttx and the account) run on two different JVMs? Do I understand correctly you want to start a JTA transaction on one JVM (let's say a microservice) and you want the other JVM would join the same transaction? The JTA transactions are associated with one thread in one JVM. If you want to get multiple JVMs to run with one transaction you need to propagate context over the remote call. In this case you expect the transaction context is propagated over HTTP REST calls. Narayana provides for the propagation the subproject REST-AT (atomic transactions for REST) and you need to configure it to your project for propagation to start working. Please take a look at the Narayana quickstart quickstart/rts/at at master · jbosstm/quickstart · GitHub for this purpose.
-
3. Re: How to share transactions between multiple threads
tomjenkinson Feb 18, 2019 7:18 AM (in response to vata2999)It's not totally clear to me if you are wanting to use our REST-AT coordinator. If so then you might like to look at quickstart/rts/at/jta-service at master · jbosstm/quickstart · GitHub (look at the src/main and src/test for the demarcation)
However if you just wanting to somehow manage the transaction yourself from a REST resource you would need to handle the suspend/resume yourself perhaps in a filter maybe keyed on some ID for a hashmap you could require your clients to pass you back.
-
4. Re: How to share transactions between multiple threads
vata2999 Feb 18, 2019 9:33 PM (in response to tomjenkinson)Hi ,
Thank you for your reply tomjenkinson, ochaloup, zhurlik.
What I want to acheive :
In terms of code :
@GetMapping("/gateway-api") @Transactional(rollbackFor = RuntimeException.class) public String getApi() { //create an account, get id String accountId = restTemplate.getForObject("http://localhost:8686/account", String.class); //create a payment for above account restTemplate.getForObject("http://localhost:8585/payment?accid="+accountId, String.class); //rollback everything throw new RuntimeException("rollback everything"); }
What I've done so far :
1. created a standalone JTA server based on quickstart/TestCase.java at master · jbosstm/quickstart · GitHub
2. connected each of microservices to it with JNDI
@Bean public TransactionManager tm() throws NamingException { TransactionManager tm = (TransactionManager) new InitialContext(getJbossPropsJndi()).lookup("java:/TransactionManager"); return tm; } @Bean public UserTransaction utm() throws NamingException { UserTransaction userTransaction = (UserTransaction) new InitialContext(getJbossPropsJndi()).lookup("java:/UserTransaction"); return userTransaction; } @Bean("transactionManager") @Primary public PlatformTransactionManager ptf() throws NamingException { JtaTransactionManager transactionManager = new JtaTransactionManager(); // transactionManager.setEntityManagerFactory(emf); transactionManager.setUserTransaction(utm()); transactionManager.setTransactionManager(tm()); transactionManager.setGlobalRollbackOnParticipationFailure(true); return transactionManager; } private Properties getJbossPropsJndi() { Properties props = new Properties(); props.put(Context.PROVIDER_URL, "jnp://127.0.0.1:1099"); props.put("jndi.java.naming.factory.url", "org.jboss.naming:org.jnp.interfaces"); props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); return props; }
yet I am not getting the desired functonality.
PS : I am not using RESTAT I want to simulate it to see if it works .RESTAT code is way to complicated without a proper documentation.
-
5. Re: How to share transactions between multiple threads
zhfeng Feb 19, 2019 2:23 AM (in response to vata2999)As far as you are using the RestTemplate to invoke the remote REST services and I think you have to propagate the transaction context by some interceptors.
-
6. Re: How to share transactions between multiple threads
zhurlik Feb 19, 2019 4:09 AM (in response to vata2999)Hmm...
Are you using distributed JTA transactions across multiple XA resources? -
7. Re: How to share transactions between multiple threads
vata2999 Feb 19, 2019 5:30 AM (in response to zhurlik)zhurlik wrote:
Hmm...
Are you using distributed JTA transactions across multiple XA resources?What makes JTA transactions distributed across multiple xa resources ?
I am using XA resources with JTA transactions for each one of microservices and since it is handled by jndi in single nirayana instance I guess it should be as It is depicted.
-
8. Re: How to share transactions between multiple threads
tomjenkinson Feb 19, 2019 5:58 AM (in response to vata2999)1 of 1 people found this helpfulThanks for the details vata2999. If you are not going to use the REST-AT integration then you must take care to asssociate and diassociate the transaction with the thread yourself and note that the payment and account services will either need to be in the same JVM or use a distributed transaction protocol like JTS.
If the payment and account services can be in the same JVM you need something like:
@GetMapping("/starttx") public byte[] starttx() throws NotSupportedException, SystemException { TransactionManager transactionManager = com.arjuna.ats.jta.TransactionManager.transactionManager(); transactionManager.begin(); Transaction transaction = transactionManager.suspend(); // stash this transaction some place related to a globally unique ID, you could use the Narayana UID byte[] bytes = ((com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple)transaction).get_uid().getBytes(); transactions.put(bytes, transaction); return bytes; } @GetMapping("/createAccount") public String account(byte[] transactionUID) throws SystemException { Transaction transaction = transactions.get(transactionUID); tm.resume(transaction); em.persist(new AccountEntity("acc1")); return "created account"; } @GetMapping("/committx") public byte[] committx(byte[] transactionUID) { Transaction transaction = transactions.get(transactionUID); tm.resume(transaction); transaction.commit(); }
-
9. Re: How to share transactions between multiple threads
vata2999 Feb 19, 2019 11:52 PM (in response to tomjenkinson)Hi tomjenkinson,
Wow That was awesome. just few drawbacks :
after a round trip
1.starttx
2.create an account
3. committx
I call starttx for 5 consecutives times I get this exception sometimes :
ARJUNA016051: thread is already associated with a transaction!
What can I do about that ?
Transaction reaper :
For How long a transaction can be open ? Can I change the interval ?
I get this
2019-02-20 12:46:11.836 WARN 18852 --- [nsaction Reaper] com.arjuna.ats.arjuna : ARJUNA012117: TransactionReaper::check timeout for TX 0:ffffc0a800de:f594:5c6cdb01:11d in state RUN
2019-02-20 12:46:11.837 WARN 18852 --- [Reaper Worker 0] com.arjuna.ats.arjuna : ARJUNA012121: TransactionReaper::doCancellations worker Thread[Transaction Reaper Worker 0,5,main] successfully canceled TX 0:ffffc0a800de:f594:5c6cdb01:11d
-
10. Re: How to share transactions between multiple threads
ochaloup Feb 20, 2019 4:09 AM (in response to vata2999)1 of 1 people found this helpfulHi vata2999 ,
if you call the `transactionManager.begin()` a new transaction is tried to be bound to the current thread (it would be the ThreadLocal variable). If you want to start the transaction you can check the state TransactionManager (Java(TM) EE 8 Specification APIs) and based on that to decide to start the new transaction with `begin()` or just use the already existing one (TransactionManager (Java(TM) EE 8 Specification APIs) ).
On the reaper. Reaper manages transaction timeout. If the timeout elapses the reaper ensures the transaction is rolled-back. You can define the timeout of transaction with API call - TransactionManager (Java(TM) EE 8 Specification APIs) (you need to do so before the transaction starts aka. before you call `begin()`), you can define default timeout for started transactions by narayana configuration call (`arjPropertyManager.getCoordinatorEnvironmentBean().setDefaultTimeout(...)`). But the Narayana configuration should be set up rather by the descriptor. You can find more details on configuration of narayana in the documentation Narayana Project Documentation or in this section of the blog post Narayana team blog: Narayana periodic recovery of XA transactions .
-
11. Re: How to share transactions between multiple threads
tomjenkinson Feb 20, 2019 5:09 AM (in response to vata2999)Sorry, I missed a tm.suspend in the account method.
public String account(byte[] transactionUID) throws SystemException { Transaction transaction = transactions.get(transactionUID); tm.resume(transaction); em.persist(new AccountEntity("acc1")); tm.suspend(); // no need to cache as it will already be in the transactions map from starttx return "created account"; }
I see Ondra has given some good background on how to setTransactionTimeout.