MyBatis and OSGi
Raising MyBatis
Few people assume what difficulties are confronting us on the path of introducing familiar technologies into new systems. One of the not obvious difficulties is to make MyBatis friends with OSGi components. The most extraordinary difficulty is to hide your classes in the private part of the system. We don’t want to put our objects out. We hide our SIM card and microSD card in the phone case. Yes, we know that there are these things, but we don’t want to show anyone. The same with objects inside the OSGi component (bundle).
So, MyBatis, being a third-party library, cannot reach private objects. And we so want to close our secrets from everyone. Already the hands itch and the chair creaks with impatience.
For normal operation, we need to learn how to cook MyBatis in parts. To begin with, the configuration is best configured in Java code, rather than in familiar XML. Since we have OSGi, then access to the database is at the level of the OSGi service that implements the DataSource interface.
No one will tell you how to configure MyBatis to work with distributed transactions. They simply are not at the level of MyBatis Framework. I have to use third-party systems. One thing we know for sure is that in this case you need to specify a transaction manager - ManagedTransactionFactory. For local transactions, select JdbcTransactionFactory.
Then the fun part begins - these are classes superimposed on query parameters and results. We are just hiding these classes in the private part of OSGi components. Only one solution works here - assign an alias to each class. In this case, MyBatis will create a map of the classes. The keys will be the class alias, and the value will be the class. The configuration object, being created in the local loader, has normal access to the class from this map. Dynamically creating an object of the desired class does not cause problems.
If we forget to assign an alias and specify the full path of the class to be raised in xml, MyBatis will not be able to raise this class. The object raising factory was created at the component level of MyBatis (bundle) and does not have access to the class loader of our OSGi component. MyBatis banally scolds us with an error at the stage of reading the results of executing an SQL query.
It is very convenient to use mapper interfaces in MyBatis. Here you can go in two ways in storing SQL queries. You can store the request in the XML file of the same name located in the same package where the interface lives. You can place the request in annotations of the called method. In the case of annotations, class aliases are optional. MyBatis, processing annotations, will do this work on its own. Oh, yes, you can make a complete mess of XML files and annotations. In XML format, there is a beautiful description of resultMap, and in annotations it corresponds to a complete mess in several methods. But in the annotation you can specify the name resultMap from the XML description. Also in the XML description there is an interesting functionality - import of parts of SQL queries. It looks very nice when methods get the same objects and differ only in query parameters.
Yes, MyBatis documentation recommends registering all XML request files in the configuration. I would say that we need to register not XML, but interfaces (mapper). The system will take an XML file of the same name next to the interface. But, as practice shows, this work can be postponed until better times. Let's just say that there is a chance that some requests will be executed rarely enough, that the procedure for raising these requests will be just an extra waste of time and memory.
Performance
Here we smoothly move on to performance. Performance is not in terms of increasing database server performance. Performance in terms of storage and maintenance costs of facilities and requests.
The simpler the objects passed in the parameters and the generated objects in the queries, the faster the system picks them up. As was said, the process of raising can be postponed until better times. Thus, reducing the initial setup time of MyBatis. Moreover, the system does not spend much effort on parsing the structure of classes in the parameters and results of queries. It is enough to register a class once and the system will use a ready-made class decomposition map. The org.apache.ibatis.reflection.Reflector class is responsible for this. I hope that soon we will see a slightly different construction - org.apache.ibatis.reflection.ReflectorFactory.
Why is the ReflectorFactory innovation made?
As mentioned above, MyBatis once remembers the structure of the object. Well, if the objects are relatively few. If there are a lot of objects, well, say, a few dozen megabytes, and they are rarely used. In this case, system performance becomes its weak point. Nobody voluntarily cleans the memory of unnecessary objects. As a result, the JVM stores structures of unused classes in a special section of Java 7 permgen. ReflectorFactory will be used to clean this small piece of memory. At the moment, the loss of MyBatis configuration (Config) leads to the release of resources such as memory for storing SQL queries, mapping interfaces. MyBatis, forgetting about the configuration, also forgets about the ReflectorFactory, thereby destroying the structure of the classes used.
What is the advantage of MyBatis from JPA or JDO implementations?
The main advantage is the ability to dynamically bind a large number of objects to the database and the ability to forget these objects. Well, honestly. We know that JPA scans all classes for the presence of JPA Entity annotations. It is wonderful. What's next? Nothing further. There is no information on how the JPA will forget about these classes.
The author at JDO DataNucleuse did implement dynamic class lifting in OSGi. A hint was made that the system does not unload objects when the OSGi component stops. Years passed, and unloading of registered objects did not appear. I think similar problems are present in other JPA implementations.
Distributed Transactions
The second topic covered in this article is distributed transactions. MyBatis does not have a distributed transaction support implementation.
Relatively recently, a very interesting and useful object has appeared in MyBatis - SqlSessionManager. It allows you to use a single session to the database in one thread. Sessions in different threads do not overlap. The process of opening and closing a session is easily ported to annotation processors. This is used almost everywhere, in libraries based on Spring, CDI, Guice. Everywhere you can find transaction management annotations. And you can even see support for distributed transactions.
But there is one interesting trick in distributed transactions. This is an opportunity to launch a new transaction within an existing transaction. Its name is "Required New." What is happening with MyBatis? Everything is simple and sad. Working in the same thread, the SqlSessionManager object returns a previously opened connection. That is, when opening a new transaction, we actually take away the old connection in a foreign transaction.
The abnormal completion of a nested transaction will not lead to a rollback of actions, since all the changes went as part of an external transaction. Accepting a nested transaction and rolling back an external transaction will result in the loss of changes that were planned to be made in the nested transaction.
The mybatis-guice project proposed a solution to such a delicate problem. A special XAResource is generated for the SqlSessionManager object. The transaction controller, TransactionManager, manages XA-registered resources. Suspending an XA transaction causes the session information to switch to the SqlSessionManager.
Why did this appear in mybatis-guice?
Again, OSGi is to blame. OSGi has its own specification, OSGi Blueprint. It is very similar to the Spring Framework. They had one ancestor. For a number of reasons, the two Framework conflict. Either the first or the second. No sladu. CDI Weld is a chip for web applications. So the developers of Weld tried that they prescribed this useful solution exclusively for the web. So you have to look for such a solution that does not interfere with living among noisy and not very friendly neighbors. Guice is a system that gets along well within several configurations that do not interfere with each other.
Let's just say that two configurations easily live in one component. One for working with local Jdbc transactions and the second for working with distributed JTA transactions. The sets of classes are the same, there are no unnecessary annotations and xml descriptions. Even MyBatis configuration happens with the same method in the class.
I hope that soon this functionality will appear in the new version of mybatis-guice.