Adding Transactions to the XPages Jakarta EE Support Project
Wed Jul 20 16:03:55 EDT 2022
- Dec 14 2021 - Updating The XPages JEE Support Project To Jakarta EE 9, A Travelogue
- Dec 20 2021 - JSP and MVC Support in the XPages JEE Project
- Jan 06 2022 - Migrating a Large XPages App to Jakarta EE 9
- Jan 11 2022 - XPages Jakarta EE Support 2.2.0
- Jan 13 2022 - DQL, QueryResultsProcessor, and JNoSQL
- Jan 25 2022 - Implementing a Basic JNoSQL Driver for Domino
- Feb 07 2022 - Video Series On The XPages Jakarta EE Project
- Feb 11 2022 - JSF in the XPages Jakarta EE Support Project
- Apr 28 2022 - So Why Jakarta?
- May 25 2022 - XPages Jakarta EE 2.5.0 And The Looming Java-Version Wall
- Jul 11 2022 - Adding Concurrency to the XPages Jakarta EE Support Project
- Jul 20 2022 - Adding Transactions to the XPages Jakarta EE Support Project
- Nov 22 2022 - XPages Jakarta EE 2.9.0 and Next Steps
- Apr 20 2023 - XPages JEE 2.11.0 and the Javadoc Provider
- May 04 2023 - The Loose Roadmap for XPages Jakarta EE Support
- May 25 2023 - XPages JEE 2.12.0: JNoSQL Views and PrimeFaces Support
- Jul 21 2023 - XPages JEE 2.13.0
- Oct 27 2023 - XPages JEE 2.14.0
- Feb 16 2024 - XPages JEE 2.15.0 and Plans for JEE 10 and 11
As my work of going down the list of JEE specs is hitting dwindling returns, I decided to give a shot to implementing the Jakarta Transactions spec.
Implementation Oddities
This one's a little spicy for a couple of reasons, one of which is that it's really a codified implementation of another spec, the X/Open XA standard, which is an old standard for transaction processing. As is often the case, "old" here also means "fiddly", but fortunately it's not too bad for this need.
Another reason is that, unlike with a lot of the specs I've implemented, all of the existing implementations seem a bit too heavyweight for me to adapt. I may look around again later: I could have missed one, and eventually GlassFish's implementation may spin off. In the mean time, I wrote a from-scratch implementation for the XPages JEE project: it doesn't cover everything in the spec, and in particular doesn't support suspend/resume for transactions, but it'll work for normal cases in an NSF.
The Spec In Use
Fortunately, while implementations are generally complex (as befits the problem space), the actual spec is delightfully simple for users. There are two main things for an app developer to know about: the jakarta.transaction.UserTransaction
interface and the @jakarta.transaction.Transactional
annotation. These can be used separately or combined to add transactional behavior. For example:
1 2 3 4 5 6 7 8 9 10 | @Transactional public void createExampleDocAndPerson() { Person person = new Person(); /* do some business logic */ person = personRepository.save(person); ExampleDoc exampleDoc = new ExampleDoc(); /* do some business logic */ exampleDoc = repository.save(exampleDoc); } |
The @Transactional
annotation here means that the intent is that everything in this method either completes or none of it does. If something to do with ExampleDoc
fails, then the creation of the Person
doc shouldn't take effect, even though code was already executed to create and save it.
You can also use UserTransaction
directly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Inject private UserTransaction transaction; public void createExampleDocAndPerson() throws Exception { transaction.begin(); try { Person person = new Person(); /* do some business logic */ person = personRepository.save(person); ExampleDoc exampleDoc = new ExampleDoc(); /* do some business logic */ exampleDoc = repository.save(exampleDoc); transaction.commit(); } catch(Exception e) { transaction.rollback(); } } |
There's no realistic way to implement this in a general way, so the way the Transactions API takes shape is that specific resources - databases, namely - can opt in to transaction processing. While this won't necessarily save you from, say, sending out an errant email, it can make sure that related records are only ever updated together.
Domino Implementation
This support is fairly common among SQL databases and other established resources, and, fortunately for us, Domino's transaction support became official in 12.0.
So I modified the JNoSQL driver to hook into transactions when appropriate. When it's able to find an open transaction, it begins a native Domino transaction and then registers itself as a javax.transaction.xa.XAResource
(a standard Java SE interface) to either commit or roll back that DB transaction as appropriate.
From what I can tell, Domino's transaction support is a bit limited compared to what the spec can do. Apparently, Domino's support is based around thread-specific stacks - while this has the nice attribute of supporting nested transactions, it doesn't look to support suspending transactions or propagating them across threads.
Fortunately, the normal case will likely not need those more-arcane capabilities. Considering that few if any Domino applications currently make use of transactions at all, this should be a significant boost.
Next Steps
As I mentioned above, I'll likely take another look around for svelte implementations of the spec, since I'd be more than happy to cast my partial implementation to the wayside. While my version seems to check out in practice, I'd much sooner rely on a proven implementation from elsewhere.
Beyond that, any next steps may come in the form of supporting other databases via JPA. While JDBC drivers may hook into this as it is, I haven't tried that, and this could be a good impetus to adding JPA to the supported stack. That'll come post-2.7.0, though - this release has enough big-ticket items and should now be in a cool-off period before I call it solid and move to the next one.