Multitenancy - Hibernate Persistence - Don't change SCHEMA

Viewed 36

I have a problem trying to implement Multitenancy Hibernate in JPA application using Netbeans. The problem is that de method getEntityManager(String multitenancyIdentifier) is not executing the code inside itself, the value of parameter came null, so is not changing the schema.

In order to implement Multitenancy i had to include this 3 classes:

SchemaMultiTenantProvider.java
SchemaTenantResolver.java
MuiltitenancyResolver.java

You can find this example on this link: https://developers.redhat.com/blog/2020/11/12/jakarta-ee-multitenancy-with-jpa-on-wildfly-part-2#

In the SchemaMultiTenantProvider we have the line to change schema:

connection.createStatement().execute("SET SCHEMA '" + tenantIdentifier + "'");

In the SchemaTenantResolver we have a Map to select the schema in reference to user that will login to the app:

public SchemaTenantResolver(){
    userDatasourceMap = new HashMap();
    userDatasourceMap.put("default", "public");
    userDatasourceMap.put("usuario1", "prueba");
}

And the MuiltitenancyResolver we set the tenantIdentifier, setted in getEntityManager(String multitenancyIdentifier):

public void setTenantIdentifier(String tenantIdentifier) {
    this.tenantIdentifier = tenantIdentifier;
}

In the persistence i have this:

< provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

< jta-data-source>jdbc/dspw</jta-data-source>
< exclude-unlisted-classes>false</exclude-unlisted-classes>
< properties>
  < property name="javax.persistence.schema-generation.database.action" value="none"/>
  < property name="hibernate.dialect" value="org.hibernate.dialect.PostgresPlusDialect"/>
  < property name="hibernate.multiTenancy" value="SCHEMA"/>
  < property name="hibernate.tenant_identifier_resolver" value="services.SchemaTenantResolver"/>
  < property name="hibernate.multi_tenant_connection_provider" value="services.SchemaMultiTenantProvider"/>
</ properties>

In my AbstractFacade i have this:

public abstract class AbstractFacade<T> {

private Class<T> entityClass;

@PersistenceUnit
protected EntityManagerFactory emf;

public AbstractFacade(Class<T> entityClass) {
    this.entityClass = entityClass;
    
} 


public EntityManager getEntityManager(String multitenancyIdentifier){
    
    System.out.println("id getEntityManager: " + multitenancyIdentifier);
    
    final SessionFactoryImplementor sfi = emf.unwrap(SessionFactoryImplementor.class);
    final MuiltitenancyResolver tenantResolver = (MuiltitenancyResolver) sfi.getCurrentTenantIdentifierResolver();
    tenantResolver.setTenantIdentifier(multitenancyIdentifier);
    
    return emf.createEntityManager();
}

In the same class i have this method to find any db object:

public T find(Object id, String multitenancyIdentifier) {
    System.out.println("id find: " + multitenancyIdentifier);
    return getEntityManager(multitenancyIdentifier).find(entityClass, id);
}

In one of Facades class (for the example) i have this:

public class UsuarioFacade extends AbstractFacade<Usuario> {

@PersistenceContext(unitName = "SPWPersistence")
private EntityManager em;

@Override
public EntityManager getEntityManager(String multitenancyIdentifier) {
    System.out.println("User Facade: " + multitenancyIdentifier);
    return em;
}

public UsuarioFacade() {
    super(Usuario.class);
}

}

Finally i make a query using (userFacade) UsuarioFacade:

System.out.println("Test User: " + usuarioFacade.find(1,"usuario1").getNombre());

And this is the result:

id find: usuario1|#]
User Facade: usuario1|#]
Identificador schema: null|#]
Test User: Juan Perez Caceres|#]

The test User: Juan Perez Caceres is in the public schema (PostGres DB), but i am passing the user: usuario1 that correspont to schema: prueba (test). It is not changing the shema.

What can i do? O_o

1 Answers

I already resolved the problem. What was causing the problem is that I, stupidly was using an EntityManager that was not that i setted.

The solution is, in the AbstractFacade class, create and EntityManager em object:

@PersistenceContext(unitName = "SPWPersistence")
protected EntityManager em;

Then modify the getEntityManager() method this way:

public EntityManager getEntityManager(String multitenancyIdentifier){
    
    System.out.println("id getEntityManager: " + multitenancyIdentifier);
    
    final SessionFactoryImplementor sfi = emf.unwrap(SessionFactoryImplementor.class);
    final MuiltitenancyResolver tenantResolver = (MuiltitenancyResolver) sfi.getCurrentTenantIdentifierResolver();
    tenantResolver.setTenantIdentifier(multitenancyIdentifier);
    
    em = emf.createEntityManager();
    
    return em;
}

Finally erase the instance of getEntityManager() method of UsuarioFacade, it since you won't need it:

@Stateless
public class UsuarioFacade extends AbstractFacade<Usuario> {

    public UsuarioFacade() {
        super(Usuario.class);
    }

}

Done! now you can use your Facade like always:

System.out.println("Test User: " + usuarioFacade.find(1,"usuario1").getNombre());

Result:

 id find: usuario1|#]
 id getEntityManager: usuario1|#]
 Identificador schema: usuario1|#]
 Test User: Marlboro|#]

It changing the schema!

Happy end :D

Related