Published 12/13/2018 02:30 PM
Updated 05/16/2019 10:27 AM
A unique ask came across my inbox: Take an existing application and move it to a training environment. Here’s where the unique part comes in. Each user logging in to the application should have its own set of data. That data should be initialized to a clean and known state, and the user’s actions shouldn’t interfere with anyone else using the application.
We started scratching our heads, and a few ideas started to form.
My initial thought was to create our own DataSource. I planned to grab the SecurityContext within the DataSource and return the actual DataSource that corresponded with that user. Then a colleague of mine pointed me to this fantastic abstract class, AbstractRoutingDataSource.
This class provides a method-protected Object determineCurrentLookupKey(), which returns a key to a map that contains the appropriate DataSource object. This way, you can configure the DataSources in your XML configuration and look them up by the value returned.
This approach is a great solution to the multitenant problem. That’s to say you have a single application but multiple businesses/users/tenants, each requiring their own data set while using the same business logic. I wanted to go one step further, however. I wanted to be able to create DataSources at runtime, initialize the schema and data, and return those.
In this contrite example, we’re using hypersonic in-memory databases, generating a huge memory leak, but it shows the concept. After a couple of overridden methods, we had a solution.
First, we had to override the protected DataSource determineTargetDataSource() method because the AbstractRoutingDataSource keeps its targetDataSources private and makes a copy of it in afterPropertiesSet. Next, we needed to override afterPropertiesSet to set our instance of the targetDataSources map to that of the super class. Finally, we implemented the protected Object determineCurrentLookupKey() method.
We borrowed from Spring’s pet shop example, with some simplification of the DataSources, but in the end, we had a new DataSource with a new hypersonic database instance being created for every request.