Unittest a transactional method with REQUIRES_NEW and always force rollback - spring

I want to test a service-method that inserts data into a table by calling a DAO in a loop. The service-method is annotated with
#Transactional(propagation = Propagation.REQUIRES_NEW)
The unit-test calls the service-method and is annotated with
#Transactional
Now I would like to tell the transaction that it always should do a rollback at the end. I don't want to clean up the db manually after the testruns.
#Rollback and EntityManager.getTransaction().setRollbackOnly() does'nt work. I think the reason is that the annotation and setRollbackOnly() are only applied on the Transaction that is created by the test-method and not on the transaction that is created by the service-method.
Does anyone know how to solve that?

I don't think it's possible to easily rollback a REQUIRES_NEW transaction. SpringTest starts a transaction and it can rollback the transaction that it started. But not the transactions started inside.
So you either may fall back to REQUIRED or write your tests to work fine even if they commit. If you choose the latter, you can achieve test isolation via randomization.

You can probably create a controllable mock implementation of org.springframework.transaction.PlatformTransactionManager delegating to the real one with only single difference that it would optionally mark a new transaction as read-only. Something like that if using java-based context configuration:
...
public class DaoTest {
...
#Autowired
Dao _dao;
#Autowired
MockPlatformTransactionManager _transactionManager;
...
#Test
#Transactional
public void testSomeAction() throws Exception
{
try (AutoCloseable ignored = _transactionManager.withRollBack())
{
_dao.someAction();
}
}
...
interface MockPlatformTransactionManager extends PlatformTransactionManager
{
AutoCloseable withRollBack();
}
...
// Somewhere in the #Configuration class
#Bean
MockPlatformTransactionManager transactionManager()
{
// an instance of real TM is adapted to become a MockPlatformTransactionManager
return new MockPlatformTransactionManager()
{
private final PlatformTransactionManager _delegate =
// TODO: same transaction manager as before
new DataSourceTransactionManager(dataSource());
private boolean shouldRollBack = false;
#Override
public TransactionStatus getTransaction(final TransactionDefinition definition)
throws TransactionException
{
final TransactionStatus transaction = _delegate.getTransaction(definition);
if (shouldRollBack)
transaction.setRollbackOnly();
return transaction;
}
#Override
public void commit(final TransactionStatus status) throws TransactionException
{
_delegate.commit(status);
}
#Override
public void rollback(final TransactionStatus status) throws TransactionException
{
_delegate.rollback(status);
}
#Override
public AutoCloseable withRollBack()
{
shouldRollBack = true;
return new AutoCloseable()
{
#Override
public void close() throws Exception
{
shouldRollBack = false;
}
};
}
};
}

If you use a PlatformTransactionManager implementation that permits to set the UserTransaction (like org.springframework.transaction.jta.JtaTransactionManager) you can instantiate an UserTransaction implementation which commit method does a rollback.
import com.atomikos.icatch.jta.UserTransactionImp;
#Bean
public UserTransaction myUserTransaction() {
final UserTransactionImp userTransactionImp = new UserTransactionImp() {
#Override
public void commit() throws javax.transaction.RollbackException, javax.transaction.HeuristicMixedException,
javax.transaction.HeuristicRollbackException, javax.transaction.SystemException, java.lang.IllegalStateException,
java.lang.SecurityException {
rollback();
}
};
return userTransactionImp;
}
And then, in your PlatformTransactionManager bean:
#Bean
public PlatformTransactionManager transactionManager(
#Qualifier("myUserTransaction") UserTransaction myUserTransaction,
#Qualifier("myTransactionManager") TransactionManager myTransactionManager
) {
final JtaTransactionManager jtaTm = new JtaTransactionManager();
jtaTm.setTransactionManager(myTransactionManager);
jtaTm.setUserTransaction(myUserTransaction);
return jtaTm;
}
I've also made my dao does not make any flush.

Related

How to rollback transaction from service class?

I am trying to rollback for some condition by throwing exception. But i can not find proper way to do this. Here is my service class
#Service
public class UserManager implements IUserManager {
private final IBasicEM basicEM;
public ApplicantInfoManager(IBasicEM basicEM) {
this.basicEM = basicEM;
}
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public boolean purgeUser(Long id) throws Exception {
//business logic
basicEM.Update(entity)
if(//condition) {
throw New RollBackException("//message")
}
//business logic
}
}
And here is my BasicEM class
#Component
#Transactional(value = "transactionManager", rollbackFor = Exception.class)
public class BasicEM {
#PersistenceContext(unitName = "db1")
private EntityManager em;
public void update(Object target) {
em.merge(target);
}
}
So what i want is to call that update method, then if the condition is true undo the update.
My intention is that when i throw the exception, the transaction ends and does not commit the update. But i am wrong and the update is already done. Please help me to achieve my goal.
In order to achieve what you want you will need to have a transaction already in the Service method. The default propagation type for #Transactional(value = "transactionManager", rollbackFor = Exception.class) is Propagation.REQUIRED which means that if your Service is already included in a transaction, the basicEM.Update(entity) will also be included in such transaction.
#Service
public class UserManager implements IUserManager {
private final IBasicEM basicEM;
public ApplicantInfoManager(IBasicEM basicEM) {
this.basicEM = basicEM;
}
#Override
#Transactional(value = "transactionManager",
propagation = Propagation.REQUIRES_NEW, rollbackFor = RollBackException.class)
public boolean purgeUser(Long id) throws Exception {
//business logic
basicEM.Update(entity)
if(//condition) {
throw New RollBackException("//message")
}
//business logic
}
}
If RollBackException is a RuntimeException you don't need to explicitly configure that the transaction should rollback when it is thrown. If it is not, then you need to configure it as follows: #Transactional(value = "transactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = RollBackException.class).

Transaction is not rolling back in spring batch item writer

Transaction is not rolling back in spring batch. I am throwing exception intentionally to test transaction roll back. Database transaction is committing even with exception thrown from the item writer.Below is the method in writer which is saving into DB. compEvent object is jpa repository injected into this class
private void writeCEs(Map> agentMap) throws FailedCompensationException, Exception {
for (Entry> agent : agentMap.entrySet()) {
agent.getValue().stream().forEach((ce) -> {
compEvent.save(ce);
});
updateRecordFileStatus(agent.getKey());
//postToAccounting(agent.getKey(), agent.getValue());
}
throw new Exception("Testing XA roolback.... ");
}
I tried marking transaction for the above method with
#Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
Still it is committing the transaction. I am sure I am missing something. any help would be appreciated.
Below is my batch configuration
#EnableBatchProcessing
#EnableTransactionManagement #Configuration #ComponentScan({ "com.pm.*" })
public class TrueBatchConfig extends DefaultBatchConfigurer {
#Autowired
private JobBuilderFactory jobs;
#Autowired
private StepBuilderFactory steps;
#Autowired
EventReader reader;
#Autowired
private EventProcessor processor;
#Autowired
private EventWriter writer;
#Bean
protected Step step1(ThreadPoolTaskExecutor executor) {
DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
attribute.setPropagationBehavior(Propagation.REQUIRED.value());
attribute.setIsolationLevel(Isolation.DEFAULT.value());
attribute.setTimeout(30);
return steps.get("step1")., Map>>chunk(10).reader(reader)
.processor(processor).writer(writer).transactionAttribute(attribute).build();
}
#Bean(name = "firstBatchJob")
public Job job(#Qualifier("step1") Step step1) {
return jobs.get("firstBatchJob").start(step1).build();
}
}
According to your configuration, Spring Batch is not configured to use your JpaTransactionManager, you need to override getTransactionManager, see example here. In your case it should be something like:
public class TrueBatchConfig extends DefaultBatchConfigurer {
// ..
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager(); // TODO set entity manager factory
}
}

springboot jta transaction rollback is not working

I want to perform transaction across database using JTA (using atomikos) configuration.
I have below code which I wanted to perform in one transaction. However when I run the application, it saves entityObject1 and update eventObject2 and doesnt rollback when an exception is thrown when i run l.intValue() statement. below is all code that I am using with configuration for JTA.
Am i missing anything? Could anyone please help.
public void testJTATRansaction() {
service1.saveEvent1(eventObject1);
service2.updateEvent2(eventObject2);
}
saveEvent1 method in service1:
#Transactional(propagation=Propagation.REQUIRED, rollbackFor = Exception.class)
public int saveEvent1(Object eventObject1) {
return repository1.save(eventObject1);
}
updateEvent2 method in service2:
#Transactional(propagation=Propagation.REQUIRED, rollbackFor = Exception.class)
public int updateEvent2(Object eventObject2) {
int i = l.intValue(); //l is null object, to throw error
return repository2.updateEvent2(eventObject2);
}
I am using default save method from repository1 (JPARepository save method).
updateEvent2 method in repository2 class:
#Modifying
#Transactional(propagation=Propagation.REQUIRED, rollbackFor = Exception.class)
#Query(UPDATE_EVENTS)
public int updateEvent2(
#Param(value = "eventObject2") Object eventObject2);
I am using spring boot application class to initialise my application:
#SpringBootApplication
#ComponentScan("com.cbc.event")
#EnableTransactionManagement
public class RatingDaemonApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(RatingDaemonApplication.class);
}
}
I have below JTA configuration:
#Configuration
#ComponentScan
#EnableTransactionManagement
public class JTATransactionConfig {
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(true);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
return hibernateJpaVendorAdapter;
}
#Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
#Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
AtomikosJtaPlatform.transactionManager = userTransactionManager;
return userTransactionManager;
}
#Bean(name = "transactionManager")
#DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
AtomikosJtaPlatform.transaction = userTransaction;
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}
}
and datasource configuration is:
#Configuration
#DependsOn("transactionManager")
#PropertySource({"classpath:application.properties"})
#EnableJpaRepositories(basePackages = {"com.cbc.repository"},
transactionManagerRef="transactionManager", entityManagerFactoryRef = "entityMF")
public class dataSourceConfiguration {
#Autowired
Environment env;
#Autowired
JpaVendorAdapter jpaVendorAdapter;
public DataSource eventsDS() {
AtomikosDataSourceBean xaDS = new AtomikosDataSourceBean();
xaDS.setXaDataSourceClassName(env.getProperty(DRIVER_CLASS_NAME));
xaDS.setXaDataSource(getMysqlXADataSource());
xaDS.setUniqueResourceName("DS");
xaDS.setMaxPoolSize(3);
return xaDS;
}
private MysqlXADataSource getMysqlXADataSource() {
MysqlXADataSource ds = new MysqlXADataSource();
ds.setPinGlobalTxToPhysicalConnection(true);
ds.setURL(env.getProperty(URL));
ds.setUser(env.getProperty(USER));
ds.setPassword(env.getProperty(PASSWORD));
return ds;
}
#Bean(name="entityMF")
public LocalContainerEntityManagerFactoryBean importedEventsEntityMF() {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
properties.put("javax.persistence.transactionType", "JTA");
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setJtaDataSource(eventsDS());
entityManager.setJpaVendorAdapter(jpaVendorAdapter);
entityManager.setPackagesToScan("com.cbc.events");
entityManager.setPersistenceUnitName("persistenceUnit");
entityManager.setJpaPropertyMap(properties);
return entityManager;
}
}
I have below AtomikosJtaPlatform class
public class AtomikosJtaPlatform extends AbstractJtaPlatform {
private static final long serialVersionUID = 1L;
static TransactionManager transactionManager;
static UserTransaction transaction;
#Override
protected TransactionManager locateTransactionManager() {
return transactionManager;
}
#Override
protected UserTransaction locateUserTransaction() {
return transaction;
}
}
This is from spring documentation
When the propagation setting is PROPAGATION_REQUIRED, a logical transaction scope is created for each method upon which the setting is applied. Each such logical transaction scope can determine rollback-only status individually, with an outer transaction scope being logically independent from the inner transaction scope. Of course, in case of standard PROPAGATION_REQUIRED behavior, all these scopes will be mapped to the same physical transaction. So a rollback-only marker set in the inner transaction scope does affect the outer transaction's chance to actually commit (as you would expect it to).
However, in the case where an inner transaction scope sets the rollback-only marker, the outer transaction has not decided on the rollback itself, and so the rollback (silently triggered by the inner transaction scope) is unexpected. A corresponding UnexpectedRollbackException is thrown at that point. This is expected behavior so that the caller of a transaction can never be misled to assume that a commit was performed when it really was not. So if an inner transaction (of which the outer caller is not aware) silently marks a transaction as rollback-only, the outer caller still calls commit. The outer caller needs to receive an UnexpectedRollbackException to indicate clearly that a rollback was performed instead.
Transaction propagation
Try changing the method declarations as below and give it a go
public int saveEvent1(Object eventObject1) throws UnexpectedRollbackException
public int updateEvent2(Object eventObject2) throws UnexpectedRollbackException
To avoid such things Its a good idea to have a separate method in one of those service classes or a completely different service class , and call both repository operations in one go , with transaction annotation
Also when you have the service methods annotated with transaction annotation then you dont need to annotate you repository methods , the more annotations you have related to transactions more complex it is to resolve issue.
Using h2 datasource,the distributed transaction is success.
But use mysql datasource,it is tested fail.
(1) First doubt the atomikos do not support MysqlXADataSource good.
(2) second think the JPA and hibernate is not support JTA so good.
Then I tink use jdbc.
#Configuration
public class ArticleConfigure {
#ConfigurationProperties("second.datasource")
#Bean(name="articleDataSourceProperties")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
//#Bean(name = "articleDataSource")
#Bean(name = "articleDataSource")
public DataSource articleDataSource() {
MysqlXADataSource mdatasource = new MysqlXADataSource();
mdatasource.setUrl(secondDataSourceProperties().getUrl());
mdatasource.setUser(secondDataSourceProperties().getUsername());
mdatasource.setPassword(secondDataSourceProperties().getPassword());
/*JdbcDataSource h2XaDataSource = new JdbcDataSource();
h2XaDataSource.setURL(secondDataSourceProperties().getUrl());*/
//atomikos datasource configure
com.atomikos.jdbc.AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mdatasource);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setUniqueResourceName("axds1");
return xaDataSource;
}
#Bean(name = "twojdbcTemplate")
public JdbcTemplate twojdbcTemplate() {
return new JdbcTemplate(articleDataSource());
}
}
TransactionConfig.
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages="cn.crazychain")
public class TransactionConfig {
#Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
//return new BitronixTransactionManager();
return userTransactionImp;
}
#Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
//#Bean(name = "atomikosTransactionManager")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
//return TransactionManagerServices.getTransactionManager();
return userTransactionManager;
}
#Bean(name = "customerJtaTransactionManager")
#DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}
}
Whether there is bug in hibernate jpa whith ax.
The conclusion is that JTA works fine with jdbc and AtomikosDataSourceBean.
The origin reference open source project is https://github.com/fabiomaffioletti/mul-at.git
The whole source code of mine is
https://github.com/lxiaodao/crazychain.
JTA transaction manager will only work if you use JNDI. JTA tx manager listens to Datasource and bring under a transaction only if the datasource bean is in Java/Web container and not in app. container.
Either you need to use JNDI for JTA to work or start using JPA transaction manager.
JTA transaction manager is mainly used in Distributed Transaction and is prone to transaction rollback failures.

#Transactional on #PostConstruct method

I want to read text data fixtures (CSV files) at the start on my application and put it in my database.
For that, I have created a PopulationService with an initialization method (#PostConstruct annotation).
I also want them to be executed in a single transaction, and hence I added #Transactional on the same method.
However, the #Transactional seems to be ignored :
The transaction is started / stopped at my low level DAO methods.
Do I need to manage the transaction manually then ?
Quote from legacy (closed) Spring forum:
In the #PostConstruct (as with the afterPropertiesSet from the InitializingBean interface) there is no way to ensure that all the post processing is already done, so (indeed) there can be no Transactions. The only way to ensure that that is working is by using a TransactionTemplate.
So if you would like something in your #PostConstruct to be executed within transaction you have to do something like this:
#Service("something")
public class Something {
#Autowired
#Qualifier("transactionManager")
protected PlatformTransactionManager txManager;
#PostConstruct
private void init(){
TransactionTemplate tmpl = new TransactionTemplate(txManager);
tmpl.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
//PUT YOUR CALL TO SERVICE HERE
}
});
}
}
I think #PostConstruct only ensures the preprocessing/injection of your current class is finished. It does not mean that the initialization of the whole application context is finished.
However you can use the spring event system to receive an event when the initialization of the application context is finished:
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
public void onApplicationEvent(ContextRefreshedEvent event) {
// do startup code ..
}
}
See the documentation section Standard and Custom Events for more details.
As an update, from Spring 4.2 the #EventListener annotation allows a cleaner implementation:
#Service
public class InitService {
#Autowired
MyDAO myDAO;
#EventListener(ContextRefreshedEvent.class)
public void onApplicationEvent(ContextRefreshedEvent event) {
event.getApplicationContext().getBean(InitService.class).initialize();
}
#Transactional
public void initialize() {
// use the DAO
}
}
Inject self and call through it the #Transactional method
public class AccountService {
#Autowired
private AccountService self;
#Transactional
public void resetAllAccounts(){
//...
}
#PostConstruct
private void init(){
self.resetAllAccounts();
}
}
For older Spring versions which do not support self-injection, inject BeanFactory and get self as beanFactory.getBean(AccountService.class)
EDIT
It looks like that since this solution has been posted 1.5 years ago developers are still under impression that if a method,
annotated with #Transactional, is called from a #PostContruct-annotated method invoked upon the Bean initialization, it won't be actually executed inside of Spring Transaction, and awkward (obsolete?) solutions get discussed and accepted instead of this very simple and straightforward one and the latter even gets downvoted.
The Doubting Thomases :) are welcome to check out an example Spring Boot application at GitHub which implements the described above solution.
What actually causes, IMHO, the confusion: the call to #Transactional method should be done through a proxied version of a Bean where such method is defined.
When a #Transactional method is called from another Bean, that another Bean usually injects this one and invokes its proxied (e.g. through #Autowired) version of it, and everything is fine.
When a #Transactional method is called from the same Bean directly, through usual Java call, the Spring AOP/Proxy machinery is not involved and the method is not executed inside of Transaction.
When, as in the suggested solution, a #Transactional method is called from the same Bean through self-injected proxy (self field), the situation is basically equivalent to a case 1.
#Platon Serbin's answer didn't work for me. So I kept searching and found the following answer that saved my life. :D
The answer is here No Session Hibernate in #PostConstruct, which I took the liberty to transcribe:
#Service("myService")
#Transactional(readOnly = true)
public class MyServiceImpl implements MyService {
#Autowired
private MyDao myDao;
private CacheList cacheList;
#Autowired
public void MyServiceImpl(PlatformTransactionManager transactionManager) {
this.cacheList = (CacheList) new TransactionTemplate(transactionManager).execute(new TransactionCallback(){
#Override
public Object doInTransaction(TransactionStatus transactionStatus) {
CacheList cacheList = new CacheList();
cacheList.reloadCache(MyServiceImpl.this.myDao.getAllFromServer());
return cacheList;
}
});
}
The transaction part of spring might not be initialized completely at #PostConstruct.
Use a listener to the ContextRefreshedEvent event to ensure, that transactions are available:
#Component
public class YourService
implements ApplicationListener<ContextRefreshedEvent> // <= ensure correct timing!
{
private final YourRepo repo;
public YourService (YourRepo repo) {this.repo = repo;}
#Transactional // <= ensure transaction!
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
repo.doSomethingWithinTransaction();
}
}
Using transactionOperations.execute() in #PostConstruct or in #NoTransaction method both works
#Service
public class ConfigurationService implements ApplicationContextAware {
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationService.class);
private ConfigDAO dao;
private TransactionOperations transactionOperations;
#Autowired
public void setTransactionOperations(TransactionOperations transactionOperations) {
this.transactionOperations = transactionOperations;
}
#Autowired
public void setConfigurationDAO(ConfigDAO dao) {
this.dao = dao;
}
#PostConstruct
public void postConstruct() {
try { transactionOperations.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(final TransactionStatus status) {
ResultSet<Config> configs = dao.queryAll();
}
});
}
catch (Exception ex)
{
LOG.trace(ex.getMessage(), ex);
}
}
#NoTransaction
public void saveConfiguration(final Configuration configuration, final boolean applicationSpecific) {
String name = configuration.getName();
Configuration original = transactionOperations.execute((TransactionCallback<Configuration>) status ->
getConfiguration(configuration.getName(), applicationSpecific, null));
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
}
}

#Transactional on Spring shutdown to properly shutdown Hsqldb

The heart of this question is: Is it possible to execute a Transaction from a method triggered by a Spring shutdown hook?
At the moment I have a HyperSqlDbServer class that implements SmartLifeCycle as found in this question:
In a spring bean is it possible to have a shutdown method which can use transactions?
I have a method in that class that is marked transactional that gets invoked as part of the stop method:
#Transactional
public void executeShutdown() {
hsqlDBShutdownService.executeShutdownQuery();
hsqlDBShutdownService.closeEntityManager();
}
The service used in that method is a bit of a hack that I had to do because I could not autowire in the EntityManager to this class:
#Service
public class HsqlDBShutdownService {
#PersistenceContext
private EntityManager entityManager;
#Autowired
private HyperSqlDbServer hyperSqlDbServer;
#Transactional
public void executeShutdownQuery() {
entityManager.createNativeQuery("SHUTDOWN").executeUpdate();
}
#Transactional
public void closeEntityManager() {
entityManager.close();
}
#PostConstruct
public void setHsqlDBShutdownService() {
hyperSqlDbServer.setShutdownService(this);
}
}
You may notice that all I'm really trying to accomplish is invoking the query "SHUTDOWN" before stopping the server. Without this, the hsqldb lock file sticks around on server restart, and the server throws an exception.
The code above produces the following exception:
javax.persistence.TransactionRequiredException: Executing an update/delete query
at org.hibernate.ejb.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:96)
...
So my original question stands, but if anyone has a thought on how I could execute this query another way I'll try that as well.
FYI, I've also tried the #PreDestroy annotation, but get the same TransactionRequiredException.
Edit: For completeness, I am using the JpaTransactionManager and the #Transactional annotations work throughout my project, except on shutdown...
Edit 2: Datasource and transaction manager configuration:
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:persistence.properties")
public class PersistenceConfig implements TransactionManagementConfigurer {
private static final String PASSWORD_PROPERTY = "dataSource.password";
private static final String USERNAME_PROPERTY = "dataSource.username";
private static final String URL_PROPERTY = "dataSource.url";
private static final String DRIVER_CLASS_NAME_PROPERTY = "dataSource.driverClassName";
#Autowired
private Environment env;
#Bean
#DependsOn("hsqlDb")
public DataSource configureDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty(DRIVER_CLASS_NAME_PROPERTY));
dataSource.setUrl(env.getProperty(URL_PROPERTY));
dataSource.setUsername(env.getProperty(USERNAME_PROPERTY));
dataSource.setPassword(env.getProperty(PASSWORD_PROPERTY));
return dataSource;
}
#Bean
#DependsOn("hsqlDb")
public LocalContainerEntityManagerFactoryBean configureEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(configureDataSource());
entityManagerFactoryBean.setPackagesToScan("com.mycompany.model.db");
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties jpaProperties = new Properties();
jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, env.getProperty(org.hibernate.cfg.Environment.DIALECT));
jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, env.getProperty(org.hibernate.cfg.Environment.HBM2DDL_AUTO));
jpaProperties.put(org.hibernate.cfg.Environment.SHOW_SQL, env.getProperty(org.hibernate.cfg.Environment.SHOW_SQL));
jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_IMPORT_FILES_SQL_EXTRACTOR, env.getProperty(org.hibernate.cfg.Environment.HBM2DDL_IMPORT_FILES_SQL_EXTRACTOR));
jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_IMPORT_FILES, env.getProperty(org.hibernate.cfg.Environment.HBM2DDL_IMPORT_FILES));
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
#Override
#Bean()
#DependsOn("hsqlDb")
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new JpaTransactionManager();
}
}
I found a workaround for shutting down the HsqlDB database, but it involves avoiding the use of Spring's EntityManager and #Transactional as they apparently do not work during server shutdown. My modified HsqlDBShutdownService is below. The key change is that instead of using the EntityManager to invoke the query, I create a new jdbc connection manually, and invoke the query that way. This avoids the requirement for #Transactional:
#Service
public class HsqlDBShutdownService {
#Autowired
private ApplicationContext applicationContext;
#PersistenceContext
private EntityManager entityManager;
#Autowired
private HyperSqlDbServer hyperSqlDbServer;
public void executeShutdownQuery() {
Connection conn = null;
try {
JdbcTemplate jdbcTemplate = new JdbcTemplate(this.applicationContext.getBean(DataSource.class));
conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
conn.setAutoCommit(true);
jdbcTemplate.execute("SHUTDOWN");
} catch(Exception ex) {
ex.printStackTrace();
} finally {
try {
if(conn != null)
conn.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
#Transactional
public void closeEntityManager() {
entityManager.close();
}
#PostConstruct
public void setHsqlDBShutdownService() {
hyperSqlDbServer.setShutdownService(this);
}
}
The server can now restart successfully without leaving Hsqldb lock files around.

Resources