In a post last august sbzoom proposed a solution to make spring-data-mongoDB multi-tenant:
"You have to make your own RepositoryFactoryBean. Here is the example from the Spring Data MongoDB Reference Docs. You would still have to implement your own MongoTemplate and delay or remove the ensureIndexes() call. But you would have to rewrite a few classes to make sure your MongoTemplate is called instead of Spring's."
Did anybody implement this or something equivalent?
There's quite a few ways to skin the cat here. It essentially all boils down to on which level you'd like to apply the tenancy.
Basics
The basic approach is to bind some kind of key identifying the customer on a per-thread basis, so that you can find out about the customer the current thread of execution deals with. This is usually achieved by populating a ThreadLocal with some authentication related information as you can usually derive the tenant from the logged in user.
Now if that's in place there's a few options of where to apply the tenant knowledge. Let me briefly outline the most common ones:
Multi-tenancy on the database level
One way to separate data for multiple clients is to have individual databases per tenant. Spring Data MongoDB's core abstraction for this is the MongoDBFactory interface. The easiest way here is to override SimpleMongoDbFactory.getDb(String name) and call the parent method with the database name e.g. enriched by the tenant prefix or the like.
Multi-tenancy on the collection level
Another option is to have tenant specific collections, e.g. through tenant pre- or postfixes. This mechanism can actually be leveraged by using the Spring Expression language (SpEl) in the #Document annotation's collectionName attribute. First, expose the tenant prefix through a Spring bean:
#Component("tenantProvider")
public class TenantProvider {
public String getTenantId() {
// … implement ThreadLocal lookup here
}
}
Then use SpEL in your domain types #Document mapping:
#Document(collectionName = "#{tenantProvider.getTenantId()}_accounts"
public class Account { … }
SpEl allows you to refer to Spring beans by name and execute methods on them. MongoTemplate (and thus the repository abstraction transitively) will use the mapping metadata of the document class and the mapping subsystem will evaluate the collectionName attribute to find out about the collection to interact with.
I had a similar approach to Oliver Gierke.
At least on database-level. https://github.com/Loki-Afro/multi-tenant-spring-mongodb
You should be able to do things like this:
MultiTenantMongoDbFactory.setDatabaseNameForCurrentThread("test");
this.personRepository.save(createPerson("Phillip", "Wirth", ChronoUnit.YEARS.between(
LocalDate.of(1992, Month.FEBRUARY, 3),
LocalDate.now())));
System.out.println("data from test: " + this.personRepository.findAll());
// okay? fine. - lets switch the database
MultiTenantMongoDbFactory.setDatabaseNameForCurrentThread("test666");
// should be empty
System.out.println("data from test666: " + this.personRepository.findAll());
for springboot 2.3.3
overriding doGetMongoDatabase helped to achieve multi tenancy
protected MongoDatabase doGetMongoDatabase(String dbName) {
}
https://github.com/jose-m-thomas/mongo_multi_tenancy_spring_boot_2_3_3
Full featured multi-tenant/tenancy with Spring Boot + MongoDB + Spring MVC with shared/global database configuration.
https://github.com/arun2pratap/multitenant-spring-mongodb
Related
I am using a multi tenant spring boot web application which reads some data from a database.
I have built schema per tenant model, implementing MultiTenantConnectionProvider and CurrentTenantIdentifierResolver to set connections based on the tenant. The tenant is resolved using a ThreadLocal variable, set in a Filter (built by extending OncePerRequestFilter).
However, I need to solve for a specific case where I do not get the tenant information at the filter. I can however, get the tenant information later while processing the request in a service, but by this time the entity manager is set (seems to be done by OpenSessionInViewFilter looking at spring sources) to use the default schema and all my queries fail because the default schema do not contain the data I need.
My question is, how do I set the entity manager to point to the tenant specific schema at service level, after the hibernate filter has already set the session? I could do whatever the filter (OpenSessionInViewFilter) is doing, something like the below:
EntityManager entityManager = entityManagerFactory.createEntityManager();
TransactionSynchronizationManager.bindResource(entityManager, new EntityManagerHolder(entityManager));
//unbind once I am done using the repositories
But I was thinking if this is the right way or if there is any other better, easier, documented way.
EntityManager entityManager = entityManagerFactory.createEntityManager();
TransactionSynchronizationManager.bindResource(entityManager, new
EntityManagerHolder(entityManager));
The above didn't work for me. However, I found a workaround - by running my task in a different thread (I just created a fixed thread pool executor and submitted my tasks) and setting the new tenant in a ThreadLocal object, I was able to achieve what I wanted. With the right CurrentTenantIdentifierResolver and MultiTenantConnectionProvider implementations, a connection to the expected tenant schema got established.
I am working in a multi-tenant environment where data can be accessed from about 10 different datasources (and entitymanagers) with a webapplication (rest) frontend.
The entitymanager to be used is depending on a URL parameter in the rest api, ex. api/orders/1/1000003.
I need to use entitymanager "1" to fetch the data. At the moment I am using a method in the repository layer where I call setDistrict(1), before creating a hibernate session and creating a query via hibernate Criteria.
All is working fine, but I am worried about the fact that the method will need to be synchronized to avoid getting data from a wrong entitymanager.
When I synchronize the repository method I am worried that the performance will be horrible..
What is the good strategy for implementing this multi-tenant access so performance is good and the correct data will be returned under heavy load as well?
Thanks for your advice.
The SessionFactory of Hibernate allows to use a tenancy behavior:
SCHEMA Correlates to the separate schema approach. It is an error to attempt to open a session without a tenant identifier using
this strategy. Additionally, a
org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider
must be specified.
DATABASE Correlates to the separate database approach. It is an error to attempt to open a session without a tenant identifier
using this strategy. Additionally, a
org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider
must be specified.
DISCRIMINATOR Correlates to the partitioned (discriminator) approach. It is an error to attempt to open a session without a tenant
identifier using this strategy. This strategy is not yet implemented
in Hibernate as of 4.0 and 4.1. Its support is planned for 5.0.
In your case I think you need SCHEMA or DATABASE and have to implement the MultiTenantConnectionProvider (source).
/**
* Simplisitc implementation for illustration purposes supporting 2 hard coded providers (pools) and leveraging
* the support class {#link org.hibernate.service.jdbc.connections.spi.AbstractMultiTenantConnectionProvider}
*/
public class MultiTenantConnectionProviderImpl extends AbstractMultiTenantConnectionProvider {
private final ConnectionProvider acmeProvider = ConnectionProviderUtils.buildConnectionProvider( "acme" );
private final ConnectionProvider jbossProvider = ConnectionProviderUtils.buildConnectionProvider( "jboss" );
#Override
protected ConnectionProvider getAnyConnectionProvider() {
return acmeProvider;
}
#Override
protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
if ( "acme".equals( tenantIdentifier ) ) {
return acmeProvider;
}
else if ( "jboss".equals( tenantIdentifier ) ) {
return jbossProvider;
}
throw new HibernateException( "Unknown tenant identifier" );
}
}
For more details see the linked documentation.
I am having spring webservice application with oracle as a database. Right now i have datasource created using weblogic server. Also using eclipse linkg JPA to do both read and write transactions(insert,Read and update). Now we want to separate dataSources for read(read) and wrtie(insert or update) transactions.
My current dataSource is as followed:
JNDI NAME : jdbc/POI_DS
URL : jdbc:oracle:thin:#localhost:1521:XE
using this, I am doing both read and write transactions.
What if i do the following:
JNDI NAME : jdbc/POI_DS_READ
URL : jdbc:oracle:thin:#localhost:1521:XE
JNDI NAME : jdbc/POI_DS_WRITE
URL : jdbc:oracle:thin:#localhost:1521:XE
I knew that using XA datasource we can define multiple dataSources. Can I do same thing without XA dataSource. Does any one tried this kind of approach.
::UPDATE::
Thank you all for your responses I have implemented following solution.
I have taken the multiple database approach. where you will define multiple transactionManagers and managerFactory. I have taken only single non xa dataSource(JNDI) that is refereed in EntityManagerFactory Bean.
you can reefer following links here which are for multiple dataSources
Multiple DataSource Approach
defining #transactional value
Also explored on transaction managers org.springframework.transaction.jta.WebLogicJtaTransactionManager and org.springframework.orm.jpa.JpaTransactionManager as well.
There is an interesting article about this in Spring docs - Dynamic DataSource Routing. There is an example there, that allows you to basically switch data sources at runtime. It should help you. I'd gladly help you more, if you have any more specific questions.
EDIT: It tells, that the actual use is to have connection to multiple databases via one configuration, but you could manage to create different configs to one database with different params, as you'd need to.
I would suggest using Database "services". Each workload, read-only and read-write, would be using its own service to access the database. That way you can use AWR reports to get statistics for each service. You can also turn off read-write when you keep read-only up and running.
Here is a pointer to the Oracle Database documentation that talks about Services:
https://docs.oracle.com/database/121/ADMIN/create.htm#CIABBCAI
If you're using spring, you should be able to accomplish this without using 2 Datasources via spring #Transactional with the readonly property set to true. The reason why I suggest this is that you seem to be concerned about the transactionality only and this seems to be catered for in the spring framework?
I'd suggest something like this for your case:
#Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// do something
}
// these settings have precedence for this method
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
Using this style, you should be able to split read only services from their write counterparts, or even have read and write service methods combined. But both of these do not use 2 datasources.
Code is from the Spring Reference
I am pretty sure that you need to address the problem on the database / connection url + properties layer.
I would google around for something like read write replication.
Related to your question with JPA and transaction. You are doomed when you are using multiple Datasources. Also XA datasources are not really a solution for that. The only thing they do for you is to ensure consistency over multi data source operations. XA Transaction do only span some sort of logical transaction over two transactions (one for each datasource). From the transaction isolation point of view (as long as your not using READ_UNCOMMITED) both datasources use their own transaction. This means the read data source would not see the changes made by the write transaction.
I've set up Spring Roo following some basic guides, and I have a setup where data from my database can be accessed from a web browser using Roo's standard forms (like in this Youtube video). Now I'd like to access that data from Java code so I can "inject" it into a view from other pages on the site. How is this done?
Edit: Here's how I was able to access my data: From a controller (or any class, really), I use the this annotation along with this property definition:
#PersistenceContext
private EntityManager manager;
Then I can access the data with a query like this:
List<Announcement> results = manager.createQuery("from Announcement a where a.id = :id").setParameter("id", new Long(1)).getResultList();
This will give you a List of type Announcement (which is just an entity I created). Of course this query will yield only one result (or zero if the database doesn't have an entry with an id of 1). Thanks Micha for this solution.
You can use the #PersistenceContext annotation to get a JPA EntityManager instance in your application. Using the EntityManager you can query the database (like showed here). Since you are using Roo the entityManagerFactory bean and transaction support should already be included in your bean configuration file.
You can also use Spring data repositories to access your data.
Maybe this video can help you.
I want to setup my database with initial data programmatically. I want to populate my database for development runs, not for testing runs (it's easy). The product is built on top of Spring and JPA/Hibernate.
Developer checks out the project
Developer runs command/script to setup database with initial data
Developer starts application (server) and begins developing/testing
then:
Developer runs command/script to flush the database and set it up with new initial data because database structures or the initial data bundle were changed
What I want is to setup my environment by required parts in order to call my DAOs and insert new objects into database. I do not want to create initial data sets in raw SQL, XML, take dumps of database or whatever. I want to programmatically create objects and persist them in database as I would in normal application logic.
One way to accomplish this would be to start up my application normally and run a special servlet that does the initialization. But is that really the way to go? I would love to execute the initial data setup as Maven task and I don't know how to do that if I take the servlet approach.
There is somewhat similar question. I took a quick glance at the suggested DBUnit and Unitils. But they seem to be heavily focused in setting up testing environments, which is not what I want here. DBUnit does initial data population, but only using xml/csv fixtures, which is not what I'm after here. Then, Maven has SQL plugin, but I don't want to handle raw SQL. Maven also has Hibernate plugin, but it seems to help only in Hibernate configuration and table schema creation (not in populating db with data).
How to do this?
Partial solution 2010-03-19
Suggested alternatives are:
Using unit tests to populate the database #2423663
Using ServletContextListener to gain control on web context startup #2424943 and #2423874
Using Spring ApplicationListener and Spring's Standard and Custom Events #2423874
I implemented this using Spring's ApplicationListener:
Class:
public class ApplicationContextListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
...check if database is already populated, if not, populate it...
}
}
}
applicationContext.xml:
<bean id="applicationContextListener" class="my.namespaces.ApplicationContextListener" />
For some reason I couldn't get ContextStartedEvent launched, so I chose ContextRefreshedEvent which is launched in startup as well (haven't bumped into other situations, yet).
How do I flush the database? Currently, I simply remove HSQLDB artifacts and a new schema gets generated on startup by Hibernate. As the DB is then also empty.
You can write a unit test to populate the database, using JPA and plain Java. This test would be called by Maven as part of the standard build lifecycle.
As a result, you would get an fully initialized database, using Maven, JPA and Java as requested.
The usual way to do this is to use a SQL script. Then you run a specific bash file that populate the db using your .sql
If you want to be able to programmatically set your DB during the WebApp StartUp you can use a Web Context Listener. During the initialization of your webContext you can use a Servlet Context Listener to get access to your DAO (Service Layer.. whatever) create your entities and persist them as you use to do in your java code
p.s. as a reference Servlet Life Cycle
If you use Spring you should have a look at the Standard and Custom Events section of the Reference. That's a better way to implement a 'Spring Listener' that is aware of Spring's Context (in the case you need to retrieve your Services form it)
You could create JPA entities in a pure Java class and persist them. This class could be invoked by a servlet but also have a main method and be invoked on the command line, by maven (with the Exec Maven Plugin) or even wrapped as a Maven plugin.
But you're final workflow is not clear (do you want the init to be part of the application startup or done during the build?) and requires some clarification.
I would us a Singleton bean for that:
import javax.annotation.PostConstruct;
import javax.ejb.Startup;
import javax.ejb.Singleton;
#Singleton
#Startup
public class InitData {
#PostConstruct
public void load() {
// Load your data here.
}
}
Depend on your db. It is better to have script to set up db
In the aforementioned ServletContextListener or in a common startup place put all the forthcoming code
Define your data in an agreeable format - XML, JSON or even java serialization.
Check whether the initial data exists (or a flag indicating a successful initial import)
If it exists, skip. If it does not exist, get a new DAO (using WebApplicationContextUtils.getRequiredWebApplicationContext().getBean(..)) , iterate all predefined objects and persist them via the EntityManager in the database.
I'm having the same problem. I've tried using an init-method on the bean, but that runs on the raw bean w/o AOP and thus cannot use #Transactional. The same seems to go for #PostConstruct and the other bean lifecycle mechanism.
Given that, I switched to ApplicationListener w/ ContextRefreshedEvent; however, in this case, #PersistenceContext is failing to get an entity manager
javax.persistence.PersistenceException: org.hibernate.SessionException: Session is closed!
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:630)
at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:108)
Using spring 2.0.8, jpa1, hibernate 3.0.5.
I'm tempted to create a non-spring managed entitymanagerfactory and do everything directly but fear that would interfere w/ the rest of the Spring managed entity and transaction manager.
I'm not sure if you can get away from using some SQL. This would depend if your develoeprs are staring with an empty database with no schema defined or if the tables are there but they are empty.
If you starting with empty tables then you could use a Java approach to generating the data. I'm not that familiar with Maven but I assume you can create some task that would use your DAO classes to generate the data. You could probably even write it using a JVM based scripting language like Groovy that would be able to use your DAO classes directly. You would have a similar task that would clear the data from the tables. Then your developers would just run these tasks on the command line or through their IDE as a manual step after checkout.
If you have a fresh database instance that I think you will need to execute some SQL just to create the schema. You could technically do that with executing SQL calls with hibernate but that really doesn't seem worth it.
Found this ServletContextListener example by mkyong. Quoting the article:
You want to initialize the database connection pool before the web
application is start, is there a “main()” method in whole web
application?
This sounds to me like the right place where to have code to insert initial DB data.
I tested this approach to insert some initial data for my webapp; it works.
I found some interesting code in this repository: https://github.com/resilient-data-systems/fast-stateless-api-authentication
This works pretty neat in my project:
#Component
#DependsOn({ "dataSource" })
public class SampleDataPopulator {
private final static Logger log = LoggerFactory.getLogger(SampleDataPopulator.class);
#Inject
MyRepository myRepository
#PostConstruct
public void populateSampleData() {
MyItem item = new ResourceDatabasePopulator();
myRepository.save(item);
log.info("Populated DB with sample data");
}
}
You can put a file called data.sql in src/main/resources, it will be read and executed automatically on startup. See this tutorial.
The other answers did not work for me.