Why isn't JobScope and StepScope available from an MVC thread? - spring-batch

I'm getting Error creating bean with name 'scopedTarget.scopedTarget.processVlsCasesJob': Scope 'job' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton from a job factory class. The factory is where the job and step beans are created in the correct job/step scopes from a bean invoked during main application start up.
#Component("processVlsCasesJobFactory")
public class ProcessVlsCasesJobFactoryImpl
extends BatchJobFactoryAncestorImpl
implements ProcessVlsCasesJobFactory {
...
#Bean
#Scope(scopeName = "job", proxyMode = ScopedProxyMode.INTERFACES)
public ProcessVlsCasesJob processVlsCasesJob() {
return new ProcessVlsCasesJobImpl();
}
...
#Bean
#Scope(scopeName = "step", proxyMode = ScopedProxyMode.INTERFACES)
public ProcessVlsCasesProcessCases processVlsCasesProcessCases() {
return new ProcessVlsCasesProcessCasesImpl();
}
...
// other bean methods creating the step objects
Any attempt to allow Spring to auto-register any bean in the Job/Steps scope fails with that type of error. If those scopes are only available when (I guess) a job is running, how do I "create" the bean in the scope from the thread of the main MVC application running in Tomcat?

Why isn't JobScope and StepScope available from an MVC thread?
Those are custom scopes specific to Spring Batch, they are not part of Spring MVC. You need to specifically register them (or use #EnableBatchProcessing to have them automatically registered)
how do I "create" the bean in the scope from the thread of the main MVC application running in Tomcat?
The main thread (processing the web request) should call a JobLauncher configured with an asynchronous TaskExecutor so that the batch job is executed in a separate thread. Please see the Running Jobs from within a Web Container section which provides more details and a code example of how to do that.

I finally found the answer: #EnableBatchProcessing doesn't work within an MVC application context. In the #Configuration bean I created to configure SB (with DB2) and set up all the SB beans (like jobLauncher), I added:
jobScope = new JobScope();
jobScope.setAutoProxy(Boolean.FALSE);
jobScope.setName(JobScoped.SCOPE_NAME);
((ConfigurableBeanFactory)applicationContext.getAutowireCapableBeanFactory())
.registerScope(JobScoped.SCOPE_NAME, jobScope);
stepScope = new StepScope();
stepScope.setAutoProxy(Boolean.FALSE);
stepScope.setName(StepScoped.SCOPE_NAME);
((ConfigurableBeanFactory)applicationContext.getAutowireCapableBeanFactory())
.registerScope(StepScoped.SCOPE_NAME, stepScope);
Then the two scopes were finally available at run time and the job/step scoped beans were registered at deployment and ran properly.
Was #EBP added as part of Spring Boot? Is it only supposed to be used via a command line tool?

Related

Role of PlatformTransactionManager with Spring batch 5

The documentation is not very clear about the role of PlatformTransactionManager in steps configuration.
First, stepBuilder.tasklet and stepBuilder.chunk requires a PlatformTransactionManager as second parameter while the migration guide says it is now required to manually configure the transaction manager on any tasklet step definition (...) This is only required for tasklet steps, other step types do not require a transaction manager by design..
More over, in the documentation the transactionManager is injected via a method parameter:
/**
* Note the TransactionManager is typically autowired in and not needed to be explicitly
* configured
*/
But the transactionManager created by Spring Boot is linked to the DataSource created by Spring Boot based on spring.datasource.url. So with autoconfiguration, the following beans works together: dataSource, platformTransactionManager, jobRepository. It makes sense for job and step executions metadata management.
But unless readers, writers and tasklet works with this default DataSource used by JobOperator, the auto configured transactionManager must not be used for the steps configuration. Am I right ?
Tasklets or a chunk oriented steps will often need another PlatformTransactionManager:
if a step writes data in a specific db it needs a specific DataSource (not necessarily declared as bean otherwise the JobRepository will use it) and a specific PlatformTransactionManager linked to this DataSource
if a step writes data in a file or send message to a MOM, the ResourcelessTransactionManager is more appropriate. This useful implementation is not mentioned in the documentation.
As far as I understand, the implementation of PlatformTransactionManager for a step depends on where the data are written and has nothing to do with the transactionManager bean used by the JobOperator Am I right ?
Example:
var builder = new StepBuilder("step-1", jobRepository);
PlatformTransactionManager txManager = new ResourcelessTransactionManager();
return builder.<Input, Output> chunk(10, txManager)
.reader(reader())
.processor(processor())
.writer(writer()/*a FlatFileItemWriter*/)
.build();
or
#SpringBootApplication
#EnableBatchProcessing
public class MyJobConfiguration {
private DataSource dsForStep1Writer;
public MyJobConfiguration(#Value("${ds.for.step1.writer.url"} String url) {
this.dsForStep1Writer = new DriverManagerDataSource(url);
}
// reader() method, processor() method
JdbcBatchItemWriter<Output> writer() {
return new JdbcBatchItemWriterBuilder<Output>()
.dataSource(this.dsForStep1Writer)
.sql("...")
.itemPreparedStatementSetter((item, ps)->{/*code*/})
.build();
}
#Bean
Step step1(JobRepository jobRepository) {
var builder = new StepBuilder("step-1", jobRepository);
var txManager = new JdbcTransactionManager(this.dsForStep1Writer);
return builder.<Input, Output> chunk(10, txManager)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
// other methods
}
Is that correct ?
Role of PlatformTransactionManager with Spring batch 5
The role of the transaction manager did not change between v4 and v5. I wrote an answer about this a couple of years ago for v4, so I will update it for v5 here:
In Spring Batch, there are two places where a transaction manager is used:
In the proxies created around the JobRepository/JobExplorer to create transactional methods when interacting with the job repository/explorer
In each step definition to drive the step's transaction
Typically, the same transaction manager is used in both places, but this is not a requirement. It is perfectly fine to use a ResourcelessTransactionManager with the job repository to not store any meta-data and a JpaTransactionManager in the step to persist data in a database.
Now in v5, #EnableBatchProcessing does not register a transaction manager bean in the application context anymore. You either need to manually configure one in the application context, or use the one auto-configured by Spring Boot (if you are a Spring Boot user).
What #EnableBatchProcessing will do though is look for a bean named transactionManager in the application context and set it on the auto-configured JobRepository and JobExplorer beans (this is configurable with the transactionManagerRef attribute). Again, this transaction manager bean could be manually configured or auto-configured by Boot.
Once that in place, it is up to you to set that transaction manager on your steps or not.

Spring Boot with application managed persistence context

I am trying to migrate an application from EJB3 + JTA + JPA (EclipseLink). Currently, this application makes use of application managed persistent context due to an unknown number of databases on design time.
The application managed persistent context allows us to control how to create EntityManager (e.g. supply different datasources JNDI to create proper EntityManager for specific DB on runtime).
E.g.
Map properties = new HashMap();
properties.put(PersistenceUnitProperties.TRANSACTION_TYPE, "JTA");
//the datasource JNDI is by configuration and without prior knowledge about the number of databases
//currently, DB JNDI are stored in a externalized file
//the datasource is setup by operation team
properties.put(PersistenceUnitProperties.JTA_DATASOURCE, "datasource-jndi");
properties.put(PersistenceUnitProperties.CACHE_SHARED_DEFAULT, "false");
properties.put(PersistenceUnitProperties.SESSION_NAME, "xxx");
//create the proper EntityManager for connect to database decided on runtime
EntityManager em = Persistence.createEntityManagerFactory("PU1", properties).createEntityManager();
//query or update DB
em.persist(entity);
em.createQuery(...).executeUpdate();
When deployed in a EJB container (e.g. WebLogic), with proper TransactionAttribute (e.g. TransactionAttributeType.REQUIRED), the container will take care of the transaction start/end/rollback.
Now, I am trying to migrate this application to Spring Boot.
The problem I encounter is that there is no transaction started even after I annotate the method with #Transactional(propagation = Propagation.REQUIRED).
The Spring application is packed as an executable JAR file and run with embadded Tomcat.
When I try to execute those update APIs, e.g. EntityManager.persist(..), EclipseLink always complains about:
javax.persistence.TransactionRequiredException: 'No transaction is currently active'
Sample code below:
//for data persistence
#Service
class DynamicServiceImpl implements DynamicService {
//attempt to start a transaction
#Transactional(propagation = Propagation.REQUIRED)
public void saveData(DbJndi, EntityA){
//this return false that no transaction started
TransactionSynchronizationManager.isActualTransactionActive();
//create an EntityManager based on the input DbJndi to dynamically
//determine which DB to save the data
EntityManager em = createEm(DbJndi);
//save the data
em.persist(EntityA);
}
}
//restful service
#RestController
class RestController{
#Autowired
DynamicService service;
#RequestMapping( value = "/saveRecord", method = RequestMethod.POST)
public #ResponseBody String saveRecord(){
//save data
service.saveData(...)
}
}
//startup application
#SpringBootApplication
class TestApp {
public static void main(String[] args) {
SpringApplication.run(TestApp.class, args);
}
}
persistence.xml
-------------------------------------------
&ltpersistence-unit name="PU1" transaction-type="JTA">
&ltproperties>
&lt!-- comment for spring to handle transaction??? -->
&lt!--property name="eclipselink.target-server" value="WebLogic_10"/ -->
&lt/properties>
&lt/persistence-unit>
-------------------------------------------
application.properties (just 3 lines of config)
-------------------------------------------
spring.jta.enabled=true
spring.jta.log-dir=spring-test # Transaction logs directory.
spring.jta.transaction-manager-id=spring-test
-------------------------------------------
My usage pattern does not follow most typical use cases (e.g. with known number of DBs - Spring + JPA + multiple persistence units: Injecting EntityManager).
Can anybody give me advice on how to solve this issue?
Is there anybody who has ever hit this situation that the DBs are not known in design time?
Thank you.
I finally got it work with:
Enable tomcat JNDI and create the datasource JNDI to each DS programmatically
Add transaction stuff
com.atomikos:transactions-eclipselink:3.9.3 (my project uses eclipselink instead of hibernate)
org.springframework.boot:spring-boot-starter-jta-atomikos
org.springframework:spring-tx
You have pretty much answered the question yourself: "When deployed in a EJB container (e.g. WebLogic), with proper TransactionAttribute (e.g. TransactionAttributeType.REQUIRED), the container will take care of the transaction start/end/rollback".
WebLogic is compliant with the Java Enterprise Edition specification which is probably why it worked before, but now you are using Tomcat (in embedded mode) which are NOT.
So you simply cannot do what you are trying to do.
This statement in your persistence.xml file:
<persistence-unit name="PU1" transaction-type="JTA">
requires an Enterprise Server (WebLogic, Glassfish, JBoss etc.)
With Tomcat you can only do this:
<persistence-unit name="PU1" transaction-type="RESOURCE_LOCAL">
And you need to handle transactions by your self:
myEntityManager.getTransaction.begin();
... //Do your transaction stuff
myEntityManager.getTransaction().commit();

ModuleClassLoader singleton

I have this situation:
a JBOSS instance
application client.war
application server.war
a jboss module, properly installed, containing only the interfaces.
The server.war application implements the jboss module interfaces, and publishes these implementations with a JNDI bind. The client.war application with a lookup uses implementations server.war.
A runtime client.war can call the implementation exposed by server.war, but as soon as I try to start a transaction hibernate I get the following error:
ERROR [stderr] java.lang.IllegalStateException: JBAS016071: Singleton
not set for ModuleClassLoader for Module "client.war:main" from
Service Module Loader. This means that you are trying to access a weld
deployment with a Thread Context ClassLoader that is not associated
with the deployment.
There I bumped my head for days, but I can not understand what the problem is. Someone can help me?
Set the class loader on the child thread to be the same as the parent.
Get parent class loader:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Set child class loader :
ClassLoader cl = Thread.currentThread().setContextClassLoader(cl);
When the child thread is done, make sure to unset the class loader to null, to avoid leaks in case of thread pools.
Although CDI will work in the child thread other things such as remote EJB invocation and JNDI lookups will not.
A much better approch wuld be to use an async EJB invocations You can just create an EJB that looks something like:
#Singleton
public class AsyncBean {
#Asynchronous
public void performTask(int a, int b) {
// the client doesn't care what happens here
}
This would mean that your async task will have the TCCL set correctly, JNDI will work etc (basically it is a full EE invocation).
You can configure the thread pool used for async invocations in standalone.xml, but it will be used for all #Asynchronous methods in the application.
Root Cause
When an application launches its own threads, the new threads use a classloader which is different than the classloader of the originating thread, therefore injection is failing.
Reference
https://access.redhat.com/solutions/257663

#EJB annotation vs JNDI lookup + transaction

Acording to another post [1] there's no difference between invoking a session EJB via JNDI lookup and using the #EJB annotation. However, in the following scenario:
1.- call session EJB1(JDBC inserts here)
2.- From EJB1, call session EJB2 (more inserts here)
3.- Rollback the transaction (from EJB1)
If I use the #EJB annotation it works fine, but with the JNDI lookup it doesn´t, the transaction in the second EJB is a new one and the rollback doesn´t happen. All this with CMT.
I'm deploying all this stuff in a Geronimo/ibmwasce-2.1.1.6.
¿Do I need to pass the transaction from one EJB to another explicitly? I thought it was the continer job. ¿Any clues?
[1] #EJB annotation vs JNDI lookup
Update:
Code via annotation:
#EJB
private CodAppEjb codAppejbAnotacion;
Code via jndi:
CodAppEjb codAppejb;
InitialContext ctx;
Properties properties= new Properties();
properties.setProperty("java.naming.provider.url", "ejbd://127.0.0.1:4201");
properties.setProperty("java.naming.factory.initial", "org.apache.openejb.client.RemoteInitialContextFactory");
ctx = new InitialContext(properties);
codAppejb= (CodAppEjb) ctx.lookup("CodAppEjbBeanRemote");
The transaction code is just the same.
It seems, you have a transaction propagation problem.
The problem seems to be, that in your JNDI lookup you search for the remote EJB (not Local), which does NOT get executed in the same transaction context as EJB1.
When using the #EJB annotation above, the local implementation is injected, with the same transaction context.

Connect to a running JBoss AS7 instance for test purposes

I already have a integration-test phase, when I ran the selenium tests. I also want to run some unit tests in this phase, because the app is too much complex and have a lot of dependencies between his modules (a hell), so, after a week fighting against OpenEJB and Arquillian, I believe that this would be easier.
The thing is: how do I made it work?
I have the instance already running, if I instantiate an InitialContext and try to lookup some bean, I got an exception telling me that I have not set the java.naming.initial.factory, and I don't know what to put in there.
I'm also complaining about the annotated beans.
Suppose a Bean like this:
#Stateless
public class ABeanImpl implements ABean {
#EJB
private BBean;
}
Will the container automatically get right the BBean?
Thanks in advance
How to connect to JBoss 7.1 remote JNDI:
Here is the code snippet that I use for JBoss 7.1:
Properties props = new Properties();
String JBOSS_CONTEXT = "org.jboss.naming.remote.client.InitialContextFactory";
props.put("jboss.naming.client.ejb.context", true);
props.put(Context.INITIAL_CONTEXT_FACTORY, JBOSS_CONTEXT);
props.put(Context.PROVIDER_URL, "remote://localhost:4447");
props.put(Context.SECURITY_PRINCIPAL, "jboss");
props.put(Context.SECURITY_CREDENTIALS, "jboss123");
InitialContext ctx = new InitialContext(props);
Resolution of ambiguous ejb references:
According to JBoss EJB 3 reference, if at any level of your EJB environment (EJB/EAR/Server) are duplicates in used interfaces, exception will be thrown during resolution of injected beans.
Based on above, if you have got a reference to EJB bean which interface:
has two implementations in your EJB module (JAR/WAR) - exception will be thrown
has two implementations in your application (other EJB JAR's in same EAR) - exception will be thrown
has two implementations, one in module with bean ABeanImpl, second somewhere else - implemetation from current module is used.