1 2 Previous Next 16 Replies Latest reply on Feb 19, 2014 9:16 AM by nathandennis

    How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?

    inerself

      Hello, I did a implementation in hibernate for multi tenancy but without JPA, how can I implement the same strategy but using EntityManager?

       

      Here is my code with multi-tenancy in hibernate with DATABASE strategy: https://github.com/fearzendron/multitenancyhibernate.

       

      I saw the webinar about multitenancy and in the questions discussed https://community.jboss.org/wiki/Multi-tenancyDesign, in one of the answers @Steve said to user a custom ConnectionProvider in JPA. How do I do that?

       

      tks

        • 1. Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
          inerself

          Is it correct to do like this?

           

          Map props = new HashMap();

          props.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);

          props.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER,

                          MultiTenantConnectionProviderImpl.class);

          props.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER,

                          CurrentTenantIdentifierResolverImpl.class);

                

          EntityManager em = emf.createEntityManager(props);

           

          -> My problem is how to pass the tenantIdentifier to EntityManager:

          sessionFactory.withOptions().tenantIdentifier(tenant);

          • 2. Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
            sunny.dyal

            Fernando,

             

            Are you still researching on the same? I also have same issue. Nobody is replying. No suggestions. No documentation. No Example. No support.

             

            Please help if you can.

             

            @JBoss Community:

            Please do something. This is critical issue.

             

            Kind Regards

             

            Sunny Dyal

            • 3. Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
              smarlow

              Hi Sunny + Fernando,

               

              Good questions raised here.  Are you porting applications that are already written or creating new ones?  I would like to identify the edge cases that need to be handled. 

               

              For example, how would the tenant identifier be propagated on a remote EJB invocation, so that the remove JVM uses the same tenant identifier. 

               

              What about extended persistence contexts, are they expected to be used?  Typically, in clustered environments, the extended persistence context is replicated with the stateful bean that references it.  In case of failover, using the same tenant identifier on the new cluster node, is important.

               

              It seems likely to me that more (upstream) code changes are needed for multi-tenancy (for full integration of the Hibernate multi-tenancy features into EE applications.)  Although, you could also write to the Hibernate API and use what is already there.

               

              Thanks for the great questions and starting this discussion.

               

              Scott

              • 4. Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                sebersole

                Is MultiTenantConnectionProviderImpl your class?  Ultimately you need an implementation of the org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider interface.  Hibernate provides one concrete implementation based on a few assumptions : org.hibernate.engine.jdbc.connections.spi.DataSourceBasedMultiTenantConnectionProviderImpl (see its javadocs for those assumptions).

                 

                As far as "passing in" the tenant identifier, you wouldn't; JPA provides no support for passing in provider specific info during creation of an EntityManager.  Instead in JPA environments you must supply a org.hibernate.context.spi.CurrentTenantIdentifierResolver implementation to tell Hibernate how to lookup the tenant identifier while it is creating the EntityManager.  You seem to have done that, so not really following your question...

                • 5. Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                  sebersole

                  How is this a "critical issue"?  lol.  I understand its a worthwhile feature (heck that's why i added it).  And this is not even a Hibernate forum.  To be honest, without Scott pointing me to this url and asking me to respond I would never even have seen it. 

                   

                  And there is documentation : Hibernate Developer Guide

                   

                  I understand you are frustrated with a lack of response, but in my experience it usually helps when you ask things in "the correct place".

                  • 6. Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                    sebersole

                    BTW, Fernando you ask specifically about "JPA / Hibernate Multi Tenancy".  Maybe you just mean "using Hibernate multi-tenancy from (Hibernate) JPA".  But just wanted to avoid confusion.  JPA 2.1 also provides multi-tenancy "support", but multi-tenancy in JPA 2.1 means something VERY much different than what I added in Hibernate.

                     

                    In Hibernate, multi-tenancy support allows you to run tenants "side-by-side" in the same VM (same app instance).

                     

                    In JPA, multi-tenancy support means deploying your app multiple times, once per tenant.

                     

                    Just to be clear.

                    • 7. Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                      smarlow

                      JPA 2.1 also provides multi-tenancy "support", but multi-tenancy in JPA 2.1 means something VERY much different than what I added in Hibernate.

                       

                      I don't think multi-tenancy stayed in JPA 2.1 (I can't find it in the final spec).  It definitely was in the 2.1 spec for a while (as TBD) and was discussed.  I think it will be in a future spec release.

                      • 8. Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                        inerself

                        Hello Guys, I hope explain well my implementation, been a while since I did it! My application is a J2EE implementation with spring and EJB3. In my case I adopt the schema strategy for multi-tenancy, I will use a SaaS and each tenant

                         

                        for me is one company. Some key points im my implementation:

                         

                        1 - I configured the persistence.xml like this:

                         

                        <persistence-unit name="VistoSystemPU" transaction-type="JTA">

                                <provider>org.hibernate.ejb.HibernatePersistence</provider>

                                <jta-data-source>java:jboss/datasources/<database_name></jta-data-source>

                                <properties>

                                    <property name="jboss.entity.manager.jndi.name" value="java:/<application_name>EM"/>

                                    <property name="jboss.entity.manager.factory.jndi.name" value="java:/<application_name>EMF"/>

                                    <property name="hibernate.show_sql" value="true"/>

                                    <property name="hibernate.format_sql" value="true"/>

                         

                                    <property name="hibernate.multi_tenant_connection_provider"

                                              value="br.com.arizona.visto.system.global.tenant.MultiTenantConnectionProviderImpl"/>

                                    <property name="hibernate.tenant_identifier_resolver"

                                              value="br.com.arizona.visto.system.global.tenant.CurrentTenantIdentifierResolverImpl"/>

                                    <property name="hibernate.multiTenancy" value="SCHEMA"/>

                                </properties>

                            </persistence-unit>

                         

                        2 - Implementation of MultiTenantConnectionProviderImpl

                         

                        public class MultiTenantConnectionProviderImpl extends AbstractMultiTenantConnectionProvider {

                         

                            @Override

                            protected ConnectionProvider getAnyConnectionProvider() {

                                return new ConnectionProviderImpl(ArizonaTenant.DEFAULT_TENANT); <-- Here can be a simple string identifier

                            }

                            @Override

                            protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {

                                return new ConnectionProviderImpl(tenantIdentifier);

                            }

                        }

                         

                        3 - Implementation of CurrentTenantIdentifierResolverImpl

                         

                        public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {

                         

                            @Override

                            public String resolveCurrentTenantIdentifier() {

                                return TenantThreadLocal.get();

                            }

                         

                            @Override

                            public boolean validateExistingCurrentSessions() {

                                return true;

                            }

                        }

                         

                        4 - I set the tenant value to a ThreadLocal value by a filter in my webapp, in this way I can pass the value to my EJB.

                         

                        I hope help you Sunny and Scott, about your questions Steve I was not not used with the interface of the Jboss community and I added this question in the "wrong place" but the next time I will do right! I choosed the hibernate

                         

                        implementation of multitenancy to be the most advanced I found and I think it continues to be the best choice, you did a great work, my application is running with two customers in a trial way. It has been a while I don't follow this

                         

                        subject but I will update my knowledge!

                        • 9. Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                          sunny.dyal

                          Fernando,

                           

                          Thanks for sharing your approach. I will try to use with database strategy.

                           

                          Kind Regards

                           

                          Sunny Dyal

                          • 10. Re: Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                            nathandennis

                            Fernando,

                            I have been working on something similar and though you hadn't posted your solution i had a working version of this with a very similar solution. The one problem i still face that I'm curious if you have solved is the OpenEntityManagerInViewFilter used to get rid of the lazy initialization exceptions. I can't for the life of me figure out how to get something working to keep the session open. I need to get my tenant identifier into the entitymanagerfactory before recreating the entitymanager. Did you manage this already? I've a couple hibernate feature request that indicates this isn't possible yet. Any ideas?

                             

                             

                             

                            package com.eb.app.filter;
                            
                            
                            import java.io.IOException;
                            
                            
                            import javax.persistence.EntityManager;
                            import javax.persistence.EntityManagerFactory;
                            import javax.persistence.PersistenceException;
                            import javax.servlet.FilterChain;
                            import javax.servlet.ServletException;
                            import javax.servlet.http.HttpServletRequest;
                            import javax.servlet.http.HttpServletResponse;
                            
                            
                            import org.springframework.dao.DataAccessResourceFailureException;
                            import org.springframework.orm.jpa.EntityManagerFactoryUtils;
                            import org.springframework.orm.jpa.EntityManagerHolder;
                            import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
                            import org.springframework.transaction.support.TransactionSynchronizationManager;
                            import org.springframework.web.context.WebApplicationContext;
                            import org.springframework.web.context.support.WebApplicationContextUtils;
                            import org.springframework.web.filter.OncePerRequestFilter;
                            
                            
                            import com.eb.data.datasource.TenantContextManager;
                            
                            
                            /**
                            * Servlet 2.3 Filter that binds a JPA EntityManager to the thread for the
                            * entire processing of the request. Intended for the "Open EntityManager in
                            * View" pattern, i.e. to allow for lazy loading in web views despite the
                            * original transactions already being completed.
                            *
                            * <p>
                            * This filter makes JPA EntityManagers available via the current thread, which
                            * will be autodetected by transaction managers. It is suitable for service
                            * layer transactions via
                            * {@link org.springframework.orm.jpa.JpaTransactionManager} or
                            * {@link org.springframework.transaction.jta.JtaTransactionManager} as well as
                            * for non-transactional read-only execution.
                            *
                            * <p>
                            * Looks up the EntityManagerFactory in Spring's root web application context.
                            * Supports a "entityManagerFactoryBeanName" filter init-param in
                            * <code>web.xml</code>; the default bean name is "entityManagerFactory". Looks
                            * up the EntityManagerFactory on each request, to avoid initialization order
                            * issues (when using ContextLoaderServlet, the root application context will
                            * get initialized <i>after</i> this filter).
                            *
                            * @author Juergen Hoeller
                            * @since 2.0
                            * @see OpenEntityManagerInViewInterceptor
                            * @see org.springframework.orm.jpa.JpaInterceptor
                            * @see org.springframework.orm.jpa.JpaTransactionManager
                            * @see org.springframework.orm.jpa.JpaTemplate#execute
                            * @see org.springframework.orm.jpa.SharedEntityManagerCreator
                            * @see org.springframework.transaction.support.TransactionSynchronizationManager
                            */
                            public class MultiTenantOpenEntityManagerInViewFilter extends OncePerRequestFilter {
                            
                            
                                public static final String DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME = "entityManagerFactory";
                            
                            
                                private String entityManagerFactoryBeanName = DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME;
                            
                            
                                /**
                                 * Set the bean name of the EntityManagerFactory to fetch from Spring's root
                                 * application context. Default is "entityManagerFactory".
                                 *
                                 * @see #DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME
                                 */
                                public void setEntityManagerFactoryBeanName(String entityManagerFactoryBeanName) {
                                    this.entityManagerFactoryBeanName = entityManagerFactoryBeanName;
                                }
                            
                            
                                /**
                                 * Return the bean name of the EntityManagerFactory to fetch from Spring's
                                 * root application context.
                                 */
                                protected String getEntityManagerFactoryBeanName() {
                                    return this.entityManagerFactoryBeanName;
                                }
                            
                            
                                @Override
                                protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                                        throws ServletException, IOException {
                            
                            
                                    String tenant = TenantContextManager.getTenant() == null ? TenantContextManager.getDefaultTenant()
                                            : TenantContextManager.getTenant();
                                    if (request.getSession().getAttribute("tenant") != null)
                                        tenant = (String) request.getSession().getAttribute("tenant");
                            
                            
                                    EntityManagerFactory emf = lookupEntityManagerFactory(request);
                                    boolean participate = false;
                            
                            
                                    if (TransactionSynchronizationManager.hasResource(emf)) {
                                        // Do not modify the EntityManager: just set the participate flag.
                                        participate = true;
                                    } else {
                                        logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter");
                                        try {
                                            EntityManager em = createEntityManager(emf);
                                            TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em));
                                        } catch (PersistenceException ex) {
                                            throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
                                        }
                                    }
                            
                            
                                    try {
                                        filterChain.doFilter(request, response);
                                    }
                            
                            
                                    finally {
                                        if (!participate) {
                                            EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
                                                    .unbindResource(emf);
                                            logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter");
                                            EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
                                        }
                                    }
                                }
                            
                            
                                /**
                                 * Look up the EntityManagerFactory that this filter should use, taking the
                                 * current HTTP request as argument.
                                 * <p>
                                 * Default implementation delegates to the
                                 * <code>lookupEntityManagerFactory</code> without arguments.
                                 *
                                 * @return the EntityManagerFactory to use
                                 * @see #lookupEntityManagerFactory()
                                 */
                                protected EntityManagerFactory lookupEntityManagerFactory(HttpServletRequest request) {
                                    return lookupEntityManagerFactory();
                                }
                            
                            
                                /**
                                 * Look up the EntityManagerFactory that this filter should use. The default
                                 * implementation looks for a bean with the specified name in Spring's root
                                 * application context.
                                 *
                                 * @return the EntityManagerFactory to use
                                 * @see #getEntityManagerFactoryBeanName
                                 */
                                protected EntityManagerFactory lookupEntityManagerFactory() {
                                    if (logger.isDebugEnabled()) {
                                        logger.debug("Using EntityManagerFactory '" + getEntityManagerFactoryBeanName()
                                                + "' for OpenEntityManagerInViewFilter");
                                    }
                                    WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
                                    return wac.getBean(getEntityManagerFactoryBeanName(), EntityManagerFactory.class);
                                }
                            
                            
                                /**
                                 * Create a JPA EntityManager to be bound to a request.
                                 * <p>
                                 * Can be overridden in subclasses.
                                 *
                                 * @param emf
                                 *            the EntityManagerFactory to use
                                 * @see javax.persistence.EntityManagerFactory#createEntityManager()
                                 */
                                protected EntityManager createEntityManager(EntityManagerFactory emf) {
                                    return emf.createEntityManager();
                                }
                            
                            
                            }
                            
                            
                            • 11. Re: Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                              nathandennis

                              Steve would also have answers to this... if he is still monitoring this thread.

                              • 12. Re: Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                                sebersole

                                Nathan, tbh I am not sure what you are asking.  You mean somehow pushing the local "tenant" variable into the EntityManager?  You can't do it that way.  You need to use a CurrentTenantIdentifierResolver implementation configured as a bootstrap setting to the EntityManagerFactory.

                                 

                                In regards to "a couple hibernate feature request that indicates this isn't possible yet" I assume you mean:

                                [HHH-7312] Validate multi-tenancy configuration (if configured) during JPA bootstrap - Hibernate JIRA

                                ?

                                 

                                The comments on that issue even state that it works.  My contention for leaving it open is stated in my last comment that I just added today to make sure it was clear.

                                 

                                Beyond that, I have no idea what other " couple hibernate feature request(s)" you mean.

                                • 13. Re: Re: Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                                  nathandennis

                                  Steve,

                                  That is the jira i was referring to (and the other threads it was linked to). Thank you for clarifying that. You're correct, CurrentTenantIdentifierRosolver implementation configured in the persistence seems to work. (and as soon as I'm back at my desk will will post additional code here). The problem I'm running into is when using Spring MVC / JSP with hibernate configured for multi tenant I'm running into a lot of

                                   

                                  org.hibernate.LazyInitializationException: failed to lazily initialize a collection ..... - no Session


                                  Errors dealing with JSP pages calling objects on an entity that were lazily fetched. Not a Spring expert here.. In fact, quite the opposite. I've spent most of my career working in Seam, JSF, JavaEE6, DeltaSpike sort of stack. From what I've read and seen, these sort of errors are solved one of two ways. Either with Extending the Persistence context (which i found problematic in a multi tenant environment) or implementing a OpenSessionViewFilter such as the one listed above to start the session in a filter and let it span the entire request. It is from this misfortune I have found my way to this thread. I'm trying to take an large existing application already using Hibernate Session Factory and multi tenancy (with a MultiTenantOpenSessionViewFilter much like the one above except using hibernateSessionFactory) and convert it to a JPA compliant application.  Reasoning for the effort is attaining distributed and extended transaction (for implementing distributed L2Cache, JMS, etc etc)  and every other benefit that writing to JPA can bring.

                                   

                                  The thing I'm unclear about is how to keep the session open throughout the request and avoid Lazy Load exception if we are not able to implement an OpenSessionViewFilter. Do you have any thoughts, ideas, examples, or helpful hints that would push me in the correct direction. (I've even been starring at hibernate source code... normally an indicator that I'm attempting things i probably shouldn't)

                                  • 14. Re: Re: Re: How to implememt JPA / Hibernate Multi Tenancy with DATABASE Strategy?
                                    sunny.dyal

                                    Nathan,

                                     

                                    Consider AvailableSettings (Hibernate JavaDocs) or <prop key="hibernate.enable_lazy_load_no_trans">true</prop>

                                    1 2 Previous Next