2016-04-06 24 views
5

Mam system, w którym jest nieznana liczba najemców (różne instancje bazy danych na tym samym serwerze bazy danych). Mam działający kod, w którym użytkownik się loguje i wybrany jest właściwy najemca, i mogę odczytać tabelę konfiguracji dla tego lokatora.Tabele zapytań dla wielu najemców (ta sama nazwa tabeli)

Chcę, aby aplikacja w czasie uruchomienia przeglądała wszystkich lokatorów, odczytała konfigurację i podjęła działania. Przed przejściem do Spring Data JPA (wspieranego przez hibernację) było to łatwe, ponieważ łączyłem się osobno z każdą instancją bazy danych.

Nie sądzę, abym mógł użyć Spring @Transactional, ponieważ konfiguruje tylko jedno połączenie.

Mam nadzieję, że użyję tego samego interfejsu repozytorium z tym samym komponentem bean, ponieważ działa on tylko wtedy, gdy muszę uderzyć tylko jednego dzierżawcę naraz.

Mam class MultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl, który da mi źródło danych dla danego dzierżawcy, ale nie jestem pewien, jak go użyć w metodzie klasy @Service?

+0

Kiedy mówisz "czytaj konfigurację", masz na myśli tabelę w bazie danych każdego najemcy? Możesz zajrzeć do tego bloga [post] (http://anakiou.blogspot.my/2015/08/multi-tenant-application-with-spring.html), który może dać ci kilka pomysłów. Jeśli masz listę źródeł danych, myślę, że nie będzie trudno je przechwycić podczas uruchamiania aplikacji. – Mustafa

+0

używanie "tabeli konfiguracyjnej" jako przykładu mogło być złym wyborem, ponieważ może wprowadzać w błąd rzeczy. Rzeczywiście mam kilka rzeczy, które będą musiały być przeczytane przez wszystkich najemców z różnych powodów. Podany przez Ciebie wpis na blogu sugeruje znajomość źródeł danych z góry, o czym nie wiem. –

+0

Przez multi-najemca masz na myśli coś takiego jak posiadanie tabeli o nazwie "użytkownicy", która pojawia się w wielu bazach danych, takich jak 'dropbox_db',' google_drive_db', 'sky_drive_db' itp. Na serwerze bazy danych? – Saheed

Odpowiedz

2

Nie jestem pewien, czy powinienem usunąć moją poprzednią odpowiedź, e dit it lub co. Więc jeśli MOD może dać mi znać właściwą procedurę, z przyjemnością się zgodzę.

Okazuje się, że miałem rację co do korzystania z @Transactional nie będzie działać. Skończyło się na zastosowaniu niestandardowej implementacji i AbstractRoutingDataSource w celu zastąpienia moich MultiTenantConnectionProviderImpl i CurrentTenantResolverImpl.Używam tego nowego źródła danych zamiast ustawiania hibernate.multiTenancyhibernate.multi_tenant_connection_provider i hibernate.tenant_identifier_resolver

moją tymczasową klasę override wygląda następująco:

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(); 
    } 
} 

Moja TenantRoutingDataSource wygląda następująco:

@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; 
    } 
} 

potem po prostu dostarczyć moje niestandardowe źródło danych do mojego komponentu bean Entity Manager podczas jego tworzenia.

@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

Myślę, że jestem blisko jednego rozwiązania, ale nie jestem z niego całkowicie zadowolony. Chciałbym, aby pojawiła się lepsza odpowiedź.

edycja: okazuje to nie bardzo działa, jak wiosną lub hibernacji pojawia się tylko zadzwonić aktualny identyfikator lokator resolverowi raz, nie za każdym razem metoda @Transactional nazywa

To wiąże się ze zmianą realizację CurrentTenantIdentifierResolver nie tylko patrzy na aktualnego użytkownika (jeśli jest ustawiony), aby uzyskać bieżący identyfikator dzierżawcy (aż do implementatora, aby dowiedzieć się, jak to ustawić) ... musi również przyjrzeć się zmiennej lokalnej wątku, aby sprawdzić, czy zastąpiono była ustalona.

Korzystając z tego podejścia, mogę tymczasowo ustawić tenantID ... wywołać metodę usługi z określonym przeze mnie menedżerem transakcji z wieloma dzierżawcami, a następnie pobrać dane.

Moje usługi 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 
    } 
} 

Moja klasa, która otacza ustawienie ThreadLocal, wdrażanie AutoCloseable aby pomóc zmienną pewno jest czyszczone

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(); 
    } 

} 

Moje wykonanie lokator rozpoznawania, który używa nici lokalnej

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; 
    } 
Powiązane problemy