MyBatis turned our to be very reliable and not as painful as I thought when it came to creating the mappers.
As a dependency injection we decided to use Guice, and the integration between the two proved to be seamless. The only problems started showing up when we created some integrations tests to check whether the Transactional annotations were working. The bugs were many, and hard to debug, so I decided to create a dummy project to test how the Transactional annotation works once and for all.
In our project, we are using different databases in the application so we have to use private modules, which introduces a whole bunch of caveats, but before digging into it, let's start with what the internet says about this.
The Transactional annotation documentation
The official Guice wiki doesn't say much about what affect the Transactional annotation, only that:
For these tests, I created a simple environment: a MyBatis mapper that doesn't really do anything, a service implementing an interface in which I inject the mapper. I also inject the SqlSessionManager in the service to check whether the method is being executed inside a transaction by calling sqlSessionManager.isManagedSessionStarted().Fair enough. MyBatis-Guice's documentation ads some more informations about nested transactions, but still doesn't mention issues with private modules. The documentation is very poor for such an important piece of the libraryThe only restriction is that these methods must be on objects that were created by Guice and they must not beprivate
.
Setting up the environment
This is the mapper:
This is the service interface:
The tests
The tests simply create an injector, get an instance of the service and call addValue on it. The method will throw an AssertionError if a transaction was not started, indicating that the Transactional annotation was ignored.
Let's ignore the interface for now and focus on the DummyService implementation.
For the first test I just create a normal MyBatis module and bind the DummyMapper. This test passes.
Now, let's try to run a public method calling a private method annotated with Transactional. As the Guice documentation mentioned, this test fails.
Let's add private modules to the picture. I will bind the mapper and expose the mapper in a private service and then try to get an instance of the DummyService. This doesn't work, as the module has to know about the service for the Transactional annotation to work.
To make it work, you need to expose the DummyService explicitely:
Now let's see what happens with interfaces. Let's try binding and exposing the interface instead of the implementation directly, and see if the private module method still works (it does)
So far, everything is working as expected (more or less), but then something weird happened. I had a bug I was trying to fix and I was growing desperate, I tried to explicitely expose Mapper and SqlSessionManager on top of the DummyService, to get closer to our product. I thought it wouldn't make a difference but it did, and this test fails:
Even stranger, it doesn't fail if I bind and expose the implementation directly:
I probably stumbled upon a weird bug, I will update this post if something comes up, but the documentation on this topic is seriously lacking.
This test, farther away from our implementation, reproduces the issue more clearly.
Update
The MyBatis/Guice team promptly got back to me, apparently it's a known Guice issue, but it's "expected behavior". Pretty much what's happening is that Guice is instantiating the service in the private module's parent, and the annotations defined in the private module (including Transactional) are ignored. Suggested workarounds: bind instances directly when they use the Transactional annotation, or use requireExplicitBindings()