복붙노트

[SPRING] Spring + Hibernate를 이용한 멀티 테넌시 : "멀티 테넌 시용으로 구성된 SessionFactory이지만 테넌트 식별자가 지정되지 않았습니다"

SPRING

Spring + Hibernate를 이용한 멀티 테넌시 : "멀티 테넌 시용으로 구성된 SessionFactory이지만 테넌트 식별자가 지정되지 않았습니다"

Spring 3 애플리케이션에서 나는 Hibernate 4의 네이티브 MultiTenantConnectionProvider와 CurrentTenantIdentifierResolver를 통해 멀티 테넌시를 구현하려고 시도하고있다. 나는 Hibernate 4.1.3에서이 문제가 있다는 것을 알았지 만, 나는 4.1.9를 실행 중이며 여전히 비슷한 예외를 얻고있다 :

   Caused by:

org.hibernate.HibernateException: SessionFactory configured for multi-tenancy, but no tenant identifier specified
    at org.hibernate.internal.AbstractSessionImpl.<init>(AbstractSessionImpl.java:84)
    at org.hibernate.internal.SessionImpl.<init>(SessionImpl.java:239)
    at org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl.openSession(SessionFactoryImpl.java:1597)
    at org.hibernate.internal.SessionFactoryImpl.openSession(SessionFactoryImpl.java:963)
    at org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin(HibernateTransactionManager.java:328)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:334)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:105)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
    at com.afflatus.edu.thoth.repository.UserRepository$$EnhancerByCGLIB$$c844ce96.getAllUsers(<generated>)
    at com.afflatus.edu.thoth.service.UserService.getAllUsers(UserService.java:29)
    at com.afflatus.edu.thoth.HomeController.hello(HomeController.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:746)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:687)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:915)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:811)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:735)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:796)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:671)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:448)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:138)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:564)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:213)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1070)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:375)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:175)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1004)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:136)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:258)
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:109)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
    at org.eclipse.jetty.server.Server.handle(Server.java:439)
    at org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:246)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:265)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:240)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:589)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:520)
    at java.lang.Thread.run(Thread.java:722) enter code here

아래는 관련 코드입니다. MultiTenantConnectionProvider에서 필자는 매번 새로운 연결을 반환하는 바보 같은 코드를 작성했으며 CurrentTenantIdentifierResolver는 항상이 시점에서 동일한 ID를 반환합니다. 분명히이 논리는 인스턴스화 할 연결을 관리 한 후에 구현되어야합니다.

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan">
        <list>
            <value>com.afflatus.edu.thoth.entity</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
            <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
            <prop key="hibernate.hbm2ddl">${hibernate.dbm2ddl}</prop>
            <prop key="hibernate.multiTenancy">DATABASE</prop>
            <prop key="hibernate.multi_tenant_connection_provider">com.afflatus.edu.thoth.connection.MultiTenantConnectionProviderImpl</prop>
            <prop key="hibernate.tenant_identifier_resolver">com.afflatus.edu.thoth.context.MultiTenantIdentifierResolverImpl</prop>
        </props>
    </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="autodetectDataSource" value="false" />
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
package com.afflatus.edu.thoth.connection;

import java.util.Properties;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.service.jdbc.connections.spi.AbstractMultiTenantConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.hibernate.cfg.*;

public class MultiTenantConnectionProviderImpl extends AbstractMultiTenantConnectionProvider {

    private final Map<String, ConnectionProvider> connectionProviders
        = new HashMap<String, ConnectionProvider>();

    @Override
    protected ConnectionProvider getAnyConnectionProvider() {

        System.out.println("barfoo");
        Properties properties = getConnectionProperties();

        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://127.0.0.1:3306/test");
        ds.setUsername("root");
        ds.setPassword("");

        InjectedDataSourceConnectionProvider defaultProvider = new InjectedDataSourceConnectionProvider();
        defaultProvider.setDataSource(ds);
        defaultProvider.configure(properties);

        return (ConnectionProvider) defaultProvider;
    }


    @Override
    protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
        System.out.println("foobar");
        Properties properties = getConnectionProperties();

        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://127.0.0.1:3306/test2");
        ds.setUsername("root");
        ds.setPassword("");

        InjectedDataSourceConnectionProvider defaultProvider = new InjectedDataSourceConnectionProvider();
        defaultProvider.setDataSource(ds);
        defaultProvider.configure(properties);

        return (ConnectionProvider) defaultProvider;
    }

    private Properties getConnectionProperties() {
        Properties properties = new Properties();
        properties.put(AvailableSettings.DIALECT, "org.hibernate.dialect.MySQLDialect");
        properties.put(AvailableSettings.DRIVER, "com.mysql.jdbc.Driver");
        properties.put(AvailableSettings.URL, "jdbc:mysql://127.0.0.1:3306/test");
        properties.put(AvailableSettings.USER, "root");
        properties.put(AvailableSettings.PASS, "");

        return properties;

    }
}
package com.afflatus.edu.thoth.context;

import org.hibernate.context.spi.CurrentTenantIdentifierResolver;

public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {

    public String resolveCurrentTenantIdentifier() {
        return "1";
    }

    public boolean validateExistingCurrentSessions() {
        return true;
    }

}

아무도 특별히 잘못된 것을 볼 수 있습니까? 이것은 트랜잭션이 열리 자마자 예외를 던집니다. SessionFactory가 Session을 올바르게 열지 못하고 있거나 Session이 단순히 CurrentTenantIdentifierResolver에 의해 반환 된 값을 무시하고있는 것처럼 보입니다. 이것은 CurrentTenantIdentifierResolver가 반환 한 값을 Hibernate 4.1.3에서의 문제라고 생각합니다. 이것은 해결 된 것으로 생각되었다.

해결법

  1. ==============================

    1.@Transactional을 코드의 어느 곳에서나 사용하고 있습니까 (예 : 서비스 또는 DAO 클래스 / 메소드 표시)? 내 서비스 클래스에서 @Transactional을 주석 처리 할 때까지 동일한 오류가 발생했습니다. 나는 그것이 Hibernate 4의 디폴트 openSessionInThread 행동과 관련이 있다고 생각한다.

    @Transactional을 코드의 어느 곳에서나 사용하고 있습니까 (예 : 서비스 또는 DAO 클래스 / 메소드 표시)? 내 서비스 클래스에서 @Transactional을 주석 처리 할 때까지 동일한 오류가 발생했습니다. 나는 그것이 Hibernate 4의 디폴트 openSessionInThread 행동과 관련이 있다고 생각한다.

    또한 ConnectionProvider 및 TenantIdentifierResolver의 사용자 정의 구현없이 최대 절전 모드를 구성했습니다. 나는 hibernate.connection.datasource를 java : // comp / env / jdbc /로 설정하고, jndi 리소스의 이름을 session.actio.withOptions ()를 호출하는 DAO 메소드로 전달하는 jndi 기반 접근법을 사용하고있다. ) .tenantIdentifier (tenant) .openSession ();

    난 아직도 @Transactional, 함께 작업 구성을 얻을 수 있는지 주위에 놀고있어 있지만 스레드 동작에서 기본 세션 jndi 기반 접근 방식은 지금 작동하는 것 같습니다.

  2. ==============================

    2.전방 : 비록 내가 코드를 포함하고있는이 대답을 받아들이지 만, 이것이 유용하다고 생각한다면 대런의 대답을 upvote 해주십시오. 그는 내가 이것을 해결할 수 있었던 이유 다.

    전방 : 비록 내가 코드를 포함하고있는이 대답을 받아들이지 만, 이것이 유용하다고 생각한다면 대런의 대답을 upvote 해주십시오. 그는 내가 이것을 해결할 수 있었던 이유 다.

    좋아, 그럼 우리가 간다 ....

    Darren이 지적했듯이, 이것은 SessionFactory가 세션을 부적절하게 인스턴스화 할 때 실제로 발생하는 문제입니다. 수동으로 세션을 인스턴스화하려면 아무런 문제가 없습니다. 예 :

    sessionFactory.withOptions().tenantIdentifier(tenant).openSession();
    

    그러나 @Transactional 주석은 SessionFactory가 currentTenantIdentifierResolver에서 테넌트 식별자를 가져 오지 않는 sessionFactory.getCurrentSession ()으로 세션을 열도록합니다.

    Darren은 DAO 계층에서 세션을 수동으로 열 것을 제안했지만, 이는 각 DAO 방법이 로컬 범위 트랜잭션을 가질 것을 의미합니다. 이 작업을 수행하는 더 좋은 장소는 서비스 계층입니다. 각 서비스 계층 호출 (즉, doSomeLogicalTask ​​())은 여러 DAO 메소드를 호출 할 수 있습니다. 논리적으로 관련이 있기 때문에 각각의 트랜잭션이 동일한 트랜잭션에 바인딩되어야한다는 의미가 있습니다.

    게다가, 나는 각 서비스 계층에서 코드를 복제하여 트랜잭션을 생성하고 관리하는 아이디어를 좋아하지 않았다. 대신 AOP를 사용하여 새 세션을 인스턴스화하고 트랜잭션을 처리하는 조언을 사용하여 서비스 계층의 각 메소드를 래핑했습니다. 애스펙트는 현재 Session을 TheadLocal 스택에 저장합니다.이 스택은 쿼리를 위해 DAO 계층에서 액세스 할 수 있습니다.

    이 모든 작업은 DAO 수퍼 클래스의 한 줄을 제외하고는 SessionFactory가 아니라 ThreadLocal 스택에서 Session을 가져 오는 것을 제외하고는 인터페이스와 구현을 버그 수정 된 것과 동일하게 유지할 수 있습니다. 버그가 수정되면 변경할 수 있습니다.

    조금만 정리하면 코드를 곧 게시 할 것입니다. 아무도이 문제가 있으면 아래에서 자유롭게 토론하십시오.

  3. ==============================

    3.비록 이것이 더 오래된 주제일지도 모르지만, 그 대답은 이미 처리되었을 것입니다. 내가 알아 차 렸던 것은 다음과 같다.

    비록 이것이 더 오래된 주제일지도 모르지만, 그 대답은 이미 처리되었을 것입니다. 내가 알아 차 렸던 것은 다음과 같다.

    귀하의 클래스를 정의 CurrentTenantIdentifierResolver Impl :

    public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver
    

    하지만 귀하의 설정에서 MultiTenantIdentifierResolverImpl을 참조하십시오 :

    <prop key="hibernate.tenant_identifier_resolver">com.afflatus.edu.thoth.context.MultiTenantIdentifierResolverImpl</prop>
    

    내가 오늘 같은 실수를했기 때문에이 점을 지적하자. 그 후에 모든 것이 매력처럼 작동했다.

  4. ==============================

    4.이 기사에서 설명했듯이, Hibernate는 CurrentTenantIdentifierResolver 인터페이스를 정의하여 Spring이나 Java EE와 같은 프레임 워크가 EntityManagerFactiry의 기본 인스턴스화 메커니즘을 사용하도록 허용한다.

    이 기사에서 설명했듯이, Hibernate는 CurrentTenantIdentifierResolver 인터페이스를 정의하여 Spring이나 Java EE와 같은 프레임 워크가 EntityManagerFactiry의 기본 인스턴스화 메커니즘을 사용하도록 허용한다.

    따라서 CurrentTenantIdentifierResolver는 올바른 정규화 된 클래스 이름을 제공하지 않아 잘못 된 위치의 구성 속성을 통해 설정해야합니다. CurrentTenantIdentifierResolver 구현은 CurrentTenantIdentifierResolverImpl이고, hibernate.tenant_identifier_resolver는 다음과 같아야한다.

    <prop key="hibernate.tenant_identifier_resolver">com.afflatus.edu.thoth.context.CurrentTenantIdentifierResolverImpl</prop>
    

    이 문제를 해결 한 후에, HibernateTransactionManager가 getSessionFactory (). openSession ()을 호출 할 때, Hibernate는 CurrentTenantIdentifierResolverImpl을 사용하여 테넌트 식별자를 해결할 것이다.

  5. ==============================

    5.어쩌면 당신은 최대 절전 모드를 4.X로 업그레이드하고 트랜잭션을 시작하기 위해 주석이나 aspect를 사용할 필요가있을 것이다.

    어쩌면 당신은 최대 절전 모드를 4.X로 업그레이드하고 트랜잭션을 시작하기 위해 주석이나 aspect를 사용할 필요가있을 것이다.

  6. ==============================

    6.내 CurrentTenantIdentifierResolver 구현이 resolveCurrentTenantIdentifier () 메소드에 대해 null을 반환했을 때 비슷한 문제가 발생했습니다.

    내 CurrentTenantIdentifierResolver 구현이 resolveCurrentTenantIdentifier () 메소드에 대해 null을 반환했을 때 비슷한 문제가 발생했습니다.

  7. from https://stackoverflow.com/questions/14837601/multi-tenancy-with-spring-hibernate-sessionfactory-configured-for-multi-tena by cc-by-sa and MIT license