Автор оригинала: Vlad Mihalcea.
Вступление
Мне нравится интеграционное тестирование. Как я объяснил в этой статье , это хороший способ проверить, какие SQL-запросы генерируются Hibernate за кулисами. Но для интеграционных тестов требуется работающий сервер базы данных, и это первый выбор, который вы должны сделать.
Использование локального сервера баз данных, подобного производственному, для интеграционного тестирования
Для производственной среды я всегда предпочитаю использовать инкрементные сценарии DDL, так как я всегда могу знать, какая версия развернута на данном сервере и какие сценарии требуется развернуть. Я полагался на Flyway для управления обновлениями схемы для меня, и я очень доволен этим.
В небольшом проекте, где количество интеграционных тестов довольно невелико, вы также можете использовать для тестирования локальный сервер базы данных, подобный производственному. Это самый безопасный вариант, так как он гарантирует, что вы проводите тестирование в очень похожей среде с производственной настройкой.
Некоторые люди считают, что использование производственной среды повлияет на время выполнения теста, но это не так. В настоящее время вы можете использовать Docker с tmpfs для ускорения ваших тестов и выполнения их почти так же быстро, как с базой данных в памяти .
Тестирование интеграции базы данных в памяти
Другой вариант-выбрать базу данных в памяти для интеграционного тестирования. Существует множество баз данных в памяти, из которых вы можете выбрать: HSQLDB , H2 , Apache Derby , и это лишь некоторые из них.
Я использовал две стратегии генерации схем в памяти, у обеих из них есть плюсы и минусы, которые я собираюсь объяснить следующим образом.
Использование
При использовании режима гибернации генерацию схемы базы данных можно настроить с помощью свойства hibernate.hbm2ddl.auto
конфигурация.
Самый простой способ развертывания схемы-использовать параметр update
. Это полезно для целей тестирования. Я бы не стал полагаться на это в производственной среде, для которой инкрементные сценарии DDL являются гораздо лучшим подходом.
Давайте начнем с конфигурации JPA, которую вы можете найти в persistence.xml
файл:
org.hibernate.jpa.HibernatePersistenceProvider false
И конфигурация источника данных выглядит так:
${jdbc.username} ${jdbc.password} ${jdbc.url} ${jdbc.driverClassName}
Bitronix -очень надежный автономный менеджер транзакций JTA. При разработке приложений Java EE Диспетчер транзакций предоставляется Сервером приложений. Однако для проектов, основанных на Spring, нам необходимо использовать автономный менеджер транзакций, если нам нужно использовать транзакции XA.
При использовании JTA не рекомендуется смешивать XA и Локальные транзакции, так как не все источники данных XA позволяют работать внутри Локальной транзакции. К сожалению, каким бы простым ни был этот метод генерации DDL, у него есть один недостаток, который мне не слишком нравится. Я не могу отключить параметр allowLocalTransactions
, так как Hibernate создает сценарий DDL и обновляет его вне транзакции XA.
Еще одним недостатком является то, что у вас мало контроля над тем, что DDL-скрипт Hibernate развертывает от вашего имени, и в данном конкретном контексте мне не нравится ставить под угрозу гибкость по сравнению с удобством.
Если вы не используете JTA и вам не нужна гибкость при принятии решения о том, какая схема DDL будет развернута на вашем текущем сервере базы данных, то hibernate.hbm2ddl.auto=”обновление” , вероятно, ваш правильный выбор.
Гибкое развертывание схемы
Этот метод состоит из двух этапов. Первый заключается в том, чтобы Hibernate генерировал сценарии DDL, а второй-в их развертывании по индивидуальному заказу.
org.apache.maven.plugins maven-antrun-plugin generate-test-sql-scripts generate-test-resources run
Имея сценарии create_db.sql
и drop_db.sql
DDL, мы теперь должны развернуть их при запуске контекста Spring, и это делается с помощью следующего пользовательского служебного класса:
public class DatabaseScriptLifecycleHandler implements InitializingBean, DisposableBean { private static final org.slf4j.Logger log = LoggerFactory.getLogger(DatabaseScriptLifecycleHandler.class); private final Resource[] initScripts; private final Resource[] destroyScripts; private JdbcTemplate jdbcTemplate; @Autowired private TransactionTemplate transactionTemplate; private String sqlScriptEncoding = "UTF-8"; private String commentPrefix = "--"; private boolean continueOnError; private boolean ignoreFailedDrops; private boolean transactional = true; public DatabaseScriptLifecycleHandler(DataSource dataSource, Resource[] initScripts, Resource[] destroyScripts) { this.jdbcTemplate = new JdbcTemplate(dataSource); this.initScripts = initScripts; this.destroyScripts = destroyScripts; } public Resource[] getInitScripts() { return initScripts; } public Resource[] getDestroyScripts() { return destroyScripts; } public String getCommentPrefix() { return commentPrefix; } public void setCommentPrefix(String commentPrefix) { this.commentPrefix = commentPrefix; } public boolean isContinueOnError() { return continueOnError; } public void setContinueOnError(boolean continueOnError) { this.continueOnError = continueOnError; } public boolean isIgnoreFailedDrops() { return ignoreFailedDrops; } public void setIgnoreFailedDrops(boolean ignoreFailedDrops) { this.ignoreFailedDrops = ignoreFailedDrops; } public String getSqlScriptEncoding() { return sqlScriptEncoding; } public void setSqlScriptEncoding(String sqlScriptEncoding) { this.sqlScriptEncoding = sqlScriptEncoding; } public boolean isTransactional() { return transactional; } public void setTransactional(boolean transactional) { this.transactional = transactional; } public void afterPropertiesSet() throws Exception { initDatabase(); } public void destroy() throws Exception { destroyDatabase(); } public void initDatabase() { if (transactional) { transactionTemplate.execute(new TransactionCallback() { @Override public Void doInTransaction(TransactionStatus status) { runInitScripts(); return null; } }); } else { runInitScripts(); } } private void runInitScripts() { final ResourceDatabasePopulator resourceDatabasePopulator = createResourceDatabasePopulator(); jdbcTemplate.execute(new ConnectionCallback () { @Override public Void doInConnection(Connection con) throws SQLException, DataAccessException { resourceDatabasePopulator.setScripts(getInitScripts()); resourceDatabasePopulator.populate(con); return null; } }); } public void destroyDatabase() { if (transactional) { transactionTemplate.execute(new TransactionCallback () { @Override public Void doInTransaction(TransactionStatus status) { runDestroyScripts(); return null; } }); } else { runDestroyScripts(); } } private void runDestroyScripts() { final ResourceDatabasePopulator resourceDatabasePopulator = createResourceDatabasePopulator(); jdbcTemplate.execute(new ConnectionCallback () { @Override public Void doInConnection(Connection con) throws SQLException, DataAccessException { resourceDatabasePopulator.setScripts(getDestroyScripts()); resourceDatabasePopulator.populate(con); return null; } }); } protected ResourceDatabasePopulator createResourceDatabasePopulator() { ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator(); resourceDatabasePopulator.setCommentPrefix(getCommentPrefix()); resourceDatabasePopulator.setContinueOnError(isContinueOnError()); resourceDatabasePopulator.setIgnoreFailedDrops(isIgnoreFailedDrops()); resourceDatabasePopulator.setSqlScriptEncoding(getSqlScriptEncoding()); return resourceDatabasePopulator; } }
который настроен как:
На этот раз мы можем избавиться от любой локальной транзакции, чтобы безопасно установить:
Код доступен на GitHub .