How can I read items from multiples databases? I already know that is possible from files.
the following example works for read from multiples files
...
I suggest a tricky way. If we assume that one is your mysql datasource's table is base and every row in that table corresponds other mysql datasource table's row(like a join tables which are in different datasources), you could do it in your batch job itemreader. Ex of this way;
Spring DataSource Configuration;
<bean id="mySqlDataSource1" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${database1.driverClassName}"/>
<property name="url" value="${database1.url}"/>
<property name="username" value="${database1.username}"/>
<property name="password" value="${database1.password}"/>
<property name="validationQuery" value="${database1.validationQuery}"/>
</bean>
<bean id="mySqlDataSource2" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${database2.driverClassName}"/>
<property name="url" value="${database2.url}"/>
<property name="username" value="${database2.username}"/>
<property name="password" value="${database2.password}"/>
<property name="validationQuery" value="${database2.validationQuery}"/>
</bean>
Your batch-job.xml
<bean id="multiDatasorceReader" class="org.springframework.batch.item.database.JdbcCursorItemReader" scope="step">
<property name="dataSource" ref="mySqlDataSource1" />
<property name="rowMapper" ref="multiDatasourceRowMapper" />
<property name="sql">
<value>
SELECT * FROM xyz
</value>
</property>
</bean>
<bean id="multiDatasourceRowMapper" class="yourpackage.MultiDatasourceRowMapper" scope="step">
<property name="secondDataSource" ref="mySqlDataSource2" />
<property name="secondSql">
<value>
SELECT * FROM abc
</value>
</property>
</bean>
Your RowMapper looks like;
public class MultiDatasourceRowMapper implements RowMapper<String> {
private DataSource secondDataSource;
private String secondSql;
public String mapRow(ResultSet rs, int arg1) throws SQLException {
Connection conn = secondDataSource.getConnection();
PreparedStatement prep = conn.prepareStatement(secondSql);
// Do Something
return "";
}
public void setSecondDataSource(DataSource secondDataSource) {
this.secondDataSource = secondDataSource;
}
public void setSecondSql(String secondSql) {
this.secondSql = secondSql;
}
}
I suggest a simple workaround that may not be suitable to all cases, but will be useful in many:
Simply define:
The 2 steps are nearly identical, they reference the same processor and writer, but they have different readers. They will be called consecutively.
Whether this setup works will depend on the processor and writer (whether they still work correctly when called in different steps). In my case, it was sufficient to set appendAllowed=true
to the writer, such that both steps can write to the same file.
There isn't a ready-to-use component that perform what you ask; the only solution is to write a custom ItemReader<>
that delegates to JdbcCursorItemReader
(or to HibernateCursorItemReader
or to any generic ItemReader
implementation).
You need to prepare all necessary stuff (datasource, session, real database readers) and bind all delegated readers to your custom reader.
EDIT:
You need to simulate a loop using recusion of ItemReader.read()
and mantain reader and delegates state across job restarts.
class MyItemReader<T> implements ItemReader<T>, ItemStream {
private ItemReader[] delegates;
private int delegateIndex;
private ItemReader<T> currentDelegate;
private ExecutionContext stepExecutionContext;
public void setDelegates(ItemReader[] delegates) {
this.delegates = delegates;
}
@BeforeStep
private void beforeStep(StepExecution stepExecution) {
this.stepExecutionContext = stepExecution.getExecutionContext();
}
public T read() {
T item = null;
if(null != currentDelegate) {
item = currentDelegate.read();
if(null == item) {
((ItemStream)this.currentDelegate).close();
this.currentDelegate = null;
}
}
// Move to next delegate if previous was exhausted!
if(null == item && this.delegateIndex< this.delegates.length) {
this.currentDelegate = this.delegates[this.currentIndex++];
((ItemStream)this.currentDelegate).open(this.stepExecutionContext);
update(this.stepExecutionContext);
// Recurse to read() to simulate loop through delegates
item = read();
}
return item;
}
public void open(ExecutionContext ctx) {
// During open restore last active reader and restore its state
if(ctx.containsKey("index")) {
this.delegateIndex = ctx.getInt("index");
this.currentDelegate = this.delegates[this.delegateIndex];
((ItemStream)this.currentDelegate ).open(ctx);
}
}
public void update(ExecutionContext ctx) {
// Update current delegate index and state
ctx.putInt("index", this.delegateIndex);
if(null != this.currentDelegate) {
((ItemStream)this.currentDelegate).update(ctx);
}
}
public void close(ExecutionContext ctx) {
if(null != this.currentDelegate) {
((ItemStream)this.currentDelegate).close();
}
}
<bean id="myItemReader" class=path.to.MyItemReader>
<property name="delegates">
<array>
<ref bean="itemReader1"/>
<ref bean="itemReader2"/>
<ref bean="itemReader3"/>
</array>
</property>
</bean>
EDIT2: Remember to set property name; this is NECESSARY to let MyItemReader.read() works correctly
<bean id="itemReader1" class="JdbcCursorItemReader">
<property name="name" value="itemReader1" />
<!-- Set other properties -->
</bean>