2015-05-05 2 views
4

У Grails есть плагин с несколькими арендаторами для единой базы данных и многопользовательский плагин для нескольких баз данных, но один для нескольких баз данных больше не поддерживается/поддерживается , Есть ли способ, которым я могу вместо этого использовать Spring или Hibernate для многозадачного многозадачного приложения Grails?Многопользовательская аренда Spring или Hibernate для приложений с множеством баз данных Grails

+0

Ответ является наиболее вероятным. И это зависит от ваших требований. Многопользовательская аренда - это не то же самое определение для всех. Чем больше информации о ваших требованиях вы можете предоставить лучший ответ, который вы можете получить. Возьмем, например, как вам нужно идентифицировать своего арендатора по запросу? Это в URL-адресе, основанном на хосте? Домен? Источник IP? Выбрано пользователем при входе в систему? Связано с логином? Центральная база данных для обеспечения безопасности и индивидуальная для каждого арендатора? И т. Д. –

+0

«Центральная база данных для обеспечения безопасности и индивидуальная для каждого арендатора?»: Да, Stormpath DB для обеспечения безопасности и наш собственный - возможно, индивидуальный - DB (ы) для информации арендатора. «Как вам нужно идентифицировать своего арендатора по запросу?«: Я надеялся сделать это на основе имени пользователя (т. Е.« Связанный с логином »). Организация/аренда будет выбрана при регистрации. Если это невозможно, пользователи могут войти в субдомен, специфичный для их аренды. – Daniel

+0

Другой вопрос: насколько сложно это сделать? Вам нужно быстро развернуть это, поэтому я рассматриваю возможность использования Django. – Daniel

ответ

9

Вы можете использовать Hibernate Multitenancy описано здесь http://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html/ch16.html

ИЛИ

Вы можете также рассмотреть вопрос о Спринга AbstractRoutingDataSource

Основная идея заключается в том, что DataSource маршрутизации выступает в качестве посредника - время «реальный» DataSource может быть определен динамически во время выполнения на основе ключа поиска.

https://spring.io/blog/2007/01/23/dynamic-datasource-routing/

Вы можете найти новые post, что дает примерное использование в спящий режим, суть решения, которое вы можете найти в следующих двух фрагментах

public class MyRoutingDataSource extends AbstractRoutingDataSource{ 
    @Override 
    protected Object determineCurrentLookupKey() { 
     String language = LocaleContextHolder.getLocale().getLanguage(); 
     System.out.println("Language obtained: "+ language); 
     return language; 
    } 
} 

возвращаемое значение будет используемый в качестве дискриминатора для источника данных, следующая конфигурация устанавливает отображение

<bean id="dataSource" class="com.howtodoinjava.controller.MyRoutingDataSource"> 
    <property name="targetDataSources"> 
     <map key-type="java.lang.String"> 
     <entry key="en" value-ref="concreteDataSourceOne"/> 
     <entry key="es" value-ref="concreteDataSourceTwo"/> 
     </map> 
    </property> 
</bean> 
+0

Интересно. Я не знал о Spring DataSource Routing. Я собираюсь это прочитать. сделайте пример.Если бы также был способ добавить/удалить dataSources во время работы приложения, то это потрясающе (по крайней мере, для чего мне нужно). Спасибо. В любом случае, я не думаю, что есть способ сделать это и все преимущества GORM. – ionutab

1

Вот как я использую hibernate multitenancy с SCHEMA. Может быть, это будет полезно для вас.

applicationContext.xml

... 
    <bean id="multiTenantConnectionProvider" class="org.myapp.MyAppMultiTenantConnectionProvider"/> 

    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> 
     <property name="packagesToScan" value="org.myapp.entities"/> 
     <property name="multiTenantConnectionProvider" ref="multiTenantConnectionProvider"/> 
     <property name="hibernateProperties"> 
      <props> 
       <prop key="hibernate.multiTenancy">SCHEMA</prop> 
       <prop key="hibernate.tenant_identifier_resolver">org.myapp.MyAppTenantIdentifierResolver</prop> 
       ... 
      </props> 
     </property> 
    </bean> 
    ... 

MyAppMultiTenantConnectionProvider.java

public class MyAppMultiTenantConnectionProvider implements MultiTenantConnectionProvider { 

    @Override 
    public Connection getConnection(String tenantIdentifier) throws SQLException { 
     //This is where your tenant resolving logic will be implemented 
     return MyMultitenantConnectionPool.getConnection(tenantIdentifier); 
    } 
} 

MyAppTenantIdentifierResolver.java

public class MyAppTenantIdentifierResolver implements CurrentTenantIdentifierResolver { 

    @Override 
    public String resolveCurrentTenantIdentifier() { 
     /* 
     This is where you determine which tenant to use. 
     In this app SpringSecurity used for this purpose. 
     TenantUser class extends org.springframework.security.core.userdetails.User with tenant information. 
     */ 
     Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 
     if (authentication == null || !authentication.isAuthenticated()) return ""; 

     if (authentication.getPrincipal() instanceof TenantUser) { 
      TenantUser user = (TenantUser) authentication.getPrincipal(); 
      return user.getTenant(); 
     } else return ""; 
    } 

    @Override 
    public boolean validateExistingCurrentSessions() { 
     return false; 
    } 
} 
+0

Прежде всего, я хочу поблагодарить вас. Во-вторых, я должен сказать, что я не очень хорошо разбираюсь в концепциях, которые вы здесь изложили, и что я немного смущен. Как класс «MyMultite» nantConnectionPool ". Есть ли какой-либо особый тип логики внутри него или он просто создает соединение в зависимости от арендатора? 2. Вызывается ли «getConnection» для создания экземпляра сеанса или для запроса? 3. Есть ли для этого общественная рабочая демонстрация? – ionutab

+0

1 & 2) Похоже, что getConnection() вызывается для каждого запроса, поэтому «MyMultiTenantConnectionPool» должен реализовывать создание соединения и адекватную стратегию объединения. В нашем приложении используется отдельный db для хранения информации о арендаторах, статусе арендатора, соответствующих базах данных и т. Д. На данный момент у меня есть MyTenantManager для поддержки этой «мета» базы данных. Таким образом, MyMultitenantConnectionPool извлекает db-url из MyTenantManager и создает экземпляры соединений, используя метод starting. 3) Извините, но это коммерческий проект, и я не имею права публиковать источник. – Lea32

+0

Спасибо. Не уверен, что вы подразумеваете под «схемотехническим подходом» ...? Я думаю, что вы имели в виду общую базу данных для арендаторов с отдельной схемой для каждого арендатора, но ваш комментарий, похоже, указывает на другое. – Daniel

2

В нашем случае мы используем LocalContainerEntityManagerFactoryBean, где мы создаем multiTenantMySQLProvider.

<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory"> 
    <property name="dataSource" ref="dataSource"/> 
    <property name="jpaVendorAdapter"> 
     <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> 
    </property> 
    <property name="packagesToScan" value="domain"/> 
    <property name="jpaPropertyMap"> 
     <map> 
      <entry key="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" /> 
      <entry key="javax.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver" /> 
      <entry key="hibernate.show_sql" value="false" /> 
      <entry key="hibernate.multiTenancy" value="SCHEMA" /> 
      <entry key="hibernate.multi_tenant_connection_provider" value-ref="mySQLMultiTenantConnectionProvider" /> 
      <entry key="hibernate.tenant_identifier_resolver" value-ref="tenantIdentifierResolver" /> 
     </map> 
    </property> 
</bean> 




    <bean id="tenantService" 
     class="multitenancy.service.impl.TenantServiceImpl"> 
    <property name="defaultTenantId" value="${multitenancy.defaultTenantId}" /> 
    <property name="ldapTemplate" ref="ldapTemplate" /> 
</bean> 

<bean id="connectionProvider" 
     class="multitenancy.hibernate.ConnectionProviderImpl" lazy-init="false"> 
    <property name="dataSource" ref="dataSource" /> 
</bean> 

<bean id="mySQLMultiTenantConnectionProvider" 
     class="multitenancy.hibernate.MySQLMultiTenantConnectionProviderImpl" lazy-init="false"> 
    <property name="connectionProvider" ref="connectionProvider" /> 
    <property name="tenantIdentifierForAny" value="${multitenancy.tenantIdentifierForAny}" /> 
    <property name="schemaPrefix" value="${multitenancy.schemaPrefix}" /> 
</bean> 

<bean id="tenantIdentifierResolver" 
     class="multitenancy.hibernate.TenantIdentifierResolverImpl" lazy-init="false"> 
    <property name="tenantService" ref="tenantService" /> 
</bean> 

<bean id="tenantIdentifierSchedulerResolver" 
     class="security.impl.TenantIdentifierSchedulerResolverImpl" lazy-init="false"> 
    <property name="ldapTemplate" ref="ldapTemplate" /> 
</bean> 

А вот реализация MySQLMultiTenantConnectionProviderImpl

public class MySQLMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, ServiceRegistryAwareService, Stoppable { 


private static final Logger LOGGER = LoggerFactory.getLogger(MySQLMultiTenantConnectionProviderImpl.class); 

@Setter 
private ConnectionProvider connectionProvider; 

@Setter 
private String tenantIdentifierForAny; 

@Setter 
private String schemaPrefix; 

@Override 
public Connection getAnyConnection() throws SQLException { 
    return connectionProvider.getConnection(); 
} 

@Override 
public void releaseAnyConnection(Connection connection) throws SQLException { 
    connectionProvider.closeConnection(connection); 
} 

@Override 
public Connection getConnection(String tenantIdentifier) throws SQLException { 
    final Connection connection = getAnyConnection(); 
    String schema = schemaPrefix + tenantIdentifier; 
    try { 
     LOGGER.debug("setting schema in DB Connection : {}" , schema); 
     connection.createStatement().execute("USE " + schema); 
    } 
    catch (SQLException e) { 
     throw new HibernateException(
      "Could not alter JDBC connection to specified schema [" + schema + "]", e 
     ); 
    } 
    return connection; 
} 

@Override 
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { 
    try { 
     connection.createStatement().execute("USE " + tenantIdentifierForAny); 
    } 
    catch (SQLException e) { 
     LOGGER.error(" error on releaseConnection. The connection will be not closed. SQLException : {}" , e); 
     // on error, throw an exception to make sure the connection is not returned to the pool. 
     throw new HibernateException(
      "Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e 
     ); 
    } 
    // I follow the hibernate recommendation and we don't return the connetion to the pool. 
    connectionProvider.closeConnection(connection); 
} 

@Override 
public boolean supportsAggressiveRelease() { 
    return true; 
} 

@Override 
public void stop() { 

} 

@Override 
public boolean isUnwrappableAs(Class unwrapType) { 
    return ConnectionProvider.class.equals(unwrapType) || 
      MultiTenantConnectionProvider.class.equals(unwrapType) || 
      AbstractMultiTenantConnectionProvider.class.isAssignableFrom(unwrapType); 
} 

@Override 
public <T> T unwrap(Class<T> unwrapType) { 
    if (isUnwrappableAs(unwrapType)) { 
     return (T) this; 
    } 
    throw new UnknownUnwrapTypeException(unwrapType); 

} 

@Override 
public void injectServices(ServiceRegistryImplementor serviceRegistry) { 


} 

}

Смежные вопросы