Spring Boot not rolling back transaction on exception - spring-boot

I have a straightforward code that saves something to the DB and then throws an exception to see if the changes get rollback.
ResourceA.java
#Autowire ServiceA serviceA;
#PUT
#Path("/{Id:[0-9]+}")
public ObjectA updateSomethingResource(..) {
return serviceA.upateSomethingService(..);
}
ServiceA.java
#Transactional(rollbackFor=Exception.class)
public ObjectA upateSomethingService(EntitlementRequest entitlementRequest) throws ServiceException {
ObjectA objectA = getObjectFromDB(..);
objectA.setName("New Name");
dao.save(objectA)
throw new ServiceException("error"); //ServiceException extends Exception
}
When executing this and I check the DB the name of the object is still New Name. I would have excepted it to be rolled back to what it was originally.
To understand what is happening I have tried looking at what log I can enable and the only one that I managed to enable is
logging.level.org.springframework.transaction.interceptor=TRACE
which gives:
TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [xxxxx.upateSomethingService]
TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [xxxxx.upateSomethingService] after exception: xxx.ServiceException
So the transaction manager does see the exception but nothing gets rollback.
Is there something I need to enable somewhere to have rollback actually working?
What other log can I enable that could show me exactly what is happening?
UPDATE:
I have managed to show ore logs with logging.level.org.springframework=DEBUG
DEBUG o.s.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
DEBUG o.s.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
DEBUG o.s.j.s.JdbcTransactionManager - Creating new transaction with name [xxx.service.upateSomethingService]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception
DEBUG o.s.j.s.JdbcTransactionManager - Acquired Connection [1091371323, URL=jdbc:mysql://localhost:3306/table?allowMultiQueries=true, UserName=root#localhost, MySQL Connector/J] for JDBC transaction
DEBUG o.s.j.s.JdbcTransactionManager - Switching JDBC Connection [1091371323, URL=jdbc:mysql://localhost:3306/table?allowMultiQueries=true, UserName=root#localhost, MySQL Connector/J] to manual commit
DEBUG o.s.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
DEBUG o.s.j.s.JdbcTransactionManager - Initiating transaction rollback
DEBUG o.s.j.s.JdbcTransactionManager - Rolling back JDBC transaction on Connection [1091371323, URL=jdbc:mysql://localhost:3306/table?allowMultiQueries=true, UserName=root#localhost, MySQL Connector/J]
DEBUG o.s.j.s.JdbcTransactionManager - Releasing JDBC Connection [1091371323, URL=jdbc:mysql://localhost:3306/table?allowMultiQueries=true, UserName=root#localhost, MySQL Connector/J] after transaction
Looking at these logs.. spring is actually telling me it 'is' rolling back the transaction.. but nothing gets changed in the DB ?
Something odd that I just noticed is that right after dao.save(objectA) I can already see the name being changed in the DB.. so somehow MyBatis is auto committing and doesn't look like it is using the same connection has the one I see in the logs
This is how I setup the Databasource and configure MyBatis
(I have 2 datasource this is why I'm using this instead of just using application.properties properties.)
#Bean(name = MYBATIS_DATASOURCE_ONE, destroyMethod = "")
public DataSource dataSource(....) throws SQLException {
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver(driverclassname);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
[...]
return dataSource;
}
#Bean(name = A_SESSION_FACTORY, destroyMethod = "")
#Primary
public SqlSessionFactory sqlSessionFactory(#Qualifier(MYBATIS_DATASOURCE_A) DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/mapper/*.xml"));
final SqlSessionFactory sqlSessionFactory = factoryBean.getObject();
return sqlSessionFactory;
}

You latest update made it clearer... What is happening I think is that Spring is not using your manually defined DataSource when instantiating the DataSourceTransactionManager. It is probably using a BasicDataSource or something similar (one that has been created using the properties from your application.properties
MyBatis documentation clearly specifies that for Transaction to work the DataSourceTransactionManager needs to have the exact same Datasource the rest of your app is using otherwise it won't work.
Add this to your configuration and the transaction should start working
#Bean
public DataSourceTransactionManager transactionManager(#Qualifier(MYBATIS_DATASOURCE_A) DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
Normally, you wouldn't need to manually define this Bean because Spring would automatically do it for you. But since you are using your own DataSource you need to do it.
If you want to verify this. Don't declare this bean and put a breakpoint in the DataSourceTransactionManager Constructor.
You should notice that the DataSource is not the same instance as the one you have declared yourself.

Related

How to make Hibernate and Spring track transactions consistently?

When using TransactionTemplate in an Executor thread, it appears unable to consistently find the current transaction, resulting in problems like this:
14:20:56.022 [pool-2-thread-1] DEBUG HibernateTransactionManager - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_SERIALIZABLE
14:20:56.022 [pool-2-thread-1] DEBUG HibernateTransactionManager - Opened new Session [SessionImpl(1516598186<open>)] for Hibernate transaction
14:20:56.022 [pool-2-thread-1] DEBUG HibernateTransactionManager - Preparing JDBC Connection of Hibernate Session [SessionImpl(1516598186<open>)]
14:20:56.023 [pool-2-thread-1] DEBUG HibernateTransactionManager - Exposing Hibernate transaction as JDBC [org.springframework.orm.hibernate5.HibernateTransactionManager$$Lambda$1238/0x00000008009dfc40#5cc5f4]
14:20:56.026 [pool-2-thread-1] DEBUG HibernateTransactionManager - Found thread-bound Session [SessionImpl(1516598186<open>)] for Hibernate transaction
14:20:56.026 [pool-2-thread-1] DEBUG HibernateTransactionManager - Participating in existing transaction
14:20:56.052 [pool-2-thread-1] DEBUG SessionFactoryUtils - Flushing Hibernate Session on transaction synchronization
14:20:56.054 [pool-2-thread-1] DEBUG HibernateTransactionManager - Initiating transaction rollback after commit exception
javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:445)
at org.hibernate.internal.SessionImpl.checkTransactionNeededForUpdateOperation(SessionImpl.java:3478)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1394)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1389)
at org.springframework.orm.hibernate5.SessionFactoryUtils.flush(SessionFactoryUtils.java:113)
at org.springframework.orm.hibernate5.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:95)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:97)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:916)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:727)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:152)
at org.springframework.transaction.support.TransactionOperations.executeWithoutResult(TransactionOperations.java:67)
at com.example.MyClass.doProcessing(MyClass.java:109)
at com.example.MyClass.lambda$scheduleJob$4(MyClass.java:93)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
14:20:56.054 [pool-2-thread-1] DEBUG HibernateTransactionManager - Rolling back Hibernate transaction on Session [SessionImpl(1516598186<open>)]
14:20:56.056 [pool-2-thread-1] DEBUG HibernateTransactionManager - Closing Hibernate Session [SessionImpl(1516598186<open>)] after transaction
JPA is configured manually, because I have multiple DataSources in the application.
Relevant beans:
#Bean
#TelemetryDb
public LocalContainerEntityManagerFactoryBean telemetryEntityManagerFactory(
EntityManagerFactoryBuilder builder,
#TelemetryDb DataSource dataSource
) {
return builder.dataSource(dataSource)
.packages("com.example.model.telemetry")
.persistenceUnit("telemetry")
.properties(getVendorProperties(dataSource))
.jta(false)
.build();
}
#Bean
#TelemetryDb
#Order(Ordered.HIGHEST_PRECEDENCE)
public PlatformTransactionManager telemetryTransactionManager(#TelemetryDb EntityManagerFactory factory) {
return new HibernateTransactionManager((SessionFactory) factory);
}
#Configuration
#EnableJpaRepositories(
basePackageClasses = PackageMarker.class,
includeFilters = #ComponentScan.Filter(type = FilterType.CUSTOM, value = TelemetryFilter.class),
entityManagerFactoryRef = "telemetryEntityManagerFactory",
transactionManagerRef = "telemetryTransactionManager"
)
public static class Telemetry { }
The properties are:
hibernate.auto_quote_keyword=true
hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
hibernate.dialect=com.example.PostgreSQL12Dialect
And some code that gives the above log:
#Service
#RequriedArgsConstructor
public class MyClass {
private final MyRepository repository;
private ExecutorService executor = Executors.newSingleThreadExecutor();
private SessionFactory sessionFactory;
private TransactionTemplate transactionTemplate;
#Autowired
public void setTransactionManager(#TelemetryDb PlatformTransactionManager manager) {
this.sessionFactory = ((HibernateTransactionManager) manager).getSessionFactory();
this.transactionTemplate = new TransactionTemplate(manager);
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
}
public scheduleJob(long entityId) {
Boolean submit = transactionTemplate.execute(status -> {
MyEntity entity = repository.findById(entity).orElseThrow();
if (entity.isProcessing()) {
return false;
} else {
entity.setProcessing(true);
return true;
}
});
if (Boolean.TRUE.equals(submit)) {
executor.submit(() -> doProcessing(entityId));
}
}
protected void doProcessing(long entityId) {
transactionTemplate.executeWithoutResult(status -> {
MyEntity entity = repository.findById(entity).orElseThrow();
entity.setBlobField(sessionFactory.getCurrentSession().getLobHelper().createBlob(new byte[]{});
entity.setProcessing(false);
status.flush();
});
}
}
Note that the usage of TransactionTemplate outside of the executor thread completes with no issues. Note also that the createBlob() and flush() do not complain about a missing transaction.
I assume I have missed something in the configuration, but I have been trying various permutations of it, and the doProcessing method, but cannot get anything to work.
After further debugging, it appears that the getCurrentSession() call causes SpringSessionContext to look for, and then create, a session using a SessionFactoryImpl as the key rather than the LocalContainerEntityManagerFactoryBean proxy.
There doesn't seem to be any way to control the construction of the context within Hibernate. I think if I could obtain the raw SessionFactoryImpl to pass to HibernateTransactionManager then that would fix it.
I tried that (using EntityManagerFactory.unrwap) but that broke a whole load of other stuff, because when Spring Data calls createQuery it looks for the current session using the bean proxy.
It appears that SpringSessionContext doesn't work as intended. Either there was a change in Hibernate and/or there wasn't sufficient integration testing.
There is a workaround. Ignore SessionFactory.getCurrentSession() and use the TransactionSynchronizationManager directly:
#PersistenceUnit(unitName = "telemetry")
private EntityManagerFactory entityManagerFactory;
private #NonNull Session getCurrentSession() {
Object resource = TransactionSynchronizationManager.getResource(entityManagerFactory);
if (resource == null) {
throw new IllegalStateException("No current transaction");
}
return ((SessionHolder) resource).getSession();
}
That only works within an existing session. More code would be required to create a new one to match the full behaviour.
Unsetting hibernate.current_session_context_class ensures you will always get an exception when trying to use the incompatible interface, rather than getting surprise extra sessions.

Spring Batch/Data JPA application not persisting/saving data to Postgres database when calling JPA repository (save, saveAll) methods

I am near wits-end. I read/googled endlessly so far and tried the solutions on all the google/stackoverflow posts that have this similiar issue (there a quite a few). Some seemed promising, but nothing has worked for me yet; though I have made some progress and I am on the right track I believe (I'm believing at this point its something with the Transaction manager and some possible conflict with Spring Batch vs. Spring Data JPA).
References:
Spring boot repository does not save to the DB if called from scheduled job
JpaItemWriter: no transaction is in progress
Similar to the aforementioned posts, I have a Spring Boot application that is using Spring Batch and Spring Data JPA. It reads comma delimited data from a .csv file, then does some processing/transformation, and attempts to persist/save to database using the JPA Repository methods, specifically here .saveAll() (I also tried .save() method and this did the same thing), since I'm saving a List<MyUserDefinedDataType> of a user-defined data type (batch insert).
Now, my code was working fine on Spring Boot starter 1.5.9.RELEASE, but I recently attempted to upgrade to 2.X.X, which I found, after countless hours of debugging, only version 2.2.0.RELEASE would persist/save data to database. So an upgrade to >= 2.2.1.RELEASE breaks persistence. Everything is read fine from the .csv, its just when the first time the code flow hits a JPA repository method like .save() .saveAll(), the application keeps running but nothing gets persisted. I also noticed the Hikari pool logs "active=1 idle=4", but when I looked at the same log when on version 1.5.9.RELEASE, it says active=0 idle=5 immediately after persisting the data, so the application is definitely hanging. I went into the debugger and even saw after jumping into the Repository calls, it goes into almost an infinite cycle through the Spring AOP libraries and such (all third party) and I don't believe ever comes back to the real application/business logic that I wrote.
3c22fb53ed64 2021-05-20 23:53:43.909 DEBUG
[HikariPool-1 housekeeper] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Pool stats (total=5, active=1, idle=4, waiting=0)
Anyway, I tried the most common solutions that worked for other people which were:
Defining a JpaTransactionManager #Bean and injecting it into the Step function, while keeping the JobRepository using the PlatformTransactionManager. This did not work. Then I also I tried using the JpaTransactionManager also in the JobRepository #Bean, this also did not work.
Defining a #RestController endpoint in my application to manually trigger this Job, instead of doing it manually from my main Application.java class. (I talk about this more below). And per one of the posts I posted above, the data persisted correctly to the database even on spring >= 2.2.1, which further I suspect now something with the Spring Batch persistence/entity/transaction managers is messed up.
The code is basically this:
BatchConfiguration.java
#Configuration
#EnableBatchProcessing
#Import({DatabaseConfiguration.class})
public class BatchConfiguration {
// Datasource is a Postgres DB defined in separate IntelliJ project that I add to my pom.xml
DataSource dataSource;
#Autowired
public BatchConfiguration(#Qualifier("dataSource") DataSource dataSource) {
this.dataSource = dataSource;
}
#Bean
#Primary
public JpaTransactionManager jpaTransactionManager() {
final JpaTransactionManager tm = new JpaTransactionManager();
tm.setDataSource(dataSource);
return tm;
}
#Bean
public JobRepository jobRepository(PlatformTransactionManager transactionManager) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
return jobRepositoryFactoryBean.getObject();
}
#Bean
public JobLauncher jobLauncher(JobRepository jobRepository) {
SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
simpleJobLauncher.setJobRepository(jobRepository);
return simpleJobLauncher;
}
#Bean(name = "jobToLoadTheData")
public Job jobToLoadTheData() {
return jobBuilderFactory.get("jobToLoadTheData")
.start(stepToLoadData())
.listener(new CustomJobListener())
.build();
}
#Bean
#StepScope
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(maxThreads);
threadPoolTaskExecutor.setThreadGroupName("taskExecutor-batch");
return threadPoolTaskExecutor;
}
#Bean(name = "stepToLoadData")
public Step stepToLoadData() {
TaskletStep step = stepBuilderFactory.get("stepToLoadData")
.transactionManager(jpaTransactionManager())
.<List<FieldSet>, List<myCustomPayloadRecord>>chunk(chunkSize)
.reader(myCustomFileItemReader(OVERRIDDEN_BY_EXPRESSION))
.processor(myCustomPayloadRecordItemProcessor())
.writer(myCustomerWriter())
.faultTolerant()
.skipPolicy(new AlwaysSkipItemSkipPolicy())
.skip(DataValidationException.class)
.listener(new CustomReaderListener())
.listener(new CustomProcessListener())
.listener(new CustomWriteListener())
.listener(new CustomSkipListener())
.taskExecutor(taskExecutor())
.throttleLimit(maxThreads)
.build();
step.registerStepExecutionListener(stepExecutionListener());
step.registerChunkListener(new CustomChunkListener());
return step;
}
My main method:
Application.java
#Autowired
#Qualifier("jobToLoadTheData")
private Job loadTheData;
#Autowired
private JobLauncher jobLauncher;
#PostConstruct
public void launchJob () throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException
{
JobParameters parameters = (new JobParametersBuilder()).addDate("random", new Date()).toJobParameters();
jobLauncher.run(loadTheData, parameters);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Now, normally I'm reading this .csv from Amazon S3 bucket, but since I'm testing locally, I am just placing the .csv in the project directory and reading it directly by triggering the job in the Application.java main class (as you can see above). Also, I do have some other beans defined in this BatchConfiguration class but I don't want to over-complicate this post more than it already is and from the googling I've done, the problem possibly is with the methods I posted (hopefully).
Also, I would like to point out, similar to one of the other posts on Google/stackoverflow with a user having a similar problem, I created a #RestController endpoint that simply calls the .run() method the JobLauncher and I pass in the JobToLoadTheData Bean, and it triggers the batch insert. Guess what? Data persists to the database just fine, even on spring >= 2.2.1.
What is going on here? is this a clue? is something funky going wrong with some type of entity or transaction manager? I'll take any advice tips! I can provide any more information that you guys may need , so please just ask.
You are defining a bean of type JobRepository and expecting it to be picked up by Spring Batch. This is not correct. You need to provide a BatchConfigurer and override getJobRepository. This is explained in the reference documentation:
You can customize any of these beans by creating a custom implementation of the
BatchConfigurer interface. Typically, extending the DefaultBatchConfigurer
(which is provided if a BatchConfigurer is not found) and overriding the required
getter is sufficient.
This is also documented in the Javadoc of #EnableBatchProcessing. So in your case, you need to define a bean of type Batchconfigurer and override getJobRepository and getTransactionManager, something like:
#Bean
public BatchConfigurer batchConfigurer(EntityManagerFactory entityManagerFactory, DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager(entityManagerFactory);
}
#Override
public JobRepository getJobRepository() {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(getTransactionManager());
// set other properties
return jobRepositoryFactoryBean.getObject();
}
};
}
In a Spring Boot context, you could also override the createTransactionManager and createJobRepository methods of org.springframework.boot.autoconfigure.batch.JpaBatchConfigurer if needed.

Am trying to do simple JDBC call in a spring boot application is not working

I am looking forward to creating a service using spring boot application where I like to use JDBC prepared statement call which executes the stored procedure get me the required result.
I like to have connection pooling but unfortunately, I don’t know implement
Summary
(services using spring boot --->Simple JDBC with connection pooling---->Mysql)
For this, I have tried to create a data source and execute jdbc statement but not working
#Controller
public class ExampleController {
#Autowired
private ExampleRepository repo;
#RequestMapping("/")
public #ResponseBody String getDataBaseData() throws SQLException{
return repo.getDataBaseData();
}
}
#Configuration
public class DataSources {
#Bean(name = "primary")
#Primary
#ConfigurationProperties(prefix="spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
}
#Component
public class ExampleRepository {
#Autowired
private DataSource ds;
public String getDataBaseData() throws SQLException {
Connection con = ds.getConnection();
System.out.println(con);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("select * from emp");
while (rs.next())
System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getString(3));
con.close();
return rs.toString();
}
}
getting errors like below
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server.
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server.
Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
Expected Result : Database data should display in web browser
This is my github repo https://github.com/PradeepKumarHE/SpringBootWithSimpleJDBC/tree/master
where i have DBscript file to create
What I can see from your pom.xml, you are using spring-boot-starter-data-jpa. It fetches unneccessary dependencies, which triggers SpringBoot jpa autoconfiguration.
If you want to use pure jdbc with spring boot, replace spring-boot-starter-data-jpa with spring-boot-starter-jdbc (https://mvnrepository.com/artifact/org.springframework.boot/)
In this case you need to
have mysql jdbc driver declared in maven dependencies
define spring.datasource.url, spring.datasource.username, spring.datasource.password in your properties or yaml (you don't need to define spring.datasource.driver if you have only one jdbc driver in your maven deps)
remove your DataSources configuration, since springboot will autoconfigure it for you

SpringBoot manage connection pool errors

I have a spring-boot application with 3 webservices that access to two different databases declared in application.properties file.
spring.datasource.url = jdbc:oracle
spring.datasource.username = aa
spring.datasource.password = aa
spring.seconddatasource.url = jdbc:oracle2
spring.seconddatasource.username = aa
spring.seconddatasource.password = aa
When I run the application, if a connection fails it ends the whole application even if one of the connections works.
I need to connect to all databases, and if a database isn't working, try to reconnect, but the application can not end.
I have tried these configurations but with no success :
testOnBorrow=true
validationQuery=SELECT 1
timeBetweenEvictionRunsMillis = 60000
Also I have a DataBaseConfig.java with
#Configuration
public class DataBaseConfig {
#Bean(name = "mysqlDb")
#Primary
#ConfigurationProperties(prefix="spring.datasource")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "sqliteDb")
#ConfigurationProperties(prefix="spring.secondDatasource")
public DataSource sqliteDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "cli")
public JdbcTemplate slaveJdbcTemplate(#Qualifier("mysqlDb") DataSource datasource) {
return new JdbcTemplate(datasource);
}
#Bean(name = "usr")
#Primary
public JdbcTemplate masterJdbcTemplate(#Qualifier("sqliteDb") DataSource secondDatasource) {
return new JdbcTemplate(secondDatasource);
}
}
Console errors :
Unable to create initial connections of pool
HHH000342: Could not obtain connection to query metadata : ORA-01034: ORACLE not available
ORA-27101: shared memory realm does not exist
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
Create the connection pools programmatically rather than letting Spring Boot autoconfigure them for you. Then you can handle any errors in building the datasource in your code. See:
Configure DataSource programmatically in Spring Boot
Alternatively create a single connection datasource at runtime rather than at boot time, and add retry logic in the event of an error (have a look at Spring Retry)

Flyway & MyBatis: Java Configuration for Spring-Boot

I'm trying to code a web-app using Spring-Boot, HSQLDB, Flyway and MyBatis. I started without MyBatis and Flyway happily created the database every time I had removed the HSQLDB.
After I added MyBatis all was fine until I had to make changes in the initial SQL-file and removed the database. Now I'm failing to start the web-app. It seems as if Flyway and MyBatis somehow depend on each other.
My database configuration:
#Configuration
public class DatabaseConfiguration {
#Bean
public DataSource dataSource() { ... }
#Bean
public DataSourceTransactionManager transactionManager() { ... }
#Bean
public static MapperScannerConfigurer mapperScannerConfigurer() { ... }
#Bean
public DataSourceInitializer dataSourceInitializer() throws Exception { ... }
#Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception { ... }
#Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception { ... }
}
I fully understand that MapperScannerConfigurer, SqlSessionFactoryBean and SqlSessionTemplate are coming from MyBatis, all Flyway-stuff is done by Spring-Boot. From what I can tell Flyway needs a DataSource and MyBatis needs a database where the Flyway scripts ran already to allow the initialisation of the mappers.
The error I get is
### Error querying database. Cause: java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: MY_TABLE
### The error may exist in file [/home/work/Eclipse/com.sjngm.hs/target/classes/sqlmap/MyTableMapper.xml]
### The error may involve com.sjngm.hs.dao.mapper.MyTableMapper.getByName
### The error occurred while executing a query
### SQL: SELECT * FROM MY_TABLE WHERE Name = ?
### Cause: java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: MY_TABLE
Note that there is no indication in the entire log-file that Flyway already did something. Also I can't find any CREATE TABLE in HSQLDB's files, which makes the above error saying that it can't find table MY_TABLE.
I already tried moving dataSource() to a new configuration class, but that didn't help.
What do I need to do to solve that indirect dependency?
What helped was making dataSource() a #FlywayDataSource.

Resources