2016-10-24 4 views
0

Ich habe mit einem Arbeits dynamisch mit Multi-Tenant Anwendung kommen:Dynamische Multi-Tenant-WebApp (Frühjahr Hibernate)

  • Java 8
  • Java Servlet 3.1
  • Frühling 3.0.7-RELEASE (können nicht die Version ändern)
  • Hibernate 3.6.0.Final (können nicht die Version ändern)
  • Commons dbcp2

Dies ist das erste Mal, dass ich je hatte instanziiert Frühling Objekte mich so frage ich mich, wenn ich alles richtig oder wenn die App getan haben, wird die Luft sprengen in meinem Gesicht ein unspezifiziertes zukünftiges Datum während der Produktion.

Grundsätzlich ist das einzelne DataBase-Schema bekannt, aber die Datenbankdetails werden zur Laufzeit vom Benutzer angegeben. Sie können jeden Hostnamen/Port/DB-Namen/Benutzername/Passwort angeben.

Hier ist der Workflow:

  • Der Benutzer meldet sich auf der Web-App dann entweder wählt eine Datenbank aus einer bekannten Liste, oder gibt eine benutzerdefinierte Datenbank (Hostname/Port/etc.).
  • Wenn der Ruhezustand SessionFactory erfolgreich erstellt wurde (oder sich im Cache befindet), wird er für die Benutzersitzung unter Verwendung von SourceContext#setSourceId(SourceId) beibehalten. Der Benutzer kann dann mit dieser Datenbank arbeiten.
  • Wenn jemand die gleiche Datenbank auswählt/angibt, wird die gleiche zwischengespeicherte AnnotationSessionFactoryBean zurückgegeben
  • Der Benutzer kann Datenbanken jederzeit wechseln.
  • Wenn der Benutzer schaltet von einer benutzerdefinierten wegen DB (oder abmeldet) werden die zwischengespeicherten AnnotationSessionFactoryBean s entfernt/zerstört

wird also folgende Arbeiten wie vorgesehen? Hilfe und Hinweise sind sehr willkommen.

web.xml

<web-app version="3.1" ...> 
    <context-param> 
    <param-name>contextConfigLocation</param-name> 
    <param-value>classpath:applicationContext.xml</param-value> 
    </context-param> 

    <listener> 
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
    </listener> 
    <listener> <!-- Needed for SourceContext --> 
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> 
    </listener> 
<web-app> 

applicationContext.xml

<beans ...> 
    <tx:annotation-driven /> 
    <util:properties id="db" location="classpath:db.properties" /> <!-- driver/url prefix --> 
    <context:component-scan base-package="com.example.basepackage" /> 
</beans> 

UserDao.java

@Service 
public class UserDao implements UserDaoImpl { 
    @Autowired 
    private TemplateFactory templateFactory; 

    @Override 
    public void addTask() { 
     final HibernateTemplate template = templateFactory.getHibernateTemplate(); 
     final User user = (User) DataAccessUtils.uniqueResult(
       template.find("select distinct u from User u left join fetch u.tasks where u.id = ?", 1) 
     ); 

     final Task task = new Task("Do something"); 
     user.getTasks().add(task); 

     TransactionTemplate txTemplate = templateFactory.getTxTemplate(template); 
     txTemplate.execute(new TransactionCallbackWithoutResult() { 
      @Override 
      protected void doInTransactionWithoutResult(TransactionStatus status) { 
       template.save(task); 
       template.update(user); 
      } 
     }); 
    } 
} 

TemplateFactory.java

@Service 
public class TemplateFactory { 
    @Autowired 
    private SourceSessionFactory factory; 

    @Resource(name = "SourceContext") 
    private SourceContext srcCtx; // session scope, proxied bean 

    @Override 
    public HibernateTemplate getHibernateTemplate() { 
     LocalSessionFactoryBean sessionFactory = factory.getSessionFactory(srcCtx.getSourceId()); 

     return new HibernateTemplate(sessionFactory.getObject()); 
    } 

    @Override 
    public TransactionTemplate getTxTemplate(HibernateTemplate template) { 
     HibernateTransactionManager txManager = new HibernateTransactionManager(); 
     txManager.setSessionFactory(template.getSessionFactory()); 

     return new TransactionTemplate(txManager); 
    } 
} 

SourceContext.java

@Component("SourceContext") 
@Scope(value="session", proxyMode = ScopedProxyMode.INTERFACES) 
public class SourceContext { 
    private static final long serialVersionUID = -124875L; 

    private SourceId id; 

    @Override 
    public SourceId getSourceId() { 
     return id; 
    } 

    @Override 
    public void setSourceId(SourceId id) { 
     this.id = id; 
    } 
} 

SourceId. java

public interface SourceId { 
    String getHostname(); 

    int getPort(); 

    String getSID(); 

    String getUsername(); 

    String getPassword(); 

    // concrete class has proper hashCode/equals/toString methods 
    // which use all of the SourceIds properties above 
} 

SourceSessionFactory.java

@Service 
public class SourceSessionFactory { 
    private static Map<SourceId, AnnotationSessionFactoryBean> cache = new HashMap<SourceId, AnnotationSessionFactoryBean>(); 

    @Resource(name = "db") 
    private Properties db; 

    @Override 
    public LocalSessionFactoryBean getSessionFactory(SourceId id) { 
     synchronized (cache) { 
      AnnotationSessionFactoryBean sessionFactory = cache.get(id); 
      if (sessionFactory == null) { 
       return createSessionFactory(id); 
      } 
      else { 
       return sessionFactory; 
      } 
     } 
    } 

    private AnnotationSessionFactoryBean createSessionFactory(SourceId id) { 
     AnnotationSessionFactoryBean sessionFactory = new AnnotationSessionFactoryBean(); 
     sessionFactory.setDataSource(new CutomDataSource(id, db)); 
     sessionFactory.setPackagesToScan(new String[] { "com.example.basepackage" }); 
     try { 
      sessionFactory.afterPropertiesSet(); 
     } 
     catch (Exception e) { 
      throw new SourceException("Unable to build SessionFactory for:" + id, e); 
     } 

     cache.put(id, sessionFactory); 

     return sessionFactory; 
    } 

    public void destroy(SourceId id) { 
     synchronized (cache) { 
      AnnotationSessionFactoryBean sessionFactory = cache.remove(id); 
      if (sessionFactory != null) { 
       if (LOG.isInfoEnabled()) { 
        LOG.info("Releasing SessionFactory for: " + id); 
       } 

       try { 
        sessionFactory.destroy(); 
       } 
       catch (HibernateException e) { 
        LOG.error("Unable to destroy SessionFactory for: " + id); 
        e.printStackTrace(System.err); 
       } 
      } 
     } 
    } 
} 

CustomDataSource.java

public class CutomDataSource extends BasicDataSource { // commons-dbcp2 
    public CutomDataSource(SourceId id, Properties db) { 
     setDriverClassName(db.getProperty("driverClassName")); 
     setUrl(db.getProperty("url") + id.getHostname() + ":" + id.getPort() + ":" + id.getSID()); 
     setUsername(id.getUsername()); 
     setPassword(id.getPassword()); 
    } 
} 

Antwort

0

Am Ende verlängert ich Spring AbstractRoutingDataSource der Lage sein, dynamisch Datenquellen on the fly zu erstellen . Ich werde diese Antwort mit dem vollständigen Code aktualisieren, sobald alles korrekt funktioniert. Ich habe ein paar letzte Dinge zu klären, aber der Kernpunkt ist wie folgt:

@Service 
public class DynamicRoutingDataSource extends AbstractRoutingDataSource { 

    // this is pretty much the same as the above SourceSessionFactory 
    // but with a map of CustomDataSources instead of 
    // AnnotationSessionFactoryBeans 
    @Autowired 
    private DynamicDataSourceFactory dataSourceFactory; 

    // This is the sticky part. I currently have a workaround instead. 
    // Hibernate needs an actual connection upon spring startup & there's 
    // also no session in place during spring initialization. TBC. 
    // @Resource(name = "UserContext") // scope session, proxy bean 
    private UserContext userCtx; // something that returns the DB config 

    @Override 
    protected SourceId determineCurrentLookupKey() { 
     return userCtx.getSourceId(); 
    } 

    @Override 
    protected CustomDataSource determineTargetDataSource() { 
     SourceId id = determineCurrentLookupKey(); 
     return dataSourceFactory.getDataSource(id); 
    } 

    @Override 
    public void afterPropertiesSet() { 
     // we don't need to resolve any data sources 
    } 

    // Inherited methods copied here to show what's going on 

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

Also ich verdrahten nur die DynamicRoutingDataSource als Datasource für Spring SessionFactoryBean zusammen mit einem Transaction eine ganze Rest wie üblich . Wie gesagt, mehr Code zu folgen.

Verwandte Themen