В эту тяпницу зародилась у меня очередная бредовая идея. Очень часто в веб проектах возникает необходимость добавить возможность загрузки данных в БД через веб интерфейс. Проще всего сделать это путем загрузки на сервер какого-то sql файла. Или в моем случае это будет XML файл в формате DbUnit.
Итак, для начала надо понять как работает этот самый DbUnit. Есть такой интерфейс IDatabaseTester, который конфигуряется настройками соединения с БД, setUp и tearDown операциями. Ему передается объект типа IDataSet после чего можно вызывать метод onSetup() - выполнится setup операция, onTearDown() - tearDown операция.
Удобнее всего для решения поставленной задачи создать на стороне back-end-а сервис, который будет получать в качестве параметра некий InputStream и результатом работы которого будет обновленная (или нет) БД. Вот Мое решение:
// PopulationService.java
package ga.common.domain;
import java.io.InputStream;
/**
* @author Ivan Khalopik
* @version $Revision: 21 $ $Date: 2010-03-17 11:18:19 +0200 (Ср, 17 мар 2010) $
*/
public interface PopulationService {
void populate(InputStream inputStream);
}
// DbUnitPopulationService.java
package ga.common.domain;
import org.dbunit.IDatabaseTester;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import java.io.InputStream;
/**
* @author Ivan Khalopik
* @version $Revision: 21 $ $Date: 2010-03-17 11:18:19 +0200 (Ср, 17 мар 2010) $
*/
public class DbUnitPopulationService implements PopulationService {
private final IDatabaseTester databaseTester;
private final FlatXmlDataSetBuilder builder;
public DbUnitPopulationService(IDatabaseTester databaseTester) {
this.databaseTester = databaseTester;
builder = new FlatXmlDataSetBuilder();
}
public void populate(InputStream inputStream) {
try {
final IDataSet dataSet = builder.build(inputStream);
databaseTester.setDataSet(dataSet);
databaseTester.onSetup();
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
databaseTester.setDataSet(null);
}
}
}
Внутри сервиса на основе InputStream-а создается FlatXmlDataSet (ну или можно любой другой по желанию) и вызывается метод onSetup(). Можно пойти еще дальше и в секции catch выполнить метод onTearDown(). Однако для работы такого сервиса необходимо еще создать объект IDatabaseTester. Я предлагаю это сделать с помощью спрингового FactoryBean-а:
// DatabaseTesterFactoryBean.java
package ga.common.dbunit;
import org.dbunit.DataSourceDatabaseTester;
import org.dbunit.IDatabaseTester;
import org.dbunit.operation.DatabaseOperation;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import javax.sql.DataSource;
/**
* @author Ivan Khalopik
* @version $Revision: 21 $ $Date: 2010-03-17 11:18:19 +0200 (Ср, 17 мар 2010) $
*/
public class DatabaseTesterFactoryBean extends AbstractFactoryBean{
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Class getObjectType() {
return IDatabaseTester.class;
}
@Override
protected IDatabaseTester createInstance() throws Exception {
final IDatabaseTester tester = new DataSourceDatabaseTester(dataSource);
tester.setSetUpOperation(DatabaseOperation.REFRESH);
tester.setTearDownOperation(DatabaseOperation.NONE);
return tester;
}
}
Забыл сказать, что операции бывают следующие:
• NONE - ничего не делать
• UPDATE - апдейтнуть данные
• INSERT - заинсертить данные
• REFRESH - заинсертить те, которых не хватает, апдейтнуть те, которые уже есть
• DELETE - удалить данные
• DELETE_ALL - очистить БД
• CLEAN_INSERT то же, что DELETE_ALL, а затем INSERT
Я использовал здесь REFRESH на сетапе, так как в данном случае больше всего подходит.
• NONE - ничего не делать
• UPDATE - апдейтнуть данные
• INSERT - заинсертить данные
• REFRESH - заинсертить те, которых не хватает, апдейтнуть те, которые уже есть
• DELETE - удалить данные
• DELETE_ALL - очистить БД
• CLEAN_INSERT то же, что DELETE_ALL, а затем INSERT
Я использовал здесь REFRESH на сетапе, так как в данном случае больше всего подходит.
Итак, мы имеем сервис умеющий загружать данные в БД, теперь его можно применять на UI или в сервисах, например для автоинициализации БД при старте приложения. Например код страницы на Tapestry 5 будет выглядеть так:
// Populate.java
package ga.web.pages.security;
import ga.common.domain.PopulationService;
import ga.web.base.pages.AbstractPage;
import org.apache.tapestry5.annotations.Component;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.upload.services.UploadedFile;
/**
* @author Ivan Khalopik
* @version $Revision: 22 $ $Date: 2010-03-17 11:19:53 +0200 (Ср, 17 мар 2010) $
*/
public class Populate extends AbstractPage {
@Inject
private PopulationService populationService;
private UploadedFile file;
@Component
private Form populateForm;
public UploadedFile getFile() {
return file;
}
public void setFile(UploadedFile file) {
this.file = file;
}
public void onPopulate() {
if (populateForm.isValid()) {
populationService.populate(file.getStream());
}
}
}
Полный вариант исходников можно найти здесь