I have a Spring Boot application using multiple datasources:
#RequiredArgsConstructor
#Configuration
#AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class FlywayCustomConfig {
#Resource(name = "dataSource")
private final DataSource gameDatasource;
#Resource(name = "profileDataSource")
private final DataSource profileDataSource;
#Resource(name = "authDataSource")
private final DataSource authDataSource;
/**
* Primary Flyway bean. Used for the game database. This database is being wiped clean after every game.
*
* #return Flyway bean
*/
#Primary
#Bean(name = "flyway")
#ConfigurationProperties(prefix = "flyway.game")
public Flyway flywayGame() {
Flyway flyway = Flyway.configure()
.locations("classpath:db/migration/game")
.dataSource(gameDatasource)
.load();
flyway.migrate();
return flyway;
}
/**
* Profile Flyway bean. Used for the profile database. This database is persistent and should not change.
*
* #return Flyway bean
*/
#Bean(name = "flywayProfile")
#ConfigurationProperties(prefix = "flyway.profile")
public Flyway flywayProfile() {
Flyway flyway = Flyway.configure()
.locations("classpath:db/migration/profile")
.dataSource(profileDataSource)
.load();
flyway.migrate();
return flyway;
}
/**
* Auth Flyway bean. Used for the auth database. This database is persistent and should not change.
*
* #return Flyway bean
*/
#Bean(name = "flywayAuth")
#ConfigurationProperties(prefix = "flyway.auth")
public Flyway flywayAuth() {
Flyway flyway = Flyway.configure()
.locations("classpath:db/migration/auth")
.dataSource(authDataSource)
.load();
flyway.migrate();
return flyway;
}
#Bean
#Primary
public FlywayMigrationInitializer flywayInitializerGame(#Qualifier("flyway") Flyway flywayGame) {
return new FlywayMigrationInitializer(flywayGame, null);
}
#Bean
public FlywayMigrationInitializer flywayInitializerProfile(#Qualifier("flywayProfile") Flyway flywayProfile) {
return new FlywayMigrationInitializer(flywayProfile, null);
}
#Bean
public FlywayMigrationInitializer flywayInitializerAuth(#Qualifier("flywayAuth") Flyway flywayAuth) {
return new FlywayMigrationInitializer(flywayAuth, null);
}
}
This is the datasource config for my auth details:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "authEntityManagerFactory",
basePackages = { "com.withergate.api.auth.repository" }
)
public class AuthDbConfig {
/**
* Auth datasource. Used for the auth database.
*
* #return datasource
*/
#Bean(name = "authDataSource")
#ConfigurationProperties(prefix = "auth.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
/**
* Auth entity manager factory. Used for the auth database.
*
* #return entity manager factory
*/
#Bean(name = "authEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("authDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.withergate.api.auth.model")
.persistenceUnit("auth")
.build();
}
/**
* Auth transaction manager. Used for the auth database.
*
* #return transaction manager
*/
#Bean(name = "authTransactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("authEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Everything works fine when I login via thymeleaf but as soon as I try performing some OAuth2 request, I get an exception:
2020-12-11 11:55:30.668 ERROR 412122 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove from oauth_client_details where client_id = ?]; nested exception is java.sql.SQLSyntaxErrorException: Table 'game.oauth_client_details' doesn't exist] with root cause
java.sql.SQLSyntaxErrorException: Table 'game.oauth_client_details' doesn't exist
It seems that OAuth2 is trying to use wrong datasource for fetching the user details.
My OAuth2 config looks as follows:
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
private final UserDetailsService userService;
private final TokenStore tokenStore;
#Qualifier("authDataSource")
private final DataSource dataSource;
private final AuthenticationManager authenticationManager;
/**
* OAuth2Config constructor.
*/
public OAuth2Config(
UserDetailsService userService, TokenStore tokenStore, DataSource dataSource, #Lazy AuthenticationManager authenticationManager
) {
this.userService = userService;
this.tokenStore = tokenStore;
this.dataSource = dataSource;
this.authenticationManager = authenticationManager;
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) {
configurer.authenticationManager(authenticationManager);
configurer.userDetailsService(userService);
configurer.tokenStore(tokenStore);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
Does anyone have an idea what am doing wrong?
Related
I have a Spring application (not Spring Boot) where I use Spring Data JPA and annotation based Java configuration. I am trying to unit test (JUnit 4) a repository fragment where I created custom save methods, but I cannot properly load the required context and beans to run the tests:
[main] INFO org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
[main] INFO org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener#87f383f, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener#4eb7f003, org.springframework.test.context.support.DependencyInjectionTestExecutionListener#eafc191, org.springframework.test.context.support.DirtiesContextTestExecutionListener#612fc6eb, org.springframework.test.context.transaction.TransactionalTestExecutionListener#1060b431, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener#612679d6, org.springframework.test.context.event.EventPublishingTestExecutionListener#11758f2a]
[main] INFO org.springframework.data.repository.config.RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFAULT mode.
[main] INFO org.springframework.data.repository.config.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 96ms. Found 3 JPA repository interfaces.
[main] INFO org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'repositoryConfig' of type [com.example.app.config.RepositoryConfig$$EnhancerBySpringCGLIB$$4ffca507] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[main] ERROR org.springframework.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener#eafc191] to prepare test instance [com.example.app.test.tests.UserDaoTests#37313c65]
java.lang.NoClassDefFoundError: org.springframework.beans.FatalBeanException
How should I load the application context appropriately?
UserDaoTests
#ActiveProfiles("dev")
#ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes= {RepositoryConfig.class})
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
public class UserDaoTests {
private UserRepository userRepository;
#Autowired
private DataSource dataSource;
#Autowired
public void setUserRepository (UserRepository userRepository) {
this.userRepository = userRepository;
}
private User user1 = new User("user", "userpass", true);
#Before
public void init() {
System.out.println("Before running tests: init");
JdbcTemplate jdbc = new JdbcTemplate(dataSource);
jdbc.execute("delete from sec.authorities");
jdbc.execute("delete from sec.users");
}
#Test
public void testExists () {
userRepository.save(user1);
System.out.println("Users created, test exists");
assertTrue("User should exist", userRepository.existsByUsername("user"));
assertFalse("User should not exist", userRepository.existsByUsername("abcdefghj"));
}
}
RepositoryConfig
#Configuration
#ComponentScan("com.example.app.dao")
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {
"com.example.app.dao"
}, entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager")
public class RepositoryConfig {
#Profile("production")
#Bean(name = "dataSource")
public DataSource dataSource() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource("java:jboss/datasources/postgresqlDS");
}
#Profile("dev")
#Bean(name = "dataSource")
public DataSource jdbcDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setConnectionProperties(jdbcProperties().toString());
return dataSource();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean emf
= new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource());
emf.setPackagesToScan(new String[] { "com.example.app.dao" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
emf.setJpaVendorAdapter(vendorAdapter);
emf.setJpaProperties(hibernateProperties());
return emf;
}
private final Properties hibernateProperties() {
//...
}
private final Properties jdbcProperties() {
Properties jdbcProperties = new Properties();
jdbcProperties.setProperty("driverClassName", "${jdbc.driver}");
jdbcProperties.setProperty("url", "${jdbc.url}");
jdbcProperties.setProperty("username", "${jdbc.username}");
jdbcProperties.setProperty("password", "${jdbc.password}");
return jdbcProperties;
}
#Bean
public TransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
transactionManager.setDataSource(dataSource());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptiontranslator() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
UserRepository
public interface UserRepository extends CustomizedSave<User>, CrudRepository<User, Long> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
}
CustomizedSaveImpl
#Repository
public class CustomizedSaveImpl implements CustomizedSave<User>{
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#PersistenceContext
EntityManager em;
public CustomizedSaveImpl() {
System.out.println("successfully loaded users DAO");
}
public User save (User user) {
//...
}
}
This was due to a typo which created circular references:
jdbcDataSource() should return datasource instead of return dataSource();
The exception was thrown due to one of the dataSource beans pointing to another.
I am getting the below error when I try to run a spring batch.
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:779) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:760) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:747) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
at com.amhi.care.claims.query.batch.QueryBatchApplication.main(QueryBatchApplication.java:15) [classes/:na]
Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657) ~[na:1.8.0_192]
at java.util.ArrayList.get(ArrayList.java:433) ~[na:1.8.0_192]
at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.getNextJobParameters(JobLauncherCommandLineRunner.java:143) ~[spring-boot-autoconfigure-1.5.3.RELEASE.jar:1.5.3.RELEASE]
at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.execute(JobLauncherCommandLineRunner.java:212) ~[spring-boot-autoconfigure-1.5.3.RELEASE.jar:1.5.3.RELEASE]
at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.executeLocalJobs(JobLauncherCommandLineRunner.java:231) ~[spring-boot-autoconfigure-1.5.3.RELEASE.jar:1.5.3.RELEASE]
at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.launchJobFromProperties(JobLauncherCommandLineRunner.java:123) ~[spring-boot-autoconfigure-1.5.3.RELEASE.jar:1.5.3.RELEASE]
at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.run(JobLauncherCommandLineRunner.java:117) ~[spring-boot-autoconfigure-1.5.3.RELEASE.jar:1.5.3.RELEASE]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:776) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
... 4 common frames omitted
Code:
#Configuration
#EnableBatchProcessing
#EnableScheduling
#EnableTransactionManagement
#ComponentScan(QueryBatchConstants.COMPONENT_SCAN_PACKAGE)
#Import(DataSourcecConfiguration.class)
public class QueryBatchConfiguration {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Bean
public Job reminderJob() {
return jobBuilderFactory.get("reminderJob").flow(step()).next(closureStep()).end().build();
}
#Bean
public Step step() {
return stepBuilderFactory.get("step").tasklet(tasklet()).build();
}
#Bean
public Step closureStep() {
return stepBuilderFactory.get("closureStep").tasklet(closureTasklet()).build();
}
#Bean
public Tasklet tasklet(){
return new QueryBatchTasklet();
}
#Bean
public Tasklet closureTasklet(){
return new ClosureBatchTasklet();
}
#Bean
#JobScope
public JobParameters jobParamater(){
return new JobParametersBuilder()
.addDate("date", new Date())
.toJobParameters();
}
/**
* This method is used to configure the Dozer Mapper
*
* #return Mapper
* #throws IOException
*/
#Bean(name = "mapper")
public Mapper configDozerMapper() throws IOException {
DozerBeanMapper mapper = new DozerBeanMapper();
return mapper;
}
/**
* This method is used to create RIDC client manager
*
* #return IdcClientManager
*/
#Bean(name = "idcClientmanager")
public IdcClientManager idcClientmanager() {
return new IdcClientManager();
}
}
#Configuration
#EnableTransactionManagement
#ComponentScan(QueryBatchConstants.COMPONENT_SCAN_PACKAGE)
#PropertySource(QueryBatchConstants.CLASSPATH_APPLICATION_PROPERTIES)
public class DataSourcecConfiguration {
#Resource
private Environment env;
#Autowired
public DataSource dataSource;
/**
* This method is used to configure a data source
*
* #return DataSource
* #throws SQLException
*/
#Bean
public DataSource dataSource() throws SQLException {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty(QueryBatchConstants.DATABASE_DRIVER));
dataSource.setUrl(env.getRequiredProperty(QueryBatchConstants.DATABASE_URL));
dataSource.setUsername(env.getRequiredProperty(QueryBatchConstants.DATABASE_USERNAME));
dataSource.setPassword(env.getRequiredProperty(QueryBatchConstants.DATABASE_PSWRD));
return dataSource;
}
/**
* This method is used to configure a entity manager factory
*
* #return LocalContainerEntityManagerFactoryBean
* #throws SQLException
*/
#Autowired
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws SQLException {
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(dataSource());
entityManager.setPersistenceProviderClass(HibernatePersistence.class);
entityManager.setPackagesToScan(env.getRequiredProperty(QueryBatchConstants.PACKAGES_TO_SCAN));
entityManager.setJpaProperties(jpaProperties());
return entityManager;
}
/**
* This method is used to configure the JPA properties
*
* #return JPA Properties
*/
private Properties jpaProperties() {
Properties properties = new Properties();
properties.put(QueryBatchConstants.HIBERNATE_DIALECT, env.getRequiredProperty(QueryBatchConstants.HIBERNATE_DIALECT));
properties.put(QueryBatchConstants.HIBERNATE_SHOW_SQL, env.getRequiredProperty(QueryBatchConstants.HIBERNATE_SHOW_SQL));
properties.put(QueryBatchConstants.HIBERNATE_JDBC_META_DATA, env.getRequiredProperty(QueryBatchConstants.HIBERNATE_FALSE));
return properties;
}
/**
* This method is used to configure the transaction manager
*
* #param emf
* #return JpaTransactionManager
*/
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
return jdbcTemplate;
}
}
#Component
public class QueryBatchTasklet implements Tasklet{
#Autowired
private QueryBatchService autoClosureService;
#Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
autoClosureService.updateStatusAndTriggerComm();
return null;
}
}
#Component
public class ClosureBatchTasklet implements Tasklet{
#Autowired
private ClosureBatchService closureBatchService;
#Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
closureBatchService.updateStatusAndTriggerComm();
return null;
}
}
I had this error all of a sudden. Check if there are records in the database you connect to in the BATCH_JOB_INSTANCE table which don't have matching records in the BATCH_JOB_EXECUTION table. Or perhaps there is some other data inconsistency in the Spring batch metadata tables. If possible, drop and recreate them.
This error came about for us as the result of a database purge job which didn't delete everything it needed to.
Please see my answer on this similar question, it may help.
I have a spring batch project wherein I read data from a datasource , process the data and write into another primary data source. I am extending CrudRepository for dao operations.
I am trying to configure multiple datasources for my springbatch + spring boot application below is the package structure :
myproject
---com
---batch
---config
---firstDsConfig.java
---secondDsConfig.java
---firstrepository
---firstCrudRepository.java
---secondRepository
---SecondCrudRepository.java
---firstEntity
---firstDBEntity.java
---secondEntity
---secondDBEntity.java
----main
---MyMainClass.java
Code for firstDsConfig.java:
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "firstEntityManagerFactory",
transactionManagerRef = "firstTransactionManager",
basePackages = "com.batch.firstrepository"
)
#EnableTransactionManagement
public class FirstDbConfig {
#Primary
#Bean(name = "firstEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(final EntityManagerFactoryBuilder builder,
final #Qualifier("firstDs") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.batch.firstEntity")
.persistenceUnit("abc")
.build();
}
#Primary
#Bean(name = "firstTransactionManager")
public PlatformTransactionManager firstTransactionManager(#Qualifier("firstEntityManagerFactory")
EntityManagerFactory firstEntityManagerFactory) {
return new JpaTransactionManager(firstEntityManagerFactory);
}
#Primary
#Bean(name = "firstDs")
#ConfigurationProperties(prefix = "spring.datasource.first")
public DataSource firstDataSource() {
return DataSourceBuilder.create().build();
}
}
Code for secondDsConfig:
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "secondEntityManagerFactory",
transactionManagerRef = "secondTransactionManager",
basePackages = "com.batch.secondrepository"
)
#EnableTransactionManagement
public class FirstDbConfig {
#Primary
#Bean(name = "secondEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(final EntityManagerFactoryBuilder builder,
final #Qualifier("secondDs") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.batch.secondEntity")
.persistenceUnit("xyz")
.build();
}
#Primary
#Bean(name = "secondTransactionManager")
public PlatformTransactionManager secondTransactionManager(#Qualifier("secondEntityManagerFactory")
EntityManagerFactory firstEntityManagerFactory) {
return new JpaTransactionManager(secondEntityManagerFactory);
}
#Primary
#Bean(name = "secondDs")
#ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource secondDataSource() {
return DataSourceBuilder.create().build();
}
}
Here is my main class
#EnableScheduling
#EnableBatchProcessing
#SpringBootApplication(scanBasePackages = { "com.batch" })
public class MyMainClass {
#Autowired
private JobLauncher jobLauncher;
#Autowired
private JobRepository jobRepository;
#Autowired
#Qualifier(firstDs)
private DataSource dataSource;
#Autowired
#Qualifier("myJob")
private Job job;
public static void main(String[] args) throws Exception {
SpringApplication.run(MyMainClass.class, args);
}
#EventListener(ApplicationReadyEvent.class)
private void start() throws Exception {
jobLauncher.run(job, new JobParameters());
}
#Bean(name="jobService")
public JobService jobService() throws Exception {
SimpleJobServiceFactoryBean factoryBean = new SimpleJobServiceFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setJobRepository(jobRepository);
factoryBean.setJobLocator(new MapJobRegistry());
factoryBean.setJobLauncher(jobLauncher);
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
}
Here are the crud repo:
First curd repo
public interface FirstCrudRepository extends CrudRepository<FirstDbEntity, Integer> {
List<FirstDbEntity> findByOId(String oId);
}
Second curd repo
public interface SecondCrudRepository extends CrudRepository<SecondDbEntity, Integer> {
List<SecondDbEntity> findByPid(String pid);
}
When I run my application I see following error while saving record using FirstCrudRepository:
org.springframework.transaction.IllegalTransactionStateException: Pre-bound JDBC Connection found! JpaTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single JpaTransactionManager for all transactions on a single DataSource, no matter whether JPA or JDBC access.
Note: I am able to fetch details successfully from SecondCrudRepository
By default, if you provide a DataSource, Spring Batch will use a DataSourceTransactionManager which knows nothing about your JPA configuration. You need to tell Spring Batch to use your JpaTransactionManager. This is explained in the:
reference documentation: https://docs.spring.io/spring-batch/4.1.x/reference/html/index-single.html#javaConfig.
Javadoc of #EnableBatchProcessing: https://docs.spring.io/spring-batch/4.1.x/api/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.html
I suspect there are 2 different transaction managers present which would be causing issue. Annotate with #Transactional and specifying the transaction manager would help
Source:
Spring - Is it possible to use multiple transaction managers in the same application?
I have a problem with Spring Boot 2.0.0. When I throw an RuntimeException it is not rollback the transaction. I was using Spring Boot 1.5.9 with the same settings and it worked. It just migrated to Spring Boot 2 and stopped working.
My Configuration class:
#Configuration
#EnableJpaRepositories(basePackages = "com.test.repository")
#EnableTransactionManagement
public class DatabaseConfiguration {
public static final String MODEL_PACKAGE = "com.test.model";
#Value("${jdbc.url}")
private String url;
#Value("${jdbc.username}")
private String username;
#Value("${jdbc.password}")
private String password;
#Bean
public DataSource dataSource() throws SQLException {
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl(this.url);
dataSource.setUsername(this.username);
dataSource.setPassword(this.password);
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
My Business class:
#Service
public class TestBusinessImpl implements TestBusiness {
#Override
#Transactional
public void save(final Test test) {
this.testRepository.save(test);
throw new RuntimeException("Test rollback");
}
}
Does anyone know what may be happening?
Which dialect are you using? You must be specifying spring.jpa.properties.hibernate.dialect
Springboot 2.0 #Transaction doesn't get supported by org.hibernate.dialect.MySQL5Dialect
Rather use org.hibernate.dialect.MySQL5InnoDBDialect
I'm using spring-jpa. I have 2 tests.
#Test
#Transactional
public void testFindAll() {
List<Engine> eList = engineService.findAll();
Engine e = eList.get(0); //this engine id=3
List<Translation> tList = e.getTranslations();
for(Translation t : tList) {
...
}
}
This method fails with this exception:
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role: xxx.Engine.translations, could not initialize
proxy - no Session
However, this method works just fine:
#Test
#Transactional
public void testFindOne() {
Engine e = engineService.findOne(3);
List<Translation> tList = e.getTranslations();
for(Translation t : tList) {
...
}
}
Why translation list is successfully loaded in one case, but not in another?
EDIT: service/repo code:
public interface EngineRepository extends JpaRepository<Engine, Integer>
{
}
.
#Service
#Transactional
public class EngineService
{
#Autowired
private EngineRepository engineRepository;
public List<Engine> findAll()
{
return engineRepository.findAll();
}
public Engine findOne(Integer engId)
{
return engineRepository.findOne(engId);
}
}
.
public class Engine implements Serializable {
...
#OneToMany
#JoinColumn(name="ID", referencedColumnName="TRAN_ID", insertable=false, updatable=false, nullable=true)
#LazyCollection(LazyCollectionOption.EXTRA)
private List<Translation> translations;
...
}
Config:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"xxx.dao"})
#ComponentScan(basePackages = {"xxx.dao", "xxx.service", "xxx.bean"})
#PropertySource("classpath:application.properties")
public class SpringDataConfig {
#Autowired
private Environment environment;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(environment.getProperty("db.url"));
dataSource.setDriverClassName(environment.getProperty("db.driverClass"));
dataSource.setUsername(environment.getProperty("db.username"));
dataSource.setPassword(environment.getProperty("db.password"));
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setDatabase(Database.POSTGRESQL);
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getProperty("hibernate.showSQL"));
properties.put("hibernate.format_sql", environment.getProperty("hibernate.formatSQL"));
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPackagesToScan("xxx.model");
entityManagerFactoryBean.setJpaVendorAdapter(hibernateJpaVendorAdapter);
entityManagerFactoryBean.setJpaProperties(properties);
return entityManagerFactoryBean;
}
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
I think the problem here is that the session gets closed after the first line in the first case. You should check out the JpaRepository implementation of findAll().
Integration Testing with Spring
It seems you're failing to provide a Spring Context within your TestCase, what that means? The #Transactional is being ignored. Therefore you end up with the closed session exception, because there is no transaction.
Take a look how to configure a TestCase with a Spring Context here
#RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
#ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
public class MyTest {
#Autowired
EngineService engineService;
#Test
#Transactional
public void testFindOne() {}
#Test
#Transactional
public void testFindAll() {}
}