#java #hibernate #apache-commons-dbcp #spring-orm
#java #переход в спящий режим #apache-commons-dbcp #spring-orm
Вопрос:
Я разработал работающее динамическое многопользовательское приложение, используя:
- Java 8
- Java-сервлет 3.1
- Spring 3.0.7-РЕЛИЗ (невозможно изменить версию)
- Режим гибернации 3.6.0.Окончательный (невозможно изменить версию)
- Общий dbcp2
Это первый раз, когда мне приходится самому создавать экземпляры объектов Spring, поэтому мне интересно, все ли я сделал правильно, или приложение взорвется у меня на глазах в неуказанную будущую дату во время производства.
В принципе, единая схема базы данных известна, но детали базы данных будут указаны пользователем во время выполнения. Они могут свободно указывать любое имя хоста / порта / базы данных / имя пользователя / пароль.
Вот рабочий процесс:
- Пользователь входит в веб-приложение, затем либо выбирает базу данных из известного списка, либо указывает пользовательскую базу данных (имя хоста / порт / и т.д.).
- Если режим гибернации
SessionFactory
собран успешно (или найден в кэше), то он сохраняется для сеанса пользователя с использованиемSourceContext#setSourceId(SourceId)
, после чего пользователь может работать с этой базой данных. - Если кто-либо выбирает / указывает ту же базу данных, возвращается та же кэшированная
AnnotationSessionFactoryBean
- Пользователь может переключать базы данных в любой момент.
- Когда пользователь отключается от пользовательской базы данных (или выходит из системы), кэшированные
AnnotationSessionFactoryBean
файлы удаляются / уничтожаются
Итак, будет ли следующее работать так, как задумано? Помощь и указатели приветствуются.
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());
}
}
Ответ №1:
В конце я расширил Spring AbstractRoutingDataSource
, чтобы иметь возможность динамически создавать источники данных «на лету». Я обновлю этот ответ полным кодом, как только все будет работать правильно. Мне нужно разобраться с парой последних вещей, но суть этого заключается в следующем:
@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 amp; 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);
// }
}
Поэтому я просто подключаю DynamicRoutingDataSource в качестве источника данных для Spring SessionFactoryBean вместе с TransactionManager и всем остальным, как обычно. Как я уже сказал, нужно следовать большему количеству кода.