Use Spring and EJB transaction together - spring

I am getting the error while try to add the entity into the DB. I have referred this. But it did not have much info regarding my issue
I have one EJB jar which I use to manage the get,save and update the entity.
Here is my remote ejb
#Remote
public abstract interface DatalayerService{
public abstract void add(Object object)
}
Here is the implemetation
#TransactionManagement(TransactionManagementType.CONTAINER)
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class DatalayerServiceImpl implements DatalayerService{
#PersistenceContext(name="myPersistenceUnit")
EntityManager em = null;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void add(Object object) throws FatalException{
try {
em.persist(object);
}
catch (Throwable e){
throw manageDatalayerError(e);
}
finally {
}
}
}
I am trying to use the above ejb in my application
1)
#ComponentScan({"com.springboot"})
#EnableJpaRepositories
#SpringBootApplication
public class SpringEjbApplication extends SpringBootServletInitializer{
public static void main(String args[]){
SpringApplication.run(SpringApplication.class,args);
}
#Bean
public DatalayerService datalayerService() throws NamingException{
return new DatalayerServiceImpl();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException{
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[]{"com.springboot.pojo"});
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setPersistenceProviderClass(HibernatePersistenceProvider.class);
em.setJpaVendorAdapter(vendorAdapter);
em.setPersistenceUnitName("myPersistenceUnit");
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public DataSource dataSource() throws NamingException{
return (DataSource) new JndiTemplate().lookup("openejb:Resource/MyDataSource");
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
private Properties additionalProperties(){
Properties properties = new Properties();
properties.setProperty("hibernate.max_fetch_depth", "3");
properties.setProperty("hibernate.default_batch_fetch_size", "2");
properties.setProperty("hibernate.jdbc.batch_size", "100");
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("hibernate.format_sql", "false");
return properties;
}
}
2)Rest Service
#RestController
public class HomeEndPoint{
#Autowired
private IUserService iUserService;
#GetMapping("/createUser")
#Transactional(rollbackFor = {ServiceExpectedException.class,FatalException.class,MandatoryParameterMissingFatalException.class})
public void createUser() throws FatalException,ServiceExpectedException{
iUserService.createUser();
}
}
3)User Service and its impl
public interface IUserService{
public void createUser() throws FatalException;
}
#Service
#Transactional(rollbackFor = {ServiceExpectedException.class,FatalException.class,MandatoryParameterMissingFatalException.class})
public class UserServiceImpl implements IUserService{
#Autowired
private DatalayerGenericService datalayerGenericService;
#Override
public void createUser() throws FatalException,ServiceExpectedException{
Team team = new Team(simpleContextService);
team.setGroupName("MyTeam");
team.setStoreId(100);
// Team
datalayerGenericService.add(team);
Log.info(this,"add team ");
// Build user1
datalayerGenericService.add(user1);
Log.info(this,"############################# Added User1 ######################################");
// Build user2
datalayerGenericService.add(user2);
Log.info(this,"############################# Added User2 ######################################");
}
}
I tried to run the rest service with url
http://localhost:6180/SpringBootDatalayer/createUser
I am getting the below exception.
INFO: (com.edifixio.springboot.service.impl.UserServiceImpl)
####################### teamOid 20 INFO: (com.edifixio.springboot.service.impl.UserServiceImpl)
####################### Added User1 ##################################### INFO: (com.edifixio.springboot.service.impl.UserServiceImpl)
####################### Added User2 ##################################### INFO : http-nio-6180-exec-1 : AbstractBatchImpl.release : HHH000010: On release of batch it still
contained JDBC statements 2018-03-27 16:28:17,683 : ERROR :
http-nio-6180-exec-1 : BatchingBatch.performExecution : HHH000315:
Exception executing batch [java.sql.BatchUpdateException: Batch entry
0 insert into TEAM (CREATE_TIMESTAMP, PROJECT_GROUP_NAME,
LAST_UPDATE_TIMESTAMP, STORE_ID, ID) values ('2018-3-27
16:28:17.447000 +5:30:0', 'MMA_TEST', '2018-3-27 16:28:17.503000
+5:30:0', 100, 21) was aborted. Call getNextException to see the cause.], SQL: insert into TEAM (CREATE_TIMESTAMP, PROJECT_GROUP_NAME,
LAST_UPDATE_TIMESTAMP, STORE_ID, ID) values (?, ?, ?, ?, ?)
As you can see from the exception It was creating team,user1 and user2 with the use of datalayerGenericService. But tries to executes again to give AbstractBatchImpl.release : HHH000010: On release of batch it still contained JDBC statements.
Here is the exception I got:
java.sql.BatchUpdateException: Batch entry 1 insert into TEAM
(CREATE_TIMESTAMP, PROJECT_GROUP_NAME, LAST_UPDATE_TIMESTAMP,
STORE_ID, ID) values ('2018-3-29 12:9:26.611000 +5:30:0', 'MMA_TEST',
'2018-3-29 12:9:26.836000 +5:30:0', 100, 23) was aborted. Call
getNextException to see the cause.
How do I resolve this issue? Why the query executing twice?
If there is any exception occurs in EJB methods, it should rollback the transaction. But it is not happening
Note : If I remove #Transactional annotation from the service it works fine.
I am using TomEE 7.0.2 server.

Technically you don't insert neither Users nor team. You persist them to PersistenceContext. When your code leaves
#Transactional(rollbackFor = {ServiceExpectedException.class,FatalException.class,MandatoryParameterMissingFatalException.class})
public void createUser()
It has to be commited or flushed or session closed as far as you have marked it as required. It failures on very first insertion (team) because of you didn't have some of any listed above.
And yes - you are using noting from EJB here. In fact you use Hibernate and only #Transactional as Spring service annotation has effect here.

How did I resolve this issue?
I was misunderstood that there is EJB stuff involved here
But #M. Deinum clarified me that here no EJB involved, regardless the fact that I have added those annotations, which means my DatalayerServiceImpl is just a SpringBean.
Adding all those interfaces and annotations doesn't give me anything but added complexity and overhead in code that is silently ignored by Spring (apart from the Transactional as spring does support that).
Coming to my problem I have declared Team In Users class
#ManyToOne(cascade={CascadeType.PERSIST},fetch=FetchType.LAZY)
#JoinColumn(name="TEAM_ID",nullable=false)
#JsonBackReference
private Team team;
While creating the User I was referencing the Created team instance to Users class
I was doing
Team team = new Team(simpleContextService);
team.setGroupName("MMA_TEST");
team.setStoreId(100);
// Team
Long teamOid = datalayerGenericService.updateWithId(team);
Users user1 = new Users(simpleContextService);
...Set other fields
user1.setTeam(team);
As the Team declared as Cascade type Persist inside Users, it was trying to insert the team when persisting the Users.
So I have changed the Cascade type to MERGE. So it was working fine.

Related

How to register hibernate spring entity listeners

I have built an entity listener but have not figured out how to register it so that it will get called. This all runs, and I verified in the debugger that the
registration code executes (apparently successfully) at startup. But the debugger never stops in the listener code.
This is my listener:
public class DirtyAwareListener implements PostLoadEventListener
{
#Override
public void onPostLoad(PostLoadEvent postLoadEvent)
{
if (postLoadEvent.getEntity() instanceof DirtyAware)
{
((DirtyAware)postLoadEvent.getEntity()).commitFields();
}
}
}
and this is the registration component:
#Component
public class HibernateListenerConfigurer
{
#PersistenceUnit
private EntityManagerFactory emf;
#Autowired
private SessionFactory sessionFactory;
#PostConstruct
protected void init()
{
DirtyAwareListener listener = new DirtyAwareListener();
// SessionFactoryImpl sessionFactory = emf.unwrap(SessionFactoryImpl.class);
EventListenerRegistry registry = ((SessionFactoryImpl)sessionFactory).getServiceRegistry().getService(EventListenerRegistry.class);
registry.getEventListenerGroup(EventType.POST_LOAD).appendListener(listener);
}
}
Here is how my general Hibernate configuration code generates a session factory:
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(getDataSource());
sessionFactory.setPackagesToScan("com.my.entities");
sessionFactory.setHibernateProperties(getHibernateProperties());
sessionFactory.setEntityInterceptor(new DirtyAwareInterceptor());
return sessionFactory;
Note that the interceptor does work as expected (but unfortunately does not have hooks where I need them.)
To add entity listeners implement org.hibernate.integrator.spi.Integrator. See example https://www.boraji.com/hibernate-5-event-listener-example
I got this working as desired using the Integrator approach as Anton suggested. The link provided in his answer did not provide sufficient information for me to get this to work - I had to reference multiple posts and also do a bit of trial and error. Since I could not find a single post which provided the info, here is how I did it:
The listener code is the same as the above. The Configurer code is not needed - I deleted it. Here is the new Integrator code:
#Component
public class EventListenerIntegrator implements Integrator
{
#Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactoryImplementor, SessionFactoryServiceRegistry sessionFactoryServiceRegistry)
{
EventListenerRegistry eventListenerRegistry =
sessionFactoryServiceRegistry.getService(EventListenerRegistry.class);
DirtyAwareListener t = new DirtyAwareListener();
eventListenerRegistry.getEventListenerGroup(EventType.POST_LOAD).appendListener(t);
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactoryImplementor, SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {}
}
And here is the revised getSessionFactory method on my #Configuration class:
private static SessionFactory sessionFactory = null;
#Bean
public SessionFactory getSessionFactory()
{
if (sessionFactory == null)
{
BootstrapServiceRegistry bootstrapRegistry =
new BootstrapServiceRegistryBuilder()
.applyIntegrator(new EventListenerIntegrator())
.build();
StandardServiceRegistryBuilder registryBuilder =
new StandardServiceRegistryBuilder(bootstrapRegistry);
registryBuilder.applySetting(org.hibernate.cfg.Environment.DATASOURCE, getDataSource());
registryBuilder.applySettings(getHibernateProperties());
StandardServiceRegistry registry = registryBuilder.build();
MetadataSources sources = new MetadataSources(registry).addPackage("com.my.entities");
sources.addAnnotatedClass(User.class);
Metadata metadata = sources.getMetadataBuilder().build();
sessionFactory = metadata.getSessionFactoryBuilder().build();
}
return sessionFactory;
}
Note: I think the addPackage call is not needed and does not do anything. I had hoped it would do the package scan the old code was doing, but it does not do that. I simply changed that to explicity add each annotated class.

Override Default Datasource getConnection()

I am trying to convert a Spring application (for the most part) to a Spring Boot application. In the app, I have an HTTP basic filter that collects a username and password, this is then passed as variables in a DataSource implementation.
In this DataSource, the getConnection() method is so:
#Override\n public Connection getConnection() throws SQLException {
Statement stmt = null;
try {
ConnectionWrapper connection = this.authenticatedConnection.get();
if (connection == null) {
connection = new ConnectionWrapper(this.dataSource.getConnection());
StringBuilder command;
// The CONNECT command allows indicating a user name, a password
// and a database to initiate a
// new session in the server with a new profile.
command = new StringBuilder("CONNECT USER ").append(this.parameters.get().get(USER_NAME)).append(" PASSWORD ")
.append("'").append(this.parameters.get().get(PASSWORD_NAME)).append("'").append(" DATABASE ")
.append(this.parameters.get().get(DATA_BASE_NAME));
this.authenticatedConnection.set(connection);
stmt = connection.createStatement();
stmt.execute(command.toString());
}
return connection;
} catch (final SQLException e) {...`
(With \n as a new line due to StackOverflow formatting issues)
In Spring, I am able to implement #Autowired Private DataSource dataSource without a problem. In Spring Boot, as I understand it, the Object needs to be a Bean to use #Autowired, but when I add #Bean before this implemented DataSource I get "The annotation #Bean is disallowed for this location"
How can I get it so that I can do a dataSource.getConnection(); and get a connection from the primary DataSource, or be able to Override the methods of the primary DataSource?
The way I see it, there are 4 possible solutions listed here in order of preference:
Create a DataSource that is actually overwriting the spring.datasource' methods.
Get this implementation "Beanified" so I can just #Autowired the dataSource again.
I think I can skip the #Autowired and simply set this.dataSource = [unknown reference to spring.datasource defined in application.properties]
Create another DataSource class ProgrammedDataSource configured with the spring.datasource properties, then set it as this.dataSource = new ProgrammedDataSource();
but my attempts at implementing any of these solutions have produced this question.
I figured it out. I didn't need to make the Bean there, although I am still not sure why I was not allowed to call #Bean before the DataSource, but regardless.
In the application I had:
public class ServiceApplication {
#Bean
#Primary
#ConfigurationProperties("spring.datasource")
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
#Bean(name="AuthDataSource")
public DataSource authDataSource() {
return new AuthDataSource();
}
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
and in the controller I had:
#Controller
#RequestMapping("/service")
public class ServiceController {
#Autowired
public void MyBean(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = new JdbcTemplate(new AuthDataSource());
} ...
However, since I was calling new AuthDataSource() inside that JdbcTemplate, it was not doing the Autowiring. Now the Controller looks like this and it works:
#Controller
#RequestMapping("/service")
public class ServiceController {
#Autowired
#Qualifier("AuthDataSource")
private DataSource datasource;
private JdbcTemplate jdbcTemplate;
#Autowired
public void MyBean(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
} ...

InvalidDataAccessApiUsageException: Executing an update/delete query Spring XML to Java config

I'm trying to convert spring xml configuration to java configuration. This works perfectly through XML configuration. However, it throws the following exception if I use java config initializer. This happens when it tries to run JQL. The application starts properly though (with all JPA mapping initialized).
org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:410) [spring-orm-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:216) [spring-orm-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417) [spring-orm-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslat
Following is my persistence initializer class. Bit of reading suggested me this is related to transactions are not being started properly. I've put debug points to each of these methods but transactionManager method never gets executed during server startup or any later time. I'm not sure what am I doing wrong :(. Same code based works perfectly when persistence is initialized through persistence.xml.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "au.mypkg")
public class DatabaseConfig {
#Bean(name = "dataSource")
#Primary
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:jboss/datasources/mydb");
}
#PersistenceContext(unitName = "persistenceUnit")
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws Exception {
..........
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
final JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setTransactionManagerName(JBOSS_TRANSACTION_MANANGER);
return transactionManager;
}
Error occurs when accessing this method on Dao
public void updateById(final Long id) {
final String sqlQuery = "UPDATE testtable w SET w.LAST_ACCESSED = :date WHERE w.testtable_ID = :testid";
final Query query = dao.createNativeQuery(sqlQuery);
query.setParameter("date", new Date());
query.setParameter("testid", id);
query.executeUpdate();
}
I had the some problem and I resolved it by just adding #Transactional annotation on the service method that perform delete or update.
In my case it was a method that call a repository method which execute a delete by jpql like this by I think it can solve you problem too:
#Modifying
#Query("delete from OtherPayment otherPayment " +
"where otherPayment.otherPaymentParam.id = :otherPaymentParamId")
void deleteByOtherPaymentParamId(#Param("otherPaymentParamId") Long otherPaymentParamId);
Finally figured out what was going on with it. The reason this wasn't hitting the debug point was, EnableTransactionManagement imposes to auto-configure transactions for you. So if your transaction manager configured with default name which is in my case, it wouldn't try to call my method to configure transactions. The only way to get around with this is to use a different name for your transaction manager and pass that ref as a parameter on enableJPARepositories annotation.If you use one of the default names, it wouldn't make this call.
#Primary
#Bean(name = "myRealTransactionManager")
public PlatformTransactionManager transactionManager() {
final JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setTransactionManagerName(JBOSS_TRANSACTION_MANANGER);
return transactionManager;
}
.. and then
#EnableJpaRepositories(basePackages = "au.mypkg", transactionManagerRef = "myRealTransactionManager"
The other problem was I have had used setdatasource as opposed to setJtaDataSource on LocalContainerEntityManagerFatoryBean.

Spring/Hibernate #Transactional on method prevents update SQL

Spring v4.2.5 Release
Hibernate v5.1.0.Final
I have a Junit test method which performs a load, updates a property and calls saveOrUpdate(bean).
It's behaving oddly in that adding #Transactional to the method signature prevents the update SQL from being performed (No SQL generated in log).
Remove the #Transactional and the update SQL is generated and the database updated.
#Configuration
#EnableTransactionManagement
#PropertySource(
{
"classpath:jdbc.properties",
"classpath:hibernate.properties"
})
#ComponentScan(value = "com.savant.test.spring.donorservice.core.dao")
public class ApplicationContext {
#Bean(destroyMethod = "close")
#Autowired
public DataSource dataSource() {
// Hikari is a connection pool manager.
HikariDataSource dataSource = new HikariDataSource();
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
dataSource.setJdbcUrl(env.getProperty("jdbc.url"));
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setIsolateInternalQueries(true);
System.out.println(dataSource);
dataSource.setConnectionTestQuery("SELECT count(*) from system.onerow");
dataSource.setMaximumPoolSize(3);
dataSource.setAutoCommit(false);
return dataSource;
}
#Bean
#Autowired
public LocalSessionFactoryBean sessionFactory(DataSource datasouce) {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(datasouce);
sessionFactory.setPackagesToScan(package_to_scan);
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
private Properties hibernateProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.put(hibernate_dialect, env.getProperty(hibernate_dialect));
hibernateProperties.put(hibernate_current_session_context_class, env.getProperty(hibernate_current_session_context_class));
hibernateProperties.put(hibernate_connection_autocommit, env.getProperty(hibernate_connection_autocommit));
hibernateProperties.put(hibernate_format_sql, env.getProperty(hibernate_format_sql));
hibernateProperties.put(hibernate_hbm2ddl_auto, env.getProperty(hibernate_hbm2ddl_auto));
hibernateProperties.put(hibernate_show_sql, env.getProperty(hibernate_show_sql));
// hibernateProperties.put(hibernate_connection_provider_class, env.getProperty(hibernate_connection_provider_class));
return hibernateProperties;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager(sessionFactory);
return txManager;
}
The entities have been auto-generated using Netbeans 'Entity classes from Database'.
The main Entity has
A one-to-one relationship with FetchType.EAGER
A one-to-many relationship with FetchType.EAGER (it was LAZY - read below).
The test method looks like this.
#Test
#Transactional
public void c_testUpdateAddress1() {
System.out.println("findById");
String id = donorId;
Donor donor = donorDao.findById(id);
donor.setAbogrp(" O");
for (DonorAddress da : donor.getDonorAddressCollection()) {
da.setAddr1("Updated line");
System.out.println(da.getDonorAddressPK().getAddrtype() + " " + da.getAddr1());
}
System.out.println("Update");
Donor savedDonor = donorDao.save(donor);
}
Without #Transactional The update SQL is generated and the database
is updated.
With #Transactional The update SQL is not generated, does not appear
in the log. There are no exceptions, stepping over the Save method
in my Dao implementation everything appears fine. The bean passed in
has the correct values (updated field values), the bean returned has
the updated field values - just no SQL generated.
#Override
public Donor save(Donor bean) {
getSession().saveOrUpdate(bean);
return bean;
}
The reason I need #Transactional is to allow the address to the LAZY.
Without #Transactional I can't access the address as LAZY due to exception "failed to lazily initialize a collection of role: could not initialize proxy - no Session"
Which is as expected.
A transaction is started as soon as a #Transactional method is detected and committed as soon as that method call ends. Which in the case of a test is after the end of the test method. So during your tests you will not see the SQL.
Also when using #Transactional on a Spring based test it will by default do a rollback instead of a commit. See here in the reference guide on the default and how to change it.
Answer was provided by M Deinum as a comment

spring-boot-starter-jta-atomikos and spring-boot-starter-batch

Is it possible to use both these starters in a single application?
I want to load records from a CSV file into a database table. The Spring Batch tables are stored in a different database, so I assume I need to use JTA to handle the transaction.
Whenever I add #EnableBatchProcessing to my #Configuration class it configures a PlatformTransactionManager, which stops this being auto-configured by Atomikos.
Are there any spring boot + batch + jta samples out there that show how to do this?
Many Thanks,
James
I just went through this and I found something that seems to work. As you note, #EnableBatchProcessing causes a DataSourceTransactionManager to be created, which messes up everything. I'm using modular=true in #EnableBatchProcessing, so the ModularBatchConfiguration class is activated.
What I did was to stop using #EnableBatchProcessing and instead copy the entire ModularBatchConfiguration class into my project. Then I commented out the transactionManager() method, since the Atomikos configuration creates the JtaTransactionManager. I also had to override the jobRepository() method, because that was hardcoded to use the DataSourceTransactionManager created inside DefaultBatchConfiguration.
I also had to explicitly import the JtaAutoConfiguration class. This wires everything up correctly (according to the Actuator's "beans" endpoint - thank god for that). But when you run it the transaction manager throws an exception because something somewhere sets an explicit transaction isolation level. So I also wrote a BeanPostProcessor to find the transaction manager and call txnMgr.setAllowCustomIsolationLevels(true);
Now everything works, but while the job is running, I cannot fetch the current data from batch_step_execution table using JdbcTemplate, even though I can see the data in SQLYog. This must have something to do with transaction isolation, but I haven't been able to understand it yet.
Here is what I have for my configuration class, copied from Spring and modified as noted above. PS, I have my DataSource that points to the database with the batch tables annotated as #Primary. Also, I changed my DataSource beans to be instances of org.apache.tomcat.jdbc.pool.XADataSource; I'm not sure if that's necessary.
#Configuration
#Import(ScopeConfiguration.class)
public class ModularJtaBatchConfiguration implements ImportAware
{
#Autowired(required = false)
private Collection<DataSource> dataSources;
private BatchConfigurer configurer;
#Autowired
private ApplicationContext context;
#Autowired(required = false)
private Collection<BatchConfigurer> configurers;
private AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();
#Bean
public JobRepository jobRepository(DataSource batchDataSource, JtaTransactionManager jtaTransactionManager) throws Exception
{
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(batchDataSource);
factory.setTransactionManager(jtaTransactionManager);
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
public JobLauncher jobLauncher() throws Exception {
return getConfigurer(configurers).getJobLauncher();
}
// #Bean
// public PlatformTransactionManager transactionManager() throws Exception {
// return getConfigurer(configurers).getTransactionManager();
// }
#Bean
public JobExplorer jobExplorer() throws Exception {
return getConfigurer(configurers).getJobExplorer();
}
#Bean
public AutomaticJobRegistrar jobRegistrar() throws Exception {
registrar.setJobLoader(new DefaultJobLoader(jobRegistry()));
for (ApplicationContextFactory factory : context.getBeansOfType(ApplicationContextFactory.class).values()) {
registrar.addApplicationContextFactory(factory);
}
return registrar;
}
#Bean
public JobBuilderFactory jobBuilders(JobRepository jobRepository) throws Exception {
return new JobBuilderFactory(jobRepository);
}
#Bean
// hopefully this will autowire the Atomikos JTA txn manager
public StepBuilderFactory stepBuilders(JobRepository jobRepository, JtaTransactionManager ptm) throws Exception {
return new StepBuilderFactory(jobRepository, ptm);
}
#Bean
public JobRegistry jobRegistry() throws Exception {
return new MapJobRegistry();
}
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
AnnotationAttributes enabled = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(
EnableBatchProcessing.class.getName(), false));
Assert.notNull(enabled,
"#EnableBatchProcessing is not present on importing class " + importMetadata.getClassName());
}
protected BatchConfigurer getConfigurer(Collection<BatchConfigurer> configurers) throws Exception {
if (this.configurer != null) {
return this.configurer;
}
if (configurers == null || configurers.isEmpty()) {
if (dataSources == null || dataSources.isEmpty()) {
throw new UnsupportedOperationException("You are screwed");
} else if(dataSources != null && dataSources.size() == 1) {
DataSource dataSource = dataSources.iterator().next();
DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource);
configurer.initialize();
this.configurer = configurer;
return configurer;
} else {
throw new IllegalStateException("To use the default BatchConfigurer the context must contain no more than" +
"one DataSource, found " + dataSources.size());
}
}
if (configurers.size() > 1) {
throw new IllegalStateException(
"To use a custom BatchConfigurer the context must contain precisely one, found "
+ configurers.size());
}
this.configurer = configurers.iterator().next();
return this.configurer;
}
}
#Configuration
class ScopeConfiguration {
private StepScope stepScope = new StepScope();
private JobScope jobScope = new JobScope();
#Bean
public StepScope stepScope() {
stepScope.setAutoProxy(false);
return stepScope;
}
#Bean
public JobScope jobScope() {
jobScope.setAutoProxy(false);
return jobScope;
}
}
I found a solution where I was able to keep #EnableBatchProcessing but had to implement BatchConfigurer and atomikos beans, see my full answer in this so answer.

Resources