System variable to the update query of int-jdbc:inbound-channel-adapter - spring

I am using a int-jdbc:inbound-channel-adapter.
I am facing issue with my update statement.I need pass single value ,since I have hard coded that value in RowMapper ,that's why I am receiving 5 values in place of 1.(max-rows-per-poll=5)
My Requirement :- I need to pass a system variable to the node_id column and 5 ids select from select query to in condition
<int-jdbc:inbound-channel-adapter id="itemsInboundJdbcChannelAdapter"
auto-startup="true" data-source="dataSource" channel="ItemsInboundJdbcChannel"
query="SELECT QXXXX_ID,BXXXX_ID,TXXXX_ID,RXXXX_ID,EXXXXID,NODE_ID FROM XXXX_QXXXX WHERE XXXX_STATUS = :identier ORDER BY QXXXX_ID asc FOR UPDATE SKIP LOCKED"
update="UPDATE XXXX_QXXXX SET XXXX_STATUS ='IT_PROCESSED',NODE_ID=(:NODE_ID),UPDATE_BY='BISWO',UPDATED_ON=SYSDATE WHERE QXXXX_ID IN (:QXXXX_ID)"
row-mapper="xxDataRowMapper" max-rows-per-poll="${item.transfer.jdbc.max.rows}"
select-sql-parameter-source="myItemsSelectSqlParameterSource"
update-sql-parameter-source-factory="myUpdateParameterSource">
<int:poller id="jdbcPoller" fixed-delay="${item.transfer.poller.jdbc.fixed.delay}" task-executor="notificationExecutor" error-channel="chainToFailedOut">
<int:transactional transaction-manager="transactionManager" />
</int:poller>
</int-jdbc:inbound-channel-adapter>
<int:service-activator input-channel="ItemsInboundJdbcChannel"
ref="getMyService" >
<int:poller fixed-delay="${item.transfer.poller.jdbc.fixed.delay}" />
</int:service-activator>
<bean id="myUpdateParameterSource"
class="org.springframework.integration.jdbc.ExpressionEvaluatingSqlParameterSourceFactory">
<property name="parameterExpressions">
<map>
<entry key="NODE_ID" value=" #this['nodeId']"/>
<entry key="QXXXX_ID" value="#this['qxxxxId']" />
</map>
</property>
</bean>
xxxDataRowMapper.java
#Override
public XXXDataModel mapRow(ResultSet rs, int rowNum) throws SQLException {
XXDataModel object=null;
if(rs!=null){
long queueId=rs.getLong("QXXXX_ID");
long batchId=rs.getLong("BXXXX_ID");
long tradeRunId=rs.getLong("TXXXX_ID");
long riskRunId=rs.getLong("RXXXX_RUN_ID");
long eventId=rs.getLong("EXXXXID");
object=new XXDataModel(queueId,batchId,tradeRunId,riskRunId,eventId,this.nodeId);
}
return object;
}

I think you simply can go the same properties placeholder way and do something like this:
update="UPDATE XXXX_QXXXX SET XXXX_STATUS ='IT_PROCESSED',NODE_ID='${node_id.from-system.properties}', UPDATE_BY='BISWO',UPDATED_ON=SYSDATE WHERE QXXXX_ID IN (:QXXXX_ID)"
Spring will understand your PP pattern, resolves it against an Environment and the final value will be present in the target SQL statement to use.

Related

Spring Batch | Does JdbcPagingItemReader<T> supports multiple DataSource to support join query across multiple datasource?

I do have SQL query reader for ItemReader with only single .
But my query exception is to join tables from two different datasource and provide single output result.. something like below
<bean id="userFinder"
class="org.springframework.batch.item.database.JdbcPagingItemReader"
scope="step">
...
...
class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
<property name="dataSource1" ref="dataSource1" />
<property name="dataSource2" ref="dataSource2" />
<property name="selectClause"
value="SELECT ALIAS1.COL1, ALIAS2.COL2 " />
<property name="fromClause" value="FROM dataSource1.TABLE1.ALIAS1, dataSource2.TABLE 2.ALIAS2" />
</property>
<property name="sortKey" value="WLT_ID" />
</bean>
</property>
in single query you will be not able to join two different database. I think in java you will not able to use two datasource to run one query.
You can write your custom ItemReader and inject both datasource.
retrieve data from both table and do join in java code. but this will be very expensive operation

Spring - how to reference another property within the same bean?

I have the following definition in my configuration:
<bean class="com.project.TimerBean">
<property name="delay" value="30000" />
<property name="interval" value="60000" />
<property name="invokeThis" value="com.project.TargetClass" />
<property name="receiver" value="XYZ" />
<property name="args" value="#{interval}" />
</bean>
I would like to set the value of args to the same value as interval (in this case, 60000) without having to hard-code the value. However, the above snippet doesn't seem to work. How should I change this?
# syntax (Spel Expressions) are supposed to work the way you wrote it. You need to replace
#{interval} to #{beanId.interval}.
For example, if the id of the bean you are creating is timerBean, #{timerBean.interval} is supposed to work. You cannot refer to a property directly even if it is a part of the bean definition.
It only works if the property you are referring to is a part of another bean.
<bean id="beanA" class="org.BeanA">
<property name="prop1" value="1000" />
</bean>
<bean id="beanB" class="org.BeanB">
<property name="prop2" value = "#{beanA.prop1}" />
<property name="prop3" value = "#{beanB.prop2}" />
</bean>
In the above example, prop2 gets initialised from prop1 correctly. But prop3 gets initialised to null.
If you look at AbstractAutowireCapableBeanFactory class and method,
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs)
you can see that all the property values in a single bean definition are looped over and values are parsed. After all the values have been successfully parsed, only then they are set on the bean instance. In the above beanA and beanB example when prop3's value is getting parsed, spring looks for the value of prop2 on beanB which is not yet set and hence returns null.
AFAIK, there is no way around this except the way suggested by #Alex
PS: I am using spring version 4.1.6.RELEASE
Move interval value "60000" to the property file
yourVariableName = 60000
and change to:
<property name="interval" value="${yourVariableName}" />
<property name="args" value="${yourVariableName}" />

Spring Batch - Last item from the reader alone is getting updated

I have to read from a file (FlatFile) and update a column if that ID present in the file matches the id in the column.The file is being read properly but only the last id value is getting updated here . Please find the snippet
Job-Config.xml
<bean id="abcitemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<property name="resource" value="file:datafile/outputs/ibdData.txt" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names" value="ID,NAM,TYPE" />
<property name="delimiter" value="|"/>
</bean>
</property>
<property name="fieldSetMapper">
<bean class="com.pershing.intraware.springbatch.mapper.abcFieldsetMapper" />
</property>
</bean>
</property>
</bean>
<bean id="abcitemWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter" scope="step">
<property name="dataSource" ref="dataSource" />
<property name="sql"><value>UPDATE TEST_abc SET BIZ_ARNG_CD = CASE WHEN ID IN (SELECT ID FROM TEST_abc WHERE ID= ? and MONTH=(to_char(sysdate, 'MM')) AND YR =(to_char(sysdate, 'YY'))) THEN 'Y' ELSE 'N' END</value></property>
<!-- It will take care matching between object property and sql name parameter -->
<property name="itemPreparedStatementSetter" ref="testPrepStatementSetter" />
</bean>
</beans>
Setter.java
public class IDItemPreparedStatementSetter implements ItemPreparedStatementSetter<Test> {
#Override
public void setValues(Test item, PreparedStatement ps) throws SQLException {
// TODO Auto-generated method stub
ps.setString(1, item.getID());
}
}
Your query is updating each row of database every time it is fired. You need to restrict that. Currently; it must be setting the BIZ_ARNG_CD to 'Y' for records with ID equal to the ID of the last record passed to the writer.
You can fix this in 2 ways -
Default the database column to 'N' and don't set it to 'N' in the update statement
Add where clause in update script ( BIZ_ARNG_CD != 'Y')

read only selective columns from csv file using spring batch

there is one csv file having 100 columns, but we need only 3-5 columns which needs to be loaded into database.
I dont want to specify all the 100 columns in linetokenizer in job xml.
Please suggest how we can proceed in this case
Try using a custom fieldSetMapper. You can use it similar to a ResultSet with indexes.
You have to list all the column names only if you want automatic mapping.
Specify only the delimiter, in your case ","
<bean id="flatFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<property name="resource" value="YOURFILE" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="fieldSetMapper">
<bean class="CUSTOMFIELDSETMAPPER" />
</property>
<property name="lineTokenizer">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="delimiter" value="," />
</bean>
</property>
</bean>
</property>
</bean>
The Custom Mapper could be something like this, say if you want to read the 1st column and 25th column:
public class CustomMapper implements FieldSetMapper<CustomPOJO>{
#Override
public CustomPOJO mapFieldSet(FieldSet fieldSet) throws BindException {
CustomPOJO result = new CustomPOJO();
result.setName(fieldSet.readString(0));
result.setAddress(fieldSet.readString(24));
return result;
}
}
For further explanation on how to use the reader, please refer to this tutorial

How to make GridCacheStore-backed get() store its result in the cache

When I use a GridCacheStore-backed cache, the first get() may take a long time, because internally the GridCacheStore will perform calculations / slow search etc.
The second get() on that node will be quick because the result has been cached by that node.
However, the get() on other node is still slow because the result is not being replicated. How to make this replicated? I've set cacheMode=REPLICATED.
My config:
<bean parent="cache-template">
<property name="name" value="yagoEntityByLabel" />
<property name="cacheMode" value="REPLICATED" />
<property name="atomicityMode" value="ATOMIC" />
<property name="distributionMode" value="NEAR_PARTITIONED" />
<property name="backups" value="1" />
<property name="store">
<bean class="id.ac.itb.ee.lskk.lumen.core.yago.YagoEntityByLabelCacheStore" />
</property>
<property name="swapEnabled" value="false" />
<property name="evictionPolicy">
<bean class="org.gridgain.grid.cache.eviction.lru.GridCacheLruEvictionPolicy">
<property name="maxSize" value="10000" />
</bean>
</property>
</bean>
The workaround is to not use GridCacheStore-backed but to use put() instead, but there's a lot of typing and it's not atomic, since the logic will be:
#Nullable value = cache.get(key);
if (value == null) {
value = calculateHeavily(key);
cache.put(key, value);
}
GridGain (as most other data grids) does not replicate on Get operation - it simply loads data into cache. Your workaround using Put operation is good.
To make it atomic (if you need to do so), you can wrap your code into transaction, like so:
try (GridCacheTx tx = cache.txStart()) {
#Nullable value = cache.get(key);
if (value == null) {
value = calculateHeavily(key);
cache.put(key, value);
}
tx.commit();
}

Resources