[SPRING] Spring + Hibernate를 이용한 멀티 테넌시 : "멀티 테넌 시용으로 구성된 SessionFactory이지만 테넌트 식별자가 지정되지 않았습니다"
SPRINGSpring + 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.@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.전방 : 비록 내가 코드를 포함하고있는이 대답을 받아들이지 만, 이것이 유용하다고 생각한다면 대런의 대답을 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.비록 이것이 더 오래된 주제일지도 모르지만, 그 대답은 이미 처리되었을 것입니다. 내가 알아 차 렸던 것은 다음과 같다.
비록 이것이 더 오래된 주제일지도 모르지만, 그 대답은 이미 처리되었을 것입니다. 내가 알아 차 렸던 것은 다음과 같다.
귀하의 클래스를 정의 CurrentTenantIdentifierResolver Impl :
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver
하지만 귀하의 설정에서 MultiTenantIdentifierResolverImpl을 참조하십시오 :
<prop key="hibernate.tenant_identifier_resolver">com.afflatus.edu.thoth.context.MultiTenantIdentifierResolverImpl</prop>
내가 오늘 같은 실수를했기 때문에이 점을 지적하자. 그 후에 모든 것이 매력처럼 작동했다.
-
==============================
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.어쩌면 당신은 최대 절전 모드를 4.X로 업그레이드하고 트랜잭션을 시작하기 위해 주석이나 aspect를 사용할 필요가있을 것이다.
어쩌면 당신은 최대 절전 모드를 4.X로 업그레이드하고 트랜잭션을 시작하기 위해 주석이나 aspect를 사용할 필요가있을 것이다.
-
==============================
6.내 CurrentTenantIdentifierResolver 구현이 resolveCurrentTenantIdentifier () 메소드에 대해 null을 반환했을 때 비슷한 문제가 발생했습니다.
내 CurrentTenantIdentifierResolver 구현이 resolveCurrentTenantIdentifier () 메소드에 대해 null을 반환했을 때 비슷한 문제가 발생했습니다.
from https://stackoverflow.com/questions/14837601/multi-tenancy-with-spring-hibernate-sessionfactory-configured-for-multi-tena by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] JSP EL 표현식에서 Spring Security Principal 가져 오기 (0) | 2019.01.31 |
---|---|
[SPRING] 최대 절전 모드 (봄 ORM)와 봄 mvc 3의 적절한 사용 (0) | 2019.01.31 |
[SPRING] ReactiveCrudRepository는 봄에 최대 절전 모드를 사용합니다. (0) | 2019.01.31 |
[SPRING] 추가 로깅을 해제하려면 어떻게합니까? (0) | 2019.01.31 |
[SPRING] JpaRepository DML 작업에 대해 지원되지 않음 [delete query] (0) | 2019.01.31 |