2016-04-06 12 views
5

Ho un sistema in cui è presente un numero sconosciuto di tenant (diverse istanze di database sullo stesso server di database). Ho un codice funzionante nel quale un utente accede e il tenant corretto è selezionato, e posso leggere la tabella di configurazione per quel tenant.Tabelle di query tra più titolari (stesso nome tabella)

Desidero che l'applicazione al momento dell'inizio ricerchi tutti gli inquilini, legga la configurazione e agisca su di essa. Prima di passare a Spring Data JPA (supportato da ibernazione), questo era facile poiché stavo connettendo separatamente ogni istanza di database.

Non penso di poter utilizzare Spring @Transactional in quanto consente di impostare un'unica connessione.

Spero di utilizzare la stessa interfaccia di repository con lo stesso bean, poiché funziona quando devo solo colpire un tenant alla volta.

Ho un class MultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl che mi darà un dataSource per un dato tenant, ma non sono sicuro di come usarlo in un metodo della classe @Service?

+0

Quando si dice "leggere la configurazione" si intende una tabella nel database di ciascun titolare? Potresti dare un'occhiata a questo blog [post] (http://anakiou.blogspot.my/2015/08/multi-tenant-application-with-spring.html) che potrebbe darti qualche idea. Se si dispone di un elenco di origini dati, penso che non sarà difficile eseguirne il looping all'avvio dell'applicazione. – Mustafa

+0

utilizzando la "tabella di configurazione" come esempio potrebbe essere stata una cattiva scelta, perché può confondere le cose. In realtà ho alcune cose che dovranno essere lette in tutti gli inquilini per vari motivi. Il post del blog che fornisci suggerisce di conoscere le origini dati in primo piano, qualcosa che non conosco. –

+0

Per multi-tenant intendi qualcosa come avere una tabella chiamata 'users' che appare in più database come' dropbox_db', 'google_drive_db',' sky_drive_db' etc sul server del database? – Saheed

risposta

2

Non sono sicuro se dovrei rimuovere la mia risposta precedente, e ditalo o cosa. Quindi, se un MOD può farmi sapere la procedura corretta sarò felice di rispettare.

Risulta che avevo ragione sull'uso di @Transactional non funzionava. Ho finito per utilizzare un'implementazione personalizzata di e AbstractRoutingDataSource per sostituire il mio MultiTenantConnectionProviderImpl e CurrentTenantResolverImpl.Io uso questa nuova fonte di dati invece di impostare la hibernate.multiTenancyhibernate.multi_tenant_connection_provider e hibernate.tenant_identifier_resolver

La mia classe di modifica locale temporanea assomiglia a questo:

public class MultitenancyTemporaryOverride implements AutoCloseable 
{  
    static final ThreadLocal<String> tenantOverride = new NamedThreadLocal<>("temporaryTenantOverride"); 

    public void setCurrentTenant(String tenantId) 
    { 
     tenantOverride.set(tenantId); 
    } 

    public String getCurrentTenant() 
    { 
     return tenantOverride.get(); 
    } 

    @Override 
    public void close() throws Exception 
    { 
     tenantOverride.remove(); 
    } 
} 

mio TenantRoutingDataSource si presenta così:

@Component 
public class TenantRoutingDataSource extends AbstractDataSource implements InitializingBean 
{ 

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

    @Override 
    public Connection getConnection(String username, String password) throws SQLException 
    { 
     return determineTargetDataSource().getConnection(username, password); 
    } 

    @Override 
    public void afterPropertiesSet() throws Exception 
    { 
    } 

    protected String determineCurrentLookupKey() 
    { 
     Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 
     String database = "shared"; 
     if (authentication != null && authentication.getPrincipal() instanceof MyUser) 
     { 
      MyUser user = (MyUser) authentication.getPrincipal(); 
      database = user.getTenantId(); 
     } 
     String temporaryOverride = MultitenancyTemporaryOverride.tenantOverride.get(); 
     if (temporaryOverride != null) 
     { 
      database = temporaryOverride; 
     } 
     return database; 
    } 

    protected DataSource determineTargetDataSource() 
    { 
     return selectDataSource(determineCurrentLookupKey()); 
    } 

    public DataSource selectDataSource(String tenantIdentifier) 
    { 
     //I use C3P0 for my connection pool 
     PooledDataSource pds = C3P0Registry.pooledDataSourceByName(tenantIdentifier); 
     if (pds == null) 
      pds = getComboPooledDataSource(tenantIdentifier); 
     return pds; 
    } 

    private ComboPooledDataSource getComboPooledDataSource(String tenantIdentifier) 
    { 
     ComboPooledDataSource cpds = new ComboPooledDataSource(tenantIdentifier); 
     cpds.setJdbcUrl("A JDBC STRING HERE"); 
     cpds.setUser("MyDbUsername"); 
     cpds.setPassword("MyDbPassword"); 
     cpds.setInitialPoolSize(10); 
     cpds.setMaxConnectionAge(10000); 
     try 
     { 
      cpds.setDriverClass("com.informix.jdbc.IfxDriver"); 
     } 
     catch (PropertyVetoException e) 
     { 
      throw new RuntimeException("Weird error when setting the driver class", e); 
     } 
     return cpds; 
    } 
} 

poi ho appena forniscono la mia origine dati personalizzata al bean di fabbrica di Entity Manager durante la sua creazione.

@Service 
public class TestService 
{ 
    public void doSomeGets() 
    { 
     List<String> tenants = getListSomehow(); 
     try(MultitenancyTemporaryOverride tempOverride = new MultitenancyTemporaryOverride()) 
     { 
      for(String tenant : tenants) 
      { 
       tempOverride.setCurrentTenant(tenant); 
       //do some work here, which only applies to the tenant 
      } 
     } 
     catch (Exception e) 
     { 
      logger.error(e); 
     } 
    } 
} 
0

Penso di essere vicino a una soluzione, ma non ne sono completamente soddisfatto. Mi piacerebbe ricevere una risposta migliore.

Modificato: scopre questo non funziona del tutto, come la primavera o Hibernate sembra chiamare solo la corrente resolver identificatore inquilino una volta, non per ogni volta che un metodo viene chiamato @Transactional

Coinvolge cambiarne l'implementazione CurrentTenantIdentifierResolver non solo guardare l'utente corrente (se è impostato) per ottenere il proprio id titolare (fino a implementatore per capire come impostarlo) ... ha anche bisogno di guardare una variabile locale del thread per vedere se un override è stato impostato.

Utilizzando questo approccio, posso temporaneamente impostare il tenantID ... chiamare un metodo di servizio con il mio gestore di transazioni multi-tenancy specificato e quindi ottenere i dati.

mio Servizi Test:

@Service 
public class TestService 
{ 
    @Transactional(transactionManager = "sharedTxMgr") 
    public void doSomeGets() 
    { 
     List<String> tenants = getListSomehow(); 
     try(MultitenancyTemporaryOverride tempOverride = new MultitenancyTemporaryOverride()) 
     { 
      for(String tenant : tenants) 
      { 
       tempOverride.setCurrentTenant(tenant); 
       doTenantSpecificWork(); 
      } 
     } 
     catch (Exception e) 
     { 
      logger.error(e); 
     } 
    } 

    @Transactional(transactionManager = "tenantSpecificTxMgr") 
    public void doTenantSpecificWork() 
    { 
     //do some work here, which only applies to the tenant 
    } 
} 

La mia classe che avvolge l'impostazione ThreadLocal, attuazione AutoCloseable per contribuire a rendere variabile che sia ripulito

public class MultitenancyTemporaryOverride implements AutoCloseable 
{ 
    static final ThreadLocal<String> tenantOverride = new ThreadLocal<>(); 

    public void setCurrentTenant(String tenantId) 
    { 
     tenantOverride.set(tenantId); 
    } 

    public String getCurrentTenant() 
    { 
     return tenantOverride.get(); 
    } 

    @Override 
    public void close() throws Exception 
    { 
     tenantOverride.remove(); 
    } 

} 

mia implementazione inquilino risolutore che usa il filo locali

public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver 
{ 

    @Override 
    public String resolveCurrentTenantIdentifier() 
    { 
     Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 
     logger.debug(ToStringBuilder.reflectionToString(authentication)); 
     String database = "shared"; 
     if (authentication != null && authentication.getPrincipal() instanceof MyUser) 
     { 
      MyUser user = (MyUser) authentication.getPrincipal(); 
      database = user.getTenantId(); 
     } 
     String temporaryOverride = MultitenancyTemporaryOverride.tenantOverride.get(); 
     if(temporaryOverride != null) 
     { 
      database = temporaryOverride; 
     } 
     return database; 
    } 
Problemi correlati