[SPRING] Hibernate 4.2와 Spring 3.1.1을 사용하여 MultiTenantConnectionProvider 설정하기
SPRINGHibernate 4.2와 Spring 3.1.1을 사용하여 MultiTenantConnectionProvider 설정하기
저는 현재 별도의 스키마 aproach를 사용하여 다중 임차를 위해 최대 절전 모드를 설정하려고합니다. 약 2 일 동안 작업 한 후 Google을 통해 찾을 수있는 거의 모든 소스를 탐색 한 후 매우 좌절하기 시작했습니다.
기본적으로 나는 Hibernate devguide http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html_single/#d5e4691에 제공된 가이드를 따르려고 노력하고있다. 하지만 불행히도 ConnectionProvider를 빌드하기 위해 ConnectionProviderUtils를 찾을 수 없습니다. 현재 나는 2 점을 계산하려고하고있다.
이 문제를 해결할 수있는 방법에 대한 지침 (방법, 클래스, 자습서)
그래서 여기 내 구현의 관련 부분이 있습니다 : 데이터 소스와 Hibernate.cfg.xml :
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
<property name="url" value="jdbc:sqlserver://<host>:<port>;databaseName=<DbName>;" />
<property name="username" value=<username> />
<property name="password" value=<password> />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- property name="dataSource" ref="dataSource" /-->
<property name="annotatedClasses">
<list>
<value>c.h.utils.hibernate.User</value>
<value>c.h.utils.hibernate.Role</value>
<value>c.h.utils.hibernate.Tenant</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.SQLServerDialect
hibernate.show_sql=true
hibernate.multiTenancy=SCHEMA
hibernate.tenant_identifier_resolver=c.h.utils.hibernate.CurrentTenantIdentifierResolver
hibernate.multi_tenant_connection_provider=c.h.utils.hibernate.MSSQLMultiTenantConnectionProviderImpl
</value>
</property>
</bean>
MSSQLMultiTenantConnectionProviderImpl :
package c.hoell.utils.hibernate;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MSSQLMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 8074002161278796379L;
@Autowired
private DataSource dataSource;
public void configure(Properties props) throws HibernateException {
}
@Override
public Connection getAnyConnection() throws SQLException {
Properties properties = getConnectionProperties(); //method which sets the hibernate properties
DriverManagerConnectionProviderImpl defaultProvider = new DriverManagerConnectionProviderImpl();
defaultProvider.configure(properties);
Connection con = defaultProvider.getConnection();
ResultSet rs = con.createStatement().executeQuery("SELECT * FROM [schema].table");
rs.close(); //the statement and sql is just to test the connection
return defaultProvider.getConnection();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
<--not sure how to implement this-->
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection){
try {
this.releaseAnyConnection(connection);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public boolean supportsAggressiveRelease() {
return false;
}
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MSSQLMultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType );
}
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> unwrapType) {
if ( isUnwrappableAs( unwrapType ) ) {
return (T) this;
}
else {
throw new UnknownUnwrapTypeException( unwrapType );
}
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
지금은 설정 파일에서 필요한 설정을 얻기 위해 볼 수있는 두 가지 접근 방법이 있습니다. configure () 메소드를 실행하거나 데이터 소스의 주입을 가능하게하십시오. 나는 첫번째 것이 더 좋은 길일 것 같아요.
언급해야 할 중요한 점은, Hibernate를 하나의 세입자 (최대 수면 (Hibernate)이 사용하는 표준 ConnectionProvider를 사용하는 MultiTenantConnectionProvider를 사용하지 않고 사용하는 것을 의미)
이미이 게시물을 읽는 사람에게 큰 감사를드립니다. 답변을 기대합니다.
친애하는
나는 이것으로 조금 놀았고 MultiTenantConnectionProvider (위 코드 업데이트)에 연결 상세 정보를 하드 코드했습니다. 이것은 MultiTenantConnectionProvider와 관련하여 잘 작동합니다. 그러나 이것은 여전히 내 문제를 해결하지 못합니다. 이제 트랜잭션 관리자를 초기화 할 때 내 응용 프로그램이 실패합니다.
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
이것은 스택 추적의 예외입니다.
이 문제를 디버그 모드에서 추적 해 보았는데 문제는 내 SessionFactory가 DataSource를 유지하지 못한다는 것을 알았습니다. (hibernate.cfg.xml에 DataSource를 지정했는지 여부는 아무런 차이가 없다) 그러나 TransactionManager를 초기화 할 때 SessionFactory에서 DataSource를 가져 오려고 시도하고 결과적으로 NullPointerException으로 실패합니다. 누구나 최대 절전 모드의 내부 동작 중 어떤 시점에서 이것이 실패했는지에 대한 힌트를 가지고 있습니까? 필자가 본 모든 문서와 게시물에서 DataSource의 SessionFactory 로의 주입을 처리 할 필요가 없음을 알 수 없습니다. 당분간은 데이터 소스를 필요한 장소에 가져 오는 방법이나 초기화 흐름을 변경하는 방법을 알아 내려고합니다. 누구든지 더 좋은 아이디어가 있다면 나는 정말로 행복 할 것이다.
편집 : 또한 최대 절전 모드 포럼에 게시 됨 :
따라서 TransactionManager의 autodetectDataSource 속성을 false로 설정하여이 문제를 해결할 수있었습니다.
<property name="autodetectDataSource" value="false"/>
다음 게시물 http://forum.springsource.org/showthread.php?123478-SessionFactory-configured-for-ultultancy-but-no-tenant-identifier-specified에서 힌트를 얻었습니다. 불행히도 나는 지금 정확히 그 문제에 갇혀있다. ^^ "그러나 이것은 다른 주제에 대한 문제입니다. (편집 : 이것은 이전 테스트와 잘못된 구성 일뿐입니다.
이 주제에 관해서는, 어떻게 든 Spring Security를 사용하기위한 설정에서 이미 가지고있는 DataSource를 재사용 할 수 있기를 원하는 Hibernate가 두 곳에서 DataSource를 설정하지 않아도된다는 문제가 남아 있습니다. 그래서 문제는 여전히 MultiTenantConnectionProvider에서 DataSource의 사용을 통합하는 방법을 의미합니다. 누구든지 그 어떤 힌트를 찾을 수있는 아이디어를 가지고 있습니까?
해결법
-
==============================
1.이 질문의 주석 자 (HHH-8752) 중 한 명이 언급 한 JIRA 문제에 대한 Steve Ebersole의 의견에 따르면 :
이 질문의 주석 자 (HHH-8752) 중 한 명이 언급 한 JIRA 문제에 대한 Steve Ebersole의 의견에 따르면 :
나는 방금 그의 제안을 따랐고 그것을 가능하게 만들었다.
이것은 Spring Bean으로 정의 된 CurrentTenantIdentifierResolver입니다.
@Component @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) public class RequestURITenantIdentifierResolver implements CurrentTenantIdentifierResolver { @Autowired private HttpServletRequest request; @Override public String resolveCurrentTenantIdentifier() { String[] pathElements = request.getRequestURI().split("/"); String tenant = pathElements[1]; return tenant; } @Override public boolean validateExistingCurrentSessions() { return true; } }
이것은 Spring Bean으로 정의 된 MultiTenantConnectionProvider입니다.
@Component public class SchemaPerTenantConnectionProviderImpl implements MultiTenantConnectionProvider { @Autowired private DataSource dataSource; @Override public Connection getAnyConnection() throws SQLException { return dataSource.getConnection(); } @Override public void releaseAnyConnection(final Connection connection) throws SQLException { connection.close(); } @Override public Connection getConnection(final String tenantIdentifier) throws SQLException { final Connection connection = getAnyConnection(); try { connection.createStatement().execute("USE " + tenantIdentifier); } catch (SQLException e) { throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e); } return connection; } @Override public void releaseConnection(final String tenantIdentifier, final Connection connection) throws SQLException { try { connection.createStatement().execute("USE dummy"); } catch (SQLException e) { // on error, throw an exception to make sure the connection is not returned to the pool. // your requirements may differ throw new HibernateException( "Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e ); } finally { connection.close(); } } @Override public boolean supportsAggressiveRelease() { return true; } @Override public boolean isUnwrappableAs(Class aClass) { return false; } @Override public <T> T unwrap(Class<T> aClass) { return null; } }
마지막으로 위의 두 가지 구성 요소를 사용하도록 유선 된 LocalContainerEntityManagerFactoryBean입니다.
@Configuration public class HibernateConfig { @Bean public JpaVendorAdapter jpaVendorAdapter() { return new HibernateJpaVendorAdapter(); } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, MultiTenantConnectionProvider multiTenantConnectionProvider, CurrentTenantIdentifierResolver tenantIdentifierResolver) { LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean(); emfBean.setDataSource(dataSource); emfBean.setPackagesToScan(VistoJobsApplication.class.getPackage().getName()); emfBean.setJpaVendorAdapter(jpaVendorAdapter()); Map<String, Object> jpaProperties = new HashMap<>(); jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA); jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider); jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdentifierResolver); emfBean.setJpaPropertyMap(jpaProperties); return emfBean; } }
사용하고있는 데이터 소스는 스프링 부트에 의해 자동으로 사용 가능하게됩니다.
이게 도움이 되길 바란다.
-
==============================
2.좋아, 이걸 마무리 지으면 다음과 같이 끝났어. 나는 간단한 CurrentTenantIdentifierResolver를 사용한다. 그리고 다른 곳에서 DataSource를 내 MultiTenantConnectionProviderImpl에 주입하려고 시도하는 대신 ConnectionProvider에서 DataSource (c3p0 ComboPooledDatasource)를 만들고 내 ConnectionProvider에서 제공하는 연결 만 사용하여 시작했습니다. 그래서 나는 여분의 DataSource를 제거했다. DataSource의 속성을 쉽게 구성 할 수 있도록 속성 파일에서 구성 데이터를 가져 오는 것을 선택했습니다.
좋아, 이걸 마무리 지으면 다음과 같이 끝났어. 나는 간단한 CurrentTenantIdentifierResolver를 사용한다. 그리고 다른 곳에서 DataSource를 내 MultiTenantConnectionProviderImpl에 주입하려고 시도하는 대신 ConnectionProvider에서 DataSource (c3p0 ComboPooledDatasource)를 만들고 내 ConnectionProvider에서 제공하는 연결 만 사용하여 시작했습니다. 그래서 나는 여분의 DataSource를 제거했다. DataSource의 속성을 쉽게 구성 할 수 있도록 속성 파일에서 구성 데이터를 가져 오는 것을 선택했습니다.
CurrentTenantIdentifierResolver Impl :
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver { /** * The method returns the RequestServerName as tenantidentifier. * If no FacesContext is available null is returned. * * @return String tenantIdentifier */ @Override public String resolveCurrentTenantIdentifier() { if (FacesContext.getCurrentInstance() != null){ return FacesContext.getCurrentInstance().getExternalContext().getRequestServerName(); } else { return null; } } @Override public boolean validateExistingCurrentSessions() { return true; } }
MultiTenantConnectionProviderImpl :
PropertyUtil은 내 속성을 가져 오는 간단한 로컬 도우미 클래스에 불과합니다. 그것이 특별한 것이 아니기 때문에 나는 대답을 어지럽히 지 않기 위해 그것을 포함시키지 않을 것입니다.
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider { private static final long serialVersionUID = 8074002161278796379L; private static Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class ); private ComboPooledDataSource cpds; private Properties properties; /** * * Constructor. Initializes the ComboPooledDataSource based on the config.properties. * * @throws PropertyVetoException */ public MultiTenantConnectionProviderImpl() throws PropertyVetoException { log.info("Initializing Connection Pool!"); properties = new Properties(); try { properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties")); } catch (IOException e) { throw new RuntimeException(e); } cpds = new ComboPooledDataSource("Example"); cpds.setDriverClass(properties.getProperty("jdbc.driver")); cpds.setJdbcUrl(properties.getProperty("jdbc.url")); cpds.setUser(properties.getProperty("jdbc.user")); cpds.setPassword(PropertyUtil.getCredential("jdbc.password")); log.info("Connection Pool initialised!"); } @Override public Connection getAnyConnection() throws SQLException { log.debug("Get Default Connection:::Number of connections (max: busy - idle): {} : {} - {}",new int[]{cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()}); if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){ log.warn("Maximum number of connections opened"); } if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){ log.error("Connection pool empty!"); } return cpds.getConnection(); } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { log.debug("Get {} Connection:::Number of connections (max: busy - idle): {} : {} - {}",new Object[]{tenantIdentifier, cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()}); if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){ log.warn("Maximum number of connections opened"); } if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){ log.error("Connection pool empty!"); } return cpds.getConnection(tenantIdentifier, PropertyUtil.getCredential(tenantIdentifier)); } @Override public void releaseAnyConnection(Connection connection) throws SQLException { connection.close(); } @Override public void releaseConnection(String tenantIdentifier, Connection connection){ try { this.releaseAnyConnection(connection); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public boolean supportsAggressiveRelease() { return false; } @SuppressWarnings("rawtypes") @Override public boolean isUnwrappableAs(Class unwrapType) { return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType ); } @SuppressWarnings("unchecked") @Override public <T> T unwrap(Class<T> unwrapType) { if ( isUnwrappableAs( unwrapType ) ) { return (T) this; } else { throw new UnknownUnwrapTypeException( unwrapType ); } } }
c3p0 특정 구성은 c3p0-config.xml에서 가져옵니다.
<c3p0-config> <named-config name="Example"> <property name="acquireIncrement">3</property> <property name="preferredTestQuery">SELECT 1</property> <property name="checkoutTimeout">2000</property> <property name="idleConnectionTestPeriod">30</property> <property name="initialPoolSize">1</property> <property name="maxIdleTime">18000</property> <property name="maxPoolSize">30</property> <property name="minPoolSize">1</property> <property name="maxStatements">50</property> <property name="testConnectionOnCheckin">true</property> </named-config> </c3p0-config>
그리고 db 특성은 config.properties 파일에 의해 제공됩니다.
jdbc.url=<serverUrl> jdbc.driver=<driverClass> jdbc.dbName=<dBname> jdbc.dbowner=<dbo> jdbc.username=<user> jdbc.password=<password> hibernate.dialect=<hibernateDialect> hibernate.debug=false
자격 증명은 다른 파일과 비슷한 방식으로 가져옵니다.
개선 사항을 제공하는 모든 의견을 보내 주시면 감사하겠습니다.
-
==============================
3.
대신 대신 <bean id="multiTenantConnectionProvider" class="test.MultiTenantConnectionProviderImpl"/> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="packagesToScan" value="test.models" /> <property name="hibernateProperties"> <map> <entry key="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL82Dialect"/> <entry key="hibernate.multiTenancy" value="SCHEMA"/> <entry key="hibernate.tenant_identifier_resolver" value="test.CurrentTenantIdentifierResolverImpl"/> <entry key="hibernate.multi_tenant_connection_provider" value-ref="multiTenantConnectionProvider"/> </map> </property> </bean>
-
==============================
4.Spring Framework 버전 3.2.4부터는 Spring 컨테이너가 MultiTenantConnectionProvider 및 CurrentTenantIdentifierResolver를 관리 할 방법이 없습니다. 이로 인해 이미 구성된 DataSource, WebContext 및 다른 Spring 관리 Bean 및 기능을 사용하는 것과 같은 많은 장애가 발생합니다. 나는보다 깨끗한 솔루션을 찾으려고 노력했지만 오직 하나만 나와 있습니다.
Spring Framework 버전 3.2.4부터는 Spring 컨테이너가 MultiTenantConnectionProvider 및 CurrentTenantIdentifierResolver를 관리 할 방법이 없습니다. 이로 인해 이미 구성된 DataSource, WebContext 및 다른 Spring 관리 Bean 및 기능을 사용하는 것과 같은 많은 장애가 발생합니다. 나는보다 깨끗한 솔루션을 찾으려고 노력했지만 오직 하나만 나와 있습니다.
org.springframework.orm.hibernate4.LocalSessionFactoryBuilder를 확장하고 커스텀 LocalSessionFactoryBean을 작성합니다 (서브 클래스화할 수없고 LocalSessionFactoryBuilder를 제공 할 수 있습니다.
여기에 간다 :
package com.levitech.hibernate; import javax.sql.DataSource; import org.hibernate.cfg.Environment; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; import org.springframework.core.io.ResourceLoader; public class CustomLocalSessionFactoryBuilder extends org.springframework.orm.hibernate4.LocalSessionFactoryBuilder { public CustomLocalSessionFactoryBuilder(DataSource dataSource,ResourceLoader resourceLoader, MultiTenantConnectionProvider connectionProvider, CurrentTenantIdentifierResolver tenantIdResolver) { super(dataSource, resourceLoader); getProperties().put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider); getProperties().put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdResolver); } }
LocalSessionFactoryBean을 대체합니다 (유일한 변경은 맞춤 LocalSessionFactoryBuilder를 사용하는 afterPropertiesSet () 메소드에 있음).
package com.levitech.hibernate; /* * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.File; import java.io.IOException; import java.util.Properties; import javax.sql.DataSource; import org.hibernate.Interceptor; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.NamingStrategy; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.orm.hibernate4.HibernateExceptionTranslator; import org.springframework.orm.hibernate4.LocalSessionFactoryBuilder; /** * {@link org.springframework.beans.factory.FactoryBean} that creates a Hibernate * {@link org.hibernate.SessionFactory}. This is the usual way to set up a shared * Hibernate SessionFactory in a Spring application context; the SessionFactory can * then be passed to Hibernate-based data access objects via dependency injection. * * <p><b>NOTE:</b> This variant of LocalSessionFactoryBean requires Hibernate 4.0 or higher. * It is similar in role to the same-named class in the {@code orm.hibernate3} package. * However, in practice, it is closer to {@code AnnotationSessionFactoryBean} since * its core purpose is to bootstrap a {@code SessionFactory} from annotation scanning. * * <p><b>NOTE:</b> To set up Hibernate 4 for Spring-driven JTA transactions, make * sure to either specify the {@link #setJtaTransactionManager "jtaTransactionManager"} * bean property or to set the "hibernate.transaction.factory_class" property to * {@link org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory}. * Otherwise, Hibernate's smart flushing mechanism won't work properly. * * @author Juergen Hoeller * @since 3.1 * @see #setDataSource * @see #setPackagesToScan * @see LocalSessionFactoryBuilder */ public class CustomLocalSessionFactoryBean extends HibernateExceptionTranslator implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean { private MultiTenantConnectionProvider multiTenantConnectionProvider; private CurrentTenantIdentifierResolver tenantIdResolver; private DataSource dataSource; private Resource[] configLocations; private String[] mappingResources; private Resource[] mappingLocations; private Resource[] cacheableMappingLocations; private Resource[] mappingJarLocations; private Resource[] mappingDirectoryLocations; private Interceptor entityInterceptor; private NamingStrategy namingStrategy; private Properties hibernateProperties; private Class<?>[] annotatedClasses; private String[] annotatedPackages; private String[] packagesToScan; private Object jtaTransactionManager; private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); private Configuration configuration; private SessionFactory sessionFactory; public MultiTenantConnectionProvider getMultiTenantConnectionProvider() { return multiTenantConnectionProvider; } public void setMultiTenantConnectionProvider( MultiTenantConnectionProvider multiTenantConnectionProvider) { this.multiTenantConnectionProvider = multiTenantConnectionProvider; } public CurrentTenantIdentifierResolver getTenantIdResolver() { return tenantIdResolver; } public void setTenantIdResolver(CurrentTenantIdentifierResolver tenantIdResolver) { this.tenantIdResolver = tenantIdResolver; } /** * Set the DataSource to be used by the SessionFactory. * If set, this will override corresponding settings in Hibernate properties. * <p>If this is set, the Hibernate settings should not define * a connection provider to avoid meaningless double configuration. */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Set the location of a single Hibernate XML config file, for example as * classpath resource "classpath:hibernate.cfg.xml". * <p>Note: Can be omitted when all necessary properties and mapping * resources are specified locally via this bean. * @see org.hibernate.cfg.Configuration#configure(java.net.URL) */ public void setConfigLocation(Resource configLocation) { this.configLocations = new Resource[] {configLocation}; } /** * Set the locations of multiple Hibernate XML config files, for example as * classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml". * <p>Note: Can be omitted when all necessary properties and mapping * resources are specified locally via this bean. * @see org.hibernate.cfg.Configuration#configure(java.net.URL) */ public void setConfigLocations(Resource[] configLocations) { this.configLocations = configLocations; } /** * Set Hibernate mapping resources to be found in the class path, * like "example.hbm.xml" or "mypackage/example.hbm.xml". * Analogous to mapping entries in a Hibernate XML config file. * Alternative to the more generic setMappingLocations method. * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see #setMappingLocations * @see org.hibernate.cfg.Configuration#addResource */ public void setMappingResources(String[] mappingResources) { this.mappingResources = mappingResources; } /** * Set locations of Hibernate mapping files, for example as classpath * resource "classpath:example.hbm.xml". Supports any resource location * via Spring's resource abstraction, for example relative paths like * "WEB-INF/mappings/example.hbm.xml" when running in an application context. * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see org.hibernate.cfg.Configuration#addInputStream */ public void setMappingLocations(Resource[] mappingLocations) { this.mappingLocations = mappingLocations; } /** * Set locations of cacheable Hibernate mapping files, for example as web app * resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location * via Spring's resource abstraction, as long as the resource can be resolved * in the file system. * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see org.hibernate.cfg.Configuration#addCacheableFile(java.io.File) */ public void setCacheableMappingLocations(Resource[] cacheableMappingLocations) { this.cacheableMappingLocations = cacheableMappingLocations; } /** * Set locations of jar files that contain Hibernate mapping resources, * like "WEB-INF/lib/example.hbm.jar". * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see org.hibernate.cfg.Configuration#addJar(java.io.File) */ public void setMappingJarLocations(Resource[] mappingJarLocations) { this.mappingJarLocations = mappingJarLocations; } /** * Set locations of directories that contain Hibernate mapping resources, * like "WEB-INF/mappings". * <p>Can be used to add to mappings from a Hibernate XML config file, * or to specify all mappings locally. * @see org.hibernate.cfg.Configuration#addDirectory(java.io.File) */ public void setMappingDirectoryLocations(Resource[] mappingDirectoryLocations) { this.mappingDirectoryLocations = mappingDirectoryLocations; } /** * Set a Hibernate entity interceptor that allows to inspect and change * property values before writing to and reading from the database. * Will get applied to any new Session created by this factory. * @see org.hibernate.cfg.Configuration#setInterceptor */ public void setEntityInterceptor(Interceptor entityInterceptor) { this.entityInterceptor = entityInterceptor; } /** * Set a Hibernate NamingStrategy for the SessionFactory, determining the * physical column and table names given the info in the mapping document. * @see org.hibernate.cfg.Configuration#setNamingStrategy */ public void setNamingStrategy(NamingStrategy namingStrategy) { this.namingStrategy = namingStrategy; } /** * Set Hibernate properties, such as "hibernate.dialect". * <p>Note: Do not specify a transaction provider here when using * Spring-driven transactions. It is also advisable to omit connection * provider settings and use a Spring-set DataSource instead. * @see #setDataSource */ public void setHibernateProperties(Properties hibernateProperties) { this.hibernateProperties = hibernateProperties; } /** * Return the Hibernate properties, if any. Mainly available for * configuration through property paths that specify individual keys. */ public Properties getHibernateProperties() { if (this.hibernateProperties == null) { this.hibernateProperties = new Properties(); } return this.hibernateProperties; } /** * Specify annotated entity classes to register with this Hibernate SessionFactory. * @see org.hibernate.cfg.Configuration#addAnnotatedClass(Class) */ public void setAnnotatedClasses(Class<?>[] annotatedClasses) { this.annotatedClasses = annotatedClasses; } /** * Specify the names of annotated packages, for which package-level * annotation metadata will be read. * @see org.hibernate.cfg.Configuration#addPackage(String) */ public void setAnnotatedPackages(String[] annotatedPackages) { this.annotatedPackages = annotatedPackages; } /** * Specify packages to search for autodetection of your entity classes in the * classpath. This is analogous to Spring's component-scan feature * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}). */ public void setPackagesToScan(String... packagesToScan) { this.packagesToScan = packagesToScan; } /** * Set the Spring {@link org.springframework.transaction.jta.JtaTransactionManager} * or the JTA {@link javax.transaction.TransactionManager} to be used with Hibernate, * if any. * @see LocalSessionFactoryBuilder#setJtaTransactionManager */ public void setJtaTransactionManager(Object jtaTransactionManager) { this.jtaTransactionManager = jtaTransactionManager; } public void setResourceLoader(ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); } public void afterPropertiesSet() throws IOException { LocalSessionFactoryBuilder sfb = new CustomLocalSessionFactoryBuilder(this.dataSource, this.resourcePatternResolver, multiTenantConnectionProvider, tenantIdResolver); if (this.configLocations != null) { for (Resource resource : this.configLocations) { // Load Hibernate configuration from given location. sfb.configure(resource.getURL()); } } if (this.mappingResources != null) { // Register given Hibernate mapping definitions, contained in resource files. for (String mapping : this.mappingResources) { Resource mr = new ClassPathResource(mapping.trim(), this.resourcePatternResolver.getClassLoader()); sfb.addInputStream(mr.getInputStream()); } } if (this.mappingLocations != null) { // Register given Hibernate mapping definitions, contained in resource files. for (Resource resource : this.mappingLocations) { sfb.addInputStream(resource.getInputStream()); } } if (this.cacheableMappingLocations != null) { // Register given cacheable Hibernate mapping definitions, read from the file system. for (Resource resource : this.cacheableMappingLocations) { sfb.addCacheableFile(resource.getFile()); } } if (this.mappingJarLocations != null) { // Register given Hibernate mapping definitions, contained in jar files. for (Resource resource : this.mappingJarLocations) { sfb.addJar(resource.getFile()); } } if (this.mappingDirectoryLocations != null) { // Register all Hibernate mapping definitions in the given directories. for (Resource resource : this.mappingDirectoryLocations) { File file = resource.getFile(); if (!file.isDirectory()) { throw new IllegalArgumentException( "Mapping directory location [" + resource + "] does not denote a directory"); } sfb.addDirectory(file); } } if (this.entityInterceptor != null) { sfb.setInterceptor(this.entityInterceptor); } if (this.namingStrategy != null) { sfb.setNamingStrategy(this.namingStrategy); } if (this.hibernateProperties != null) { sfb.addProperties(this.hibernateProperties); } if (this.annotatedClasses != null) { sfb.addAnnotatedClasses(this.annotatedClasses); } if (this.annotatedPackages != null) { sfb.addPackages(this.annotatedPackages); } if (this.packagesToScan != null) { sfb.scanPackages(this.packagesToScan); } if (this.jtaTransactionManager != null) { sfb.setJtaTransactionManager(this.jtaTransactionManager); } // Build SessionFactory instance. this.configuration = sfb; this.sessionFactory = buildSessionFactory(sfb); } /** * Subclasses can override this method to perform custom initialization * of the SessionFactory instance, creating it via the given Configuration * object that got prepared by this LocalSessionFactoryBean. * <p>The default implementation invokes LocalSessionFactoryBuilder's buildSessionFactory. * A custom implementation could prepare the instance in a specific way (e.g. applying * a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass. * @param sfb LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean * @return the SessionFactory instance * @see LocalSessionFactoryBuilder#buildSessionFactory */ protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) { return sfb.buildSessionFactory(); } /** * Return the Hibernate Configuration object used to build the SessionFactory. * Allows for access to configuration metadata stored there (rarely needed). * @throws IllegalStateException if the Configuration object has not been initialized yet */ public final Configuration getConfiguration() { if (this.configuration == null) { throw new IllegalStateException("Configuration not initialized yet"); } return this.configuration; } public SessionFactory getObject() { return this.sessionFactory; } public Class<?> getObjectType() { return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class); } public boolean isSingleton() { return true; } public void destroy() { this.sessionFactory.close(); } }
응용 프로그램 컨텍스트에서 bean을 정의하십시오.
<bean id="multiTenantProvider" class="com.levitech.hibernate.MultiTenantConnectionProviderImpl" depends-on="myDataSource" lazy-init="false"></bean> <bean id="tenantIdResolver" class="com.levitech.hibernate.TenantIdResolver"></bean> <bean id="sessionFactory" class="com.levitech.hibernate.CustomLocalSessionFactoryBean" depends-on="liquibase, myDataSource, multiTenantProvider"> <property name="dataSource" ref="myDataSource"></property> <property name="multiTenantConnectionProvider" ref="multiTenantProvider"></property> <property name="tenantIdResolver" ref="tenantIdResolver"></property> <property name="mappingLocations" value="classpath*:hibernate/**/*.hbm.xml" /> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQL5Dialect hibernate.show_sql=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory hibernate.cache.use_query_cache=true hibernate.cache.use_second_level_cache=true hibernate.multiTenancy=SCHEMA </value> </property> </bean>
최대 절전 모드 속성에서 다음 값을 제공하지 마십시오. hibernate.tenant_identifier_resolver와 hibernate.multi_tenant_connection_provider
당신은 모두 준비가되어 있고 당신의 콩은 모두 봄 관리를하고 있습니다. 다시 DI를 자유롭게 사용할 수 있습니다! 희망이 사람을 도움이됩니다. 나는 Jira의 기능 요청서를 작성했다.
-
==============================
5.이 사람들의 반응과이 링크를 사용하여, 나는 스프링이나 C3P0 이외의 다른 것을 함께 사용하지 않았습니다.
이 사람들의 반응과이 링크를 사용하여, 나는 스프링이나 C3P0 이외의 다른 것을 함께 사용하지 않았습니다.
내 최대 절전 모드 설정에이 2 가지 속성을 추가해야했습니다.
properties.setProperty("hibernate.multiTenancy", "SCHEMA"); properties.setProperty("hibernate.multi_tenant_connection_provider", MultiTenantConnectionProviderImpl.class.getName());
HibernateUtils.java
import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Alex */ public class HibernateUtils { private static final Logger logger = LoggerFactory.getLogger(HibernateUtils.class); private static SessionFactory sessionFactory; static{ init(); } public static void init(){ try { Configuration configuration = new Configuration() .setProperties(ConnectionPropertiesUtils.getProperties()); ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry(); sessionFactory = configuration.buildSessionFactory(serviceRegistry); } catch (Exception e) { logger.error(e.getMessage()); } } public static Session getTenantSession(String tenant){ return getSession(tenant); } public static Session getAuthSession(){ return getSession("AUTH"); } public static Session getLogSession(){ return getSession("LOG"); } public static Session getConfigSession(){ return getSession("CONFIG"); } public static Session getSession(String tenant) throws HibernateException { if(sessionFactory == null){ init(); } return sessionFactory.withOptions().tenantIdentifier(tenant).openSession(); } @Deprecated public static Session getSession() throws HibernateException { if(sessionFactory == null){ init(); } return sessionFactory.openSession(); } }
그리고 MultiTenantConnectionProviderImpl.java
import com.mchange.v2.c3p0.ComboPooledDataSource; import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.SQLException; import org.hibernate.HibernateException; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider; import org.hibernate.service.spi.Stoppable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simplistic implementation for illustration purposes showing a single * connection pool used to serve multiple schemas using "connection altering". * Here we use the T-SQL specific USE command; Oracle users might use the ALTER * SESSION SET SCHEMA command; etc. */ public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, Stoppable { private static Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class); private ComboPooledDataSource cpds; public MultiTenantConnectionProviderImpl() throws PropertyVetoException { log.info("Initializing Connection Pool!"); cpds = new ComboPooledDataSource("Example"); cpds.setDriverClass(ConnectionPropertiesUtils.getProperty("hibernate.connection.driver_class")); cpds.setJdbcUrl(ConnectionPropertiesUtils.getProperty("hibernate.connection.url")); cpds.setUser(ConnectionPropertiesUtils.getProperty("hibernate.connection.username")); cpds.setPassword(ConnectionPropertiesUtils.getProperty("hibernate.connection.password")); log.info("Connection Pool initialised!"); } @Override public Connection getAnyConnection() throws SQLException { log.debug("Get Default Connection:::Number of connections (max: busy - idle): {} : {} - {}", new int[]{cpds.getMaxPoolSize(), cpds.getNumBusyConnectionsAllUsers(), cpds.getNumIdleConnectionsAllUsers()}); if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()) { log.warn("Maximum number of connections opened"); } if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers() == 0) { log.error("Connection pool empty!"); } return cpds.getConnection(); } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { final Connection connection = getAnyConnection(); try { //This is DB specific syntax. This work for MSSQL and MySQL //Oracle uses the ALTER SESSION SET SCHEMA command connection.createStatement().execute("USE " + tenantIdentifier); } catch (SQLException e) { throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e); } return connection; } @Override public void releaseAnyConnection(Connection connection) throws SQLException { connection.close(); } @Override public void releaseConnection(String tenantIdentifier, Connection connection) { try { this.releaseAnyConnection(connection); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public boolean supportsAggressiveRelease() { return false; } @SuppressWarnings("rawtypes") @Override public boolean isUnwrappableAs(Class unwrapType) { return ConnectionProvider.class.equals(unwrapType) || MultiTenantConnectionProvider.class.equals(unwrapType) || MultiTenantConnectionProviderImpl.class.isAssignableFrom(unwrapType); } @SuppressWarnings("unchecked") @Override public <T> T unwrap(Class<T> unwrapType) { if (isUnwrappableAs(unwrapType)) { return (T) this; } else { throw new UnknownUnwrapTypeException(unwrapType); } } public void stop() { cpds.close(); } }
from https://stackoverflow.com/questions/16213573/setting-up-a-multitenantconnectionprovider-using-hibernate-4-2-and-spring-3-1-1 by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] Spring Data JPA는 메소드 이름 해석을 사용하여 엔티티를 셀 수있는 방법이 있습니까? (0) | 2018.12.29 |
---|---|
[SPRING] Spring은 실제로 어떻게 부트 스트랩을합니까? (0) | 2018.12.29 |
[SPRING] 봄 부팅 + Oauth2 클라이언트 자격 증명 (0) | 2018.12.29 |
[SPRING] Spring Data JPA를 Spring Container 외부에서 어떻게 사용합니까? (0) | 2018.12.29 |
[SPRING] TestNG로 인한 스프링 의존성 삽입 (0) | 2018.12.29 |