Transaction is not rolling back in spring batch item writer - spring

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
}
}

Related

Spring Batch : How to change the default isolation level?

I read in the documentation :
"The default is ISOLATION_SERIALIZABLE, which prevents accidental
concurrent execution of the SAME job"
However, when I launch DIFFERENT jobs at the the same time (with a default isolation level at SERIALIZABLE), I have the error : ORA-08177: can't serialize access for this transaction. Is it normal ?
Second, to change the Default Isolation Level to READ_COMMITTED, I understood that we can't change this level in application.properties, and, that I have to redefine BatchConfigurer. Exact ?
Using BasicBatchConfigurer, I must define an explicit contructor (implicit super constructor BasicBatchConfigurer() is undefined for default constructor).
However, I have the error :
Parameter 0 of constructor in MyBatchConfigurer required a bean of type 'org.springframework.boot.autoconfigure.batch.BatchProperties' that could not be found.
How to create : BatchProperties properties, DataSource dataSource and TransactionManagerCustomizers transactionManagerCustomizers ?
This is my code :
PeopleApplication.java
#SpringBootApplication
#EnableAutoConfiguration(exclude = { BatchAutoConfiguration.class })
public class PeopleApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext ctx = new SpringApplicationBuilder(PeopleApplication.class)
.web(WebApplicationType.NONE)
.run(args);
int exitValue = SpringApplication.exit(ctx);
System.exit(exitValue);
}
}
MyBatchConfigurer.java
#Component
#PropertySource("classpath:fileA.properties")
public class MyBatchConfigurer extends BasicBatchConfigurer implements CommandLineRunner, ExitCodeGenerator {
protected MyBatchConfigurer(BatchProperties properties, DataSource dataSource, TransactionManagerCustomizers transactionManagerCustomizers) {
super(properties, dataSource, transactionManagerCustomizers);
}
#Override
protected String determineIsolationLevel() {
return "ISOLATION_" + Isolation.READ_COMMITTED;
}
#Override
public void run(String... args) {
...
}
...
}
Regards.
RESPONSE :
use #EnableAutoConfiguration instead of :
#EnableAutoConfiguration(exclude = { BatchAutoConfiguration.class })
Like this, the bean BatchProperties, DataSource and TransactionManagerCustomizers will be automatically created.
Please see the reply from Mahmoud that explains very clearly
can't serialize access for this transaction in spring batch.
Example for the usage is below and override only isolation level
--application.properties
spring.application.name=SpringBatch
####### SPRING ##############
spring.main.banner-mode=off
spring.main.web-application-type=none
spring.batch.initialize-schema=always
spring.batch.job.enabled=false // Disable default if you want to control
########JDBC Datasource########
#connection timeout 10 min
spring.datasource.hikari.connection-timeout=600000
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=100
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.poolName=SpringBoot-HikariCP
spring.datasource.url=jdbc:oracle:thin:#ngecom.ae:1521:ngbilling
spring.datasource.username=ngbilling
springbatch.datasource.password=ngbilling
#SpringBootApplication
public class YourApplication {
public static void main(String[] args) {
SpringApplication.run(SsadapterApplication.class, args);
}
}
// Your manual batch scheduler
class BatchJobScheduler extends BasicBatchConfigurer {
#Autowired
private JobLauncher jobLauncher;
#Autowired
private ApplicationArguments args;
#Autowired
private Job myJob;
protected BatchJobScheduler(BatchProperties properties, DataSource dataSource,
TransactionManagerCustomizers transactionManagerCustomizers) {
super(properties, dataSource, transactionManagerCustomizers);
}
#Override
protected String determineIsolationLevel() {
return "ISOLATION_" + Isolation.READ_COMMITTED;
}
//#Scheduled(cron = "${batch.cron}")
public void notScheduledJob() {
appId= args.getOptionValues("appId").get(0);
JobParameters params = new JobParametersBuilder().addLong("jobId"+appId, System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(myJob, params);
}
// Your batch Configuration And Spring will do everything for you to available
#Configuration
#EnableBatchProcessing
#EnableScheduling
public class BatchConfiguration {
Logger logger = LoggerFactory.getLogger(BatchConfiguration.class);
#Value("${batch.commit.chunk}")
private Integer chunkSize;
#Value("${batch.error.skipCount}")
private Integer skipErrorCount;
#Autowired
private Environment environment;
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Autowired
private DataSource dataSource;
#Autowired
private ApplicationArguments args;
#Autowired
private JdbcTemplate jdbcTemplate;
#Bean
public Job myJob() throws Exception {
return jobBuilderFactory.get("myJob").incrementer(new RunIdIncrementer())
.listener(new JobListener()).start(myStep()).build();
}
#Bean
public Step myStep() throws Exception {
return stepBuilderFactory.get("myStep").<InputObject, OutPutObject>chunk(chunkSize)
.reader(yourReader(null)).processor(yourProcessor()).writer(yourWriter())
//.faultTolerant()
//.skipLimit(skipErrorCount).skip(Exception.class)//.noSkip(FileNotFoundException.class)
.listener(invoiceSkipListener())
//.noRetry(Exception.class)
//.noRollback(Exception.class)
.build();
}

Spring #Transactional rollback not working

#RestController
#RequestMapping("/Api/Order")
public class OrderController {
private OrderService service;
private RefundService refundService;
#AsCustomer
#DeleteMapping(value = "/{orderID}/RefundApplication")
#Transactional(rollbackFor = RuntimeException.class)
public Map cancelRefundApplication(#SessionAttribute("user") User user,
#PathVariable("orderID") String orderID) {
Order order = service.getOrderByID(orderID);
RefundApplication application = refundService.get(orderID);
order.setState(Order.STATE_PAYED);
refundService.delete(orderID);
service.updateOrder(order);
throw new EntityNotFoundException("test");
}
...
I want transaction created in cancelRefundApplication method to be rolled back when a RuntimeException is thrown, and to be commit if no RuntimeException is thrown. But I find the transaction is not rolled back even if a RuntimeException is thrown. For test perpose, I change the code to make it always throw a EntityNotFoundException, and test it with following test method. After running the test, I check database and find refund application data is deleted, which means transaction is not rolled back and #Transactional annotation is not working.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {WebConfig.class, RootConfig.class, DataConfig.class})
#WebAppConfiguration
class OrderControllerTest {
#Autowired
OrderController controller;
#Autowired
UserService userService;
#Autowired
OrderService orderService;
#Autowired
AppWideExceptionHandler exceptionHandler;
private User customer;
private User seller;
private HashMap<String, Object> sessionAttrs;
private ResultMatcher success = jsonPath("$.code")
.value("0");
private MockMvc mockMvc;
#Test
void cancelRefundApplication() throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
String path = String.format("/Api/Order/%s%d0001/RefundApplication"
, simpleDateFormat.format(new Date()), customer.getID());
mockMvc.perform(delete(path)
.characterEncoding("UTF-8")
.sessionAttrs(sessionAttrs))
.andDo(print())
.andExpect(success);
}
...
This is DataConfig class:
#Configuration
#MapperScan("youshu.mapper")
public class DataConfig {
#Bean
public DataSource dataSource() {
// org.apache.ibatis.logging.LogFactory.useLog4J2Logging();
PooledDataSource pds = new PooledDataSource();
pds.setDriver("com.mysql.cj.jdbc.Driver");
pds.setUsername(...);
pds.setPassword(...);
pds.setUrl("jdbc:mysql://XXXX");
return pds;
}
#Bean
public JdbcOperations jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setTypeAliasesPackage("youshu.entity");
return sessionFactory.getObject();
}
#Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
#Bean
public SqlSessionTemplate sqlSession(SqlSessionFactory factory){
return new SqlSessionTemplate(factory);
}
}
Transactions need to be enabled manually by annotating config class with #EnableTransactionManagement
Check include or not TransactionalTestExecutionListener in your test, if not add: #TestExecutionListeners(listeners = {TransactionalTestExecutionListener.class})

Spring jdbc configuration

I have been trying to implement a web service using spring. This webservice will provide data access to a mySQL database using JDBC. I am trying to not use any xml configuration files, so I have come across a problem trying to connect to the database.
I am following the tutorial: http://spring.io/guides/tutorials/rest/ but I changed a few things along the way.
Now that I am trying to implement the connection with the database I get an error when trying to execute the tomcat instance, and I guess the problem is within the configurations.
Here follows some of my code:
Datasource configuration:
#Configuration
#Profile("mySQL")
#PropertySource("classpath:/services.properties")
public class MySQLDataSourceConfiguration implements DataSourceConfiguration{
#Inject
private Environment environment;
#Bean
public DataSource dataSource() throws Exception {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setPassword(environment.getProperty("dataSource.password"));
dataSource.setUrl(environment.getProperty("dataSource.url"));
dataSource.setUsername(environment.getProperty("dataSource.user"));
dataSource.setDriverClassName(environment.getPropertyAsClass("dataSource.driverClass", Driver.class).getName());
return dataSource;
}
}
the file service.properties is where I keep my configurations for the database, so when I desire to change the database I will just have to change 4 fields.
The JDBCConfiguration class for the setup of the JDBCtemplate
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:/services.properties")
#Import( { MySQLDataSourceConfiguration.class })
public class JdbcConfiguration {
#Autowired
private DataSourceConfiguration dataSourceConfiguration;
#Inject
private Environment environment;
#Bean
public JdbcTemplate setupJdbcTemplate() throws Exception {
return new JdbcTemplate(dataSourceConfiguration.dataSource());
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
}
Then there is the Repository, that recieves the template.
#Transactional
#Repository
#Qualifier("jdbcRepository")
public class JdbcIndividualRepository implements IndividualsRepository{
private static final Logger LOG = LoggerFactory.getLogger(JdbcIndividualRepository.class);
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
public JdbcIndividualRepository(DataSource jdbcDataSource) {
LOG.info("JDBCRepo arg constructor");
this.jdbcTemplate = new JdbcTemplate(jdbcDataSource);
}
#Override
public Individual save(Individual save) {
String sql = "INSERT INTO Individual(idIndividual, Name) VALUES(?,?)";
this.jdbcTemplate.update(sql, save.getId(), save.getName());
return save;
}
#Override
public void delete(String key) {
String sql = "DELETE FROM Individual WHERE idIndividual=?";
jdbcTemplate.update(sql, key);
}
#Override
public Individual findById(String key) {
String sql = "SELECT i.* FROM Individual i WHERE i.idIndividual=?";
return this.jdbcTemplate.queryForObject(sql, new IndividualRowMapper(), key);
}
#Override
public List<Individual> findAll() {
String sql = "SELECT * FROM Individual";
return new LinkedList<Individual>(this.jdbcTemplate.query(sql, new IndividualRowMapper()));
}
}
Then I register the jdbc configuration in the initializer class when creating the root context of the application as follows:
private WebApplicationContext createRootContext(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(CoreConfig.class, SecurityConfig.class, JdbcConfiguration.class);
rootContext.refresh();
servletContext.addListener(new ContextLoaderListener(rootContext));
servletContext.setInitParameter("defaultHtmlEscape", "true");
return rootContext;
}
However, the Tomcat server wont run because it can't autowire the class MySQLDataSourceConfiguration.
Anyone knows what the problem might be? I can give more details on the code, but the question is already really large.
Appreciate any kind of help!
Cheers
EDIT
Solved changing the JdbcConfiguration class to:
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:/services.properties")
#Import( { MySQLDataSourceConfiguration.class })
public class JdbcConfiguration {
#Autowired
private DataSource dataSource;
#Inject
private Environment environment;
#Bean
public JdbcTemplate setupJdbcTemplate() throws Exception {
return new JdbcTemplate(dataSource);
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
#Bean
public IndividualsRepository createRepo(){
return new JdbcIndividualRepository(dataSource);
}
}
Remove
#Autowired
private DataSourceConfiguration dataSourceConfiguration;
Because that's not how it's supposed to be used. Instead add to the same class the following:
#Autowired DataSource dataSource;
and use it like this: new JdbcTemplate(dataSource);
Also, try adding #ComponentScan to JdbcConfiguration class. From what I see in your code the class JdbcIndividualRepository is not picked up by anything.
In your class JdbcConfiguration, you are trying to autowire DataSourceConfiguration. I'm not really sure if that's possible - typically you should try to autwire the DataSource, not the DataSourceConfiguration.
#Import( { MySQLDataSourceConfiguration.class })
public class JdbcConfiguration {
#Autowired
private DataSource dataSource;
#Bean
public JdbcTemplate setupJdbcTemplate() throws Exception {
return new JdbcTemplate(dataSource);
}
Also if you have several DataSources and you're using Spring profiles to separate them, it's easier to provide all the DataSource beans in one file and annotate each bean with a different profile:
#Configuration
public class DataSourceConfig {
#Bean
#Profile("Test")
public DataSource devDataSource() {
.... configure data source
}
#Bean
#Profile("Prod")
public DataSource prodDataSource() {
... configure data source
}

Unittest a transactional method with REQUIRES_NEW and always force rollback

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.

#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