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