2016-10-24 5 views
0

Je suis venu avec une application multi-locataire dynamique de travail en utilisant:dynamique multi-locataires WebApp (Spring Hibernate)

  • Java 8
  • Java Servlet 3.1
  • Spring 3.0.7-RELEASE (ne peut pas changer la version)
  • Mise en veille prolongée 3.6.0.Final (ne peut pas changer la version)
  • Commons dbcp2

C'est la 1ère fois que je devais me instancier Spring objets Je me demande donc si je l'ai tout fait correctement ou si l'application va exploser dans mon visage à une date future non spécifiée pendant la production.

Fondamentalement, le schéma DataBase unique est connu, mais les détails de la base de données seront spécifiés lors de l'exécution par l'utilisateur. Ils sont libres de spécifier n'importe quel nom d'hôte/port/nom de DB/nom d'utilisateur/mot de passe.

est ici le flux de travail:

  • L'utilisateur se connecte à l'application Web alors soit choisit une base de données à partir d'une liste connue, ou spécifie une base de données personnalisée (nom d'hôte/port/etc.).
  • Si le Hibernate SessionFactory est construit avec succès (ou est trouvé dans le cache), alors il est persistant pour la session de l'utilisateur en utilisant SourceContext#setSourceId(SourceId) puis l'utilisateur peut travailler avec cette base de données.
  • Si quelqu'un choisit/spécifie la même base de données, le même mises en cache AnnotationSessionFactoryBean est retourné
  • L'utilisateur peut changer les bases de données à tout moment.
  • Lorsque l'utilisateur passe loin d'une commande DB (ou se déconnecte), les mises en cache AnnotationSessionFactoryBean s sont supprimés/détruits

Ainsi seront les travaux suivants comme prévu? L'aide et les pointeurs sont les bienvenus.

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

Répondre

0

À la fin j'étendu printemps AbstractRoutingDataSource pour pouvoir créer dynamiquement des sources de données à la volée . Je mettrai à jour cette réponse avec le code complet dès que tout fonctionnera correctement. J'ai deux ou trois dernières choses à régler, mais le point crucial de c'est comme suit:

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

Je câbler tout le DynamicRoutingDataSource comme source de données pour SessionFactoryBean de printemps avec un TransactionManager un tout le reste, comme d'habitude . Comme je l'ai dit, plus de code à suivre.