spring-integration jdbc outbound-gateway advice for handling empty result-sets - spring

I'm taking #gary-russel's suggestion and opening a new question w.r.t this older question ( Trouble using jdbc:outbound-gateway when query returns empty result set ) about spring-integration JDBC outbound-gateway calls on requests that return an empty result-set.
I've tried to use handler advice to get the request to return an empty array rather than throwing an exception.
Could you advise why this advice is not right?
<beans:beans xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans = "http://www.springframework.org/schema/beans"
xmlns:jdbc = "http://www.springframework.org/schema/jdbc"
xmlns:int = "http://www.springframework.org/schema/integration"
xmlns:int-jdbc = "http://www.springframework.org/schema/integration/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jdbc https://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
<int:gateway id="getAllCustomers-Gateway"
default-request-channel="getAllCustomers"
service-interface="demo.StringInputJsonOutputGatewayMethod" />
<int:channel id="getAllCustomers" />
<int-jdbc:outbound-gateway id="getAllCustomers-OutboundGateway"
request-channel="getAllCustomers"
query="select * from Customer"
data-source="dataSource"
max-rows="0" >
<int-jdbc:request-handler-advice-chain>
<beans:bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice" >
<beans:property name="onSuccessExpressionString" value="payload ?: {} " />
<beans:property name="returnFailureExpressionResult" value="#{true}" />
<beans:property name="onFailureExpressionString" value="{}" />
</beans:bean>
</int-jdbc:request-handler-advice-chain>
</int-jdbc:outbound-gateway>
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource" >
<beans:property name="driverClass" value="org.h2.Driver" />
<beans:property name="url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE" />
<beans:property name="username" value="sa" />
<beans:property name="password" value="" />
</beans:bean>
<jdbc:initialize-database data-source="dataSource" >
<jdbc:script location="classpath:/schema.sql" />
</jdbc:initialize-database>
</beans:beans>
The test database is initialized with this script (schema.sql:
CREATE TABLE Customer (
ID BIGINT NOT NULL AUTO_INCREMENT,
FIRST_NAME VARCHAR(30) NOT NULL,
LAST_NAME VARCHAR(30) NOT NULL,
PRIMARY KEY (ID)
);
The outbound-gateway is throwing an exception:
org.springframework.integration.handler.ReplyRequiredException: No reply produced by handler 'getAllCustomers-OutboundGateway', and its 'requiresReply' property is set to true.
Any suggestions or pointers appreciated.
Some debugging followup:
I can see that the ExpressionEvaluatingRequestHandlerAdvice is called to evaluate the successExpression but that this does not alter return result, even when a success expression is provided, something like payload ?: {}. The failureExpression is not consulted because the null result failure exception from the JdbcOutboundGateway is thrown after AdviceChain is run.
The most surprising part of this JdbcOutboundGateway problem is that the method handleRequestMessage() does receive an empty list from the JDBC call, which seems perfectly valid, but it then goes on to explicitly set this to null.
if (this.poller != null) {
...
list = this.poller.doPoll(sqlQueryParameterSource);
}
Object payload = list;
if (list.isEmpty()) {
return null;
}

I guess your point is to return an empty list as is, not null as it is there by default in the JdbcOutboundGateway.
The null is valid a result from the Joinpoint execution in the AOP Advice.
The logic in that ExpressionEvaluatingRequestHandlerAdvice is like this:
try {
Object result = callback.execute();
if (this.onSuccessExpression != null) {
evaluateSuccessExpression(message);
}
return result;
}
Since null is OK return, yo just go to the evaluateSuccessExpression() without expecting its result at all. So, in the end we just return that null.
This null is consulted in the AbstractReplyProducingMessageHandler:
if (result != null) {
sendOutputs(result, message);
}
else if (this.requiresReply && !isAsync()) {
throw new ReplyRequiredException(message, "No reply produced by handler '" +
getComponentName() + "', and its 'requiresReply' property is set to true.");
}
You may really consider to set that requiresReply to false to ignore empty lists from query execution. We may revise our "empty list" logic though, but for now it is converted directly to null: https://jira.spring.io/browse/INT-3333.
You may consider to implement a a custom AbstractRequestHandlerAdvice and check for the callback.execute() result and return an empty list as you might expect.
It is also possible with the mentioned ExpressionEvaluatingRequestHandlerAdvice, but it is a bit involved with other options and exceptions throwing from the onSuccessExpression.

Thanks to #artem-bilan's suggestions. This seems to do the trick in the end.
Add a custom advice handler extension to ExpressionEvaluatingRequestHandlerAdvice:
package demo;
import org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice;
import org.springframework.messaging.Message;
import java.util.ArrayList;
import java.util.List;
import static java.util.Collections.unmodifiableList;
public class ReplaceNullWithEmptyListHandlerAdvice extends ExpressionEvaluatingRequestHandlerAdvice {
private static final List<Object> EMPTY_LIST = unmodifiableList(new ArrayList<>());
#Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
final Object result = super.doInvoke(callback, target, message);
return result != null ? result : EMPTY_LIST;
}
}
Now can set the advicechain like this:
<int-jdbc:outbound-gateway id="getAllCustomers-OutboundGateway"
request-channel="getAllCustomers"
query="select * from Customer"
data-source="dataSource"
max-rows="0" >
<int-jdbc:request-handler-advice-chain>
<beans:bean class="demo.ReplaceNullWithEmptyListHandlerAdvice" />
</int-jdbc:request-handler-advice-chain>
</int-jdbc:outbound-gateway>

Related

Spring transaction closes connection once commit for Propagation type REQUIRED_NEW

In my application i am processing messages from queue using camel and process it in multiple threads.
I tried to persist the data to a table during the process with PlatformTransactionManager, with Propagation type "REQUIRED_NEW", but on using the commit the transaction seems to be closed. and connection not available for other process.
The application context.xml looks as in below snippet.
<!-- other definitions -->
<context:property-placeholder location="classpath:app.properties"/>
<bean id="appDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
<property name="url" value="${dburl}"/>
<property name="username" value="${dbUserName}"/>
<property name="password" value="${dbPassword}"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="appDataSource" />
</bean>
<!-- Other bean reference. -->
<bean id="itemDao" class="app.item.dao.ItemDao">
<property name="dataSource" ref="appDataSource"/>
</bean>
<bean id="orderProcess" class="app.order.process.OrderProcess" scope="prototype">
<property name="itemDao" ref="itemDao"/>
</bean>
I have a DAO classes something like below, also there are other Dao's.
public class ItemDao{
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private PlatformTransactionManager transactionManager;
private TransactionStatus transactionStatus;
//Setter injection of datasource
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
this.transactionManager = new DataSourceTransactionManager(dataSource);
}
//setterInjection
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void createAndStartTransaction()
{
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
transDef.setPropagationBehavior(Propagation.REQUIRES_NEW.ordinal());
if (transactionManager != null)
{
transactionStatus = transactionManager.getTransaction(transDef);
} // if transactionManager null log something went incorrect
}
public void commit() throws Exception
{
if (transactionManager != null && transactionStatus != null)
{
transactionManager.commit(transactionStatus);
}
}
public void rollBack() throws Exception
{
if (transactionManager != null && transactionStatus != null)
{
transactionManager.rollback(transactionStatus);
}
}
}
Finally in the code flow, once the context is defined and using those beans process the message.
Parse the message from a queue
validate the message, check if the metadata information in database, insert the data to the database.
I am trying to persist the data to database immediately at this time
After that the flow will be processing further.
The challange is that when we tried to use the
Below is what I did to persist the data to database. Refer the code snippet.
But this is working when i perform a a testing with single instance.
//....
//.. fetch info from data base using other dao's
//.. insert into another table
// Below code i added where i need to persist the data to database
try{
orderProcess.itemDao.createAndStartTransaction();
orderProcess.itemDao.
}catch(Exception exe){
orderProcess.itemDao.rollBack();
}finally{
//within try catch
orderProcess.commit();
}
//.. other dao's used to fetch the data from different table database
//.. still the process is not completed
When the process try to fetch the next message from queue, it was not able to get the connection and throws connection null exception.
What is observed is the process closes the connection abruptly, so when the process picks the next message it is not having connection defined.
SQL state [null]; error code [0]; Connection is null.; nested exception is java.sql.SQLException: Connection is null.
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84)
Any idea how to persist the transaction independently during the process.
The design is not maintainable, but was able to modify the code for my requirement. Didn't notice any side effect
The DAO call was done from different layer.
I extracted the insert/update/delete to Specific DAO class.
And created a sperate method to call the insert(), etc. in this DAO.
public void checkAndValidate(Object input){
// check data exsits in DB
boolean exists = readDao.checkForData(input);
if(!exists){
// the method which was annotated with transactional
insertDataToDB(input);
}
//.. other process..
}
#Transactional
public Object insertDataToDB(Object data) throws exception {
try{
writeDao.insertData(data);
} catch(Exception exe)
{
//handle exception
}
}

Spring Batch read step running in loop

I came across a piece of code that reads some data as the following:
public class StudioReader implements ItemReader<List<Studio>> {
#Setter private AreaDao areaDao;
#Getter #Setter private BatchContext context;
private HopsService hopsService = new HopsService();
#Override
public List<Studio> read() throws Exception {
List<Studio> list = hopsService.getStudioHops();
if (!isEmpty(list)) {
for (Studio studio : list) {
log.info("Studio being read: {}", studio.getCode());
List areaList = areaDao.getArea(studio
.getCode());
if (areaList.size() > 0) {
studio.setArea((String) areaList.get(0));
log.info("Area {1} is fetched for studio {2}", areaList.get(0), studio.getCode());
}
this.getContext().setReadCount(1);
}
}
return list;
}
However when I run the job this read is running in a loop. I found from another stackoverflow answer that it is the expected behavior. My question then is what is the best solution given this particular example? Extend StudioReader from JdbcCursorItemReader ? I found one example that defines everything in the xml which I don't want. And here is the context.xml part for the reader:
<bean class="org.springframework.batch.core.scope.StepScope" />
<bean id="ItemReader" class="com.syc.studio.reader.StudioReader" scope="step">
<property name="context" ref="BatchContext" />
<property name="areaDao" ref="AreaDao" />
</bean>
And here is the job definition in xml:
<bean id="StudioJob" class="org.springframework.batch.core.job.SimpleJob">
<property name="steps">
<list>
<bean id="StudioStep" parent="SimpleStep" >
<property name="itemReader" ref="ItemReader"/>
<property name="itemWriter" ref="ItemWriter"/>
<property name="retryableExceptionClasses">
<map>
<entry key="com.syc.studio.exception.CustomException" value="true"/>
</map>
</property>
<property name="retryLimit" value="2" />
</bean>
</list>
</property>
<property name="jobRepository" ref="jobRepository" />
</bean>
Writer:
public void write(List<? extends Object> obj) throws Exception {
List<Studio> list = (List<Studio>) obj.get(0);
for (int i = 0; i <= list.size(); i++) {
Studio studio = list.get(i);
if (apiClient == null) {
apiClient = new APIClient("v2");
}
this.uploadXML(studio);
}
The read method after suggestion from #holi-java:
public List<Studio> read() throws Exception {
if (this.listIterator == null) {
this.listIterator = initializing();
}
return this.listIterator.hasNext() ? this.listIterator.next() : null;
}
private Iterator<List<Studio>> initializing() {
List<Studio> listOfStudiosFromApi = hopsService.getStudioLocations();
for (Studio studio : listOfStudiosFromApi) {
log.info("Studio being read: {}", studio.getCode());
List areaList = areaDao.getArea(studio.getCode());
if (areaList.size() > 0) {
studio.setArea((String) areaList.get(0));
log.info("Area {1} is fetched for studio {2}", areaList.get(0), studio.getCode());
}
this.getContext().setReadCount(1);
}
return Collections.singletonList(listOfStudiosFromApi).iterator();
}
spring-batch documentation for ItemReader.read assert:
Implementations must return null at the end of the input data set.
But your read method is always return a List and should be like this:
public Studio read() throws Exception {
if (this.results == null) {
List<Studio> list = hopsService.getStudioHops();
...
this.results=list.iterator();
}
return this.results.hasNext() ? this.results.next() : null;
}
if you want your read method return a List then you must paging the results like this:
public List<Studio> read() throws Exception {
List<Studio> results=hopsService.getStudioHops(this.page++);
...
return results.isEmpty()?null:results;
}
if you can't paging the results from Service you can solved like this:
public List<Studio> read() throws Exception {
if(this.results==null){
this.results = Collections.singletonList(hopsService.getStudioHops()).iterator();
}
return this.results.hasNext()?this.results.next():null;
}
it's better not read a list of items List<Studio>, read an item at a time Studio instead. when you read a list of item you possibly duplicated iterate logic between writers and processors as you have shown the demo in comments. if you have a huge of data list to processing you can combine pagination in your reader, for example:
public Studio read() throws Exception {
if (this.results == null || !this.results.hasNext()) {
List<Studio> list = hopsService.getStudioHops(this.page++);
...
this.results=list.iterator();
}
return this.results.hasNext() ? this.results.next() : null;
}
Maybe you need to see step processing mechanism.
ItemReader - read an item at a time.
ItemProcessor - processing an item at a time.
ItemWriter - write entire chunk of items out.

Reading multiple files resides in a file system which matches the job parameters using MultiResourceItemReader

Use Case :
I would like to launch a job which takes employee id as job parameters, which will be multiple employee ids.
In a file system, files will be residing which contains employee ids as part of the file name (It is a remote file system, not local)
i need to process those files where file name contains the employee-id and passing it to the reader.
I am thinking of using MultiResourceItemReader but i am confused how to match the file name with Employee Id (Job Parameter) which is there in a file system.
Please suggest.
The class MultiResourceItemReader has a method setResources(Resources[] resources) which lets you specify resources to read either with an explicit list or with a wildcard expression (or both).
Example (explicit list) :
<bean class="org.springframework.batch.item.file.MultiResourceItemReader">
<property name="resources">
<list>
<value>file:C:/myFiles/employee-1.csv</value>
<value>file:C:/myFiles/employee-2.csv</value>
</list>
</property>
</bean>
Example (wildcard) :
<bean class="org.springframework.batch.item.file.MultiResourceItemReader">
<property name="resources" value="file:C:/myFiles/employee-*.csv" />
</bean>
As you may know, you can use job parameters in configuration by using #{jobParameters['key']} :
<bean class="org.springframework.batch.item.file.MultiResourceItemReader">
<property name="resources" value="file:C:/myFiles/employee-#{jobParameters['id']}.csv" />
</bean>
Unfortunately, wildcard expressions can't manage an OR expression over a list of value with a separator (id1, id2, id3...). And I'm guessing you don't know how many distinct values you'll have to declare an explicit list with a predefined number of variables.
However a working solution would be to use the Loop mechanism of Spring Batch with a classic FlatFileItemReader. The principle is basically to set the next="" on the last step to the first step until you have exhausted every item to read. I will provide code samples if needed.
EDIT
Let's say you have a single chunk to read one file at a time. First of all, you'd need to put the current id from the job parameter in the context to pass it to the reader.
public class ParametersManagerTasklet implements Tasklet, StepExecutionListener {
private Integer count = 0;
private Boolean repeat = true;
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
// Get job parameter and split
String[] ids = chunkContext.getStepContext().getJobParameters().getString(PARAMETER_KEY).split(DELIMITER);
// Check for end of list
if (count >= ids.length) {
// Stop loop
repeat = false;
} else {
// Save current id and increment counter
chunkContext.getStepContext().getJobExecutionContext().put(CURRENT_ID_KEY, ids[count++];
}
}
#Override
public ExitStatus afterStep(StepExecution stepExecution) {
if (!repeat) {
return new ExitStatus("FINISHED");
} else {
return new ExitStatus("CONTINUE");
}
}
}
Now you declare this step in your XML and create a loop :
<batch:step id="ParametersStep">
<batch:tasklet>
<bean class="xx.xx.xx.ParametersManagerTasklet" />
</batch:tasklet>
<batch:next on="CONTINUE" to="ReadStep" />
<batch:end on="FINISHED" />
</batch:step>
<batch:step id="ReadStep">
<batch:tasklet>
<batch:chunk commit-interval="10">
<batch:reader>
<bean class="org.springframework.batch.item.file.MultiResourceItemReader">
<property name="resources" value="file:C:/myFiles/employee-#{jobExecutionContext[CURRENT_ID_KEY]}.csv" />
</bean>
</batch:reader>
<batch:writer>
</batch:writer>
</batch:chunk>
</batch:tasklet>
<batch:next on="*" to="ParametersStep" />
</batch:step>
You can write your own FactoryBean to perform a custom resources search.
public class ResourcesFactoryBean extends AbstractFactoryBean<Resource[]> {
String[] ids;
String path;
public void setIds(String[] ids) {
this.ids = ids;
}
public void setPath(String path) {
this.path = path;
}
#Override
protected Resource[] createInstance() throws Exception {
final List<Resource> l = new ArrayList<Resource>();
final PathMatchingResourcePatternResolver x = new PathMatchingResourcePatternResolver();
for(final String id : ids)
{
final String p = String.format(path, id);
l.addAll(Arrays.asList(x.getResources(p)));
}
return l.toArray(new Resource[l.size()]);
}
#Override
public Class<?> getObjectType() {
return Resource[].class;
}
}
---
<bean id="reader" class="org.springframework.batch.item.file.MultiResourceItemReader" scope="step">
<property name="delegate" ref="itemReader" />
<property name="resources">
<bean class="ResourcesFactoryBean">
<property name="path"><value>file:C:/myFiles/employee-%s.cvs</value> </property>
<property name="ids">
<value>#{jobParameters['id']}</value>
</property>
</bean>
</property>
</bean>
jobParameter 'id' is a comma separated list of your ID.

MyBatis select statement returns null values

I'm trying to run a simple MyBatis example, selecting all rows from the "trains" table.
The problem is that the query performs, but it returns a list with the correct number of elements, but populated with null values.
The same query runned directly with JDBC PreparedStatement works fine.
Perhaps it's a configuration problem, but I cannot figure out what I'm doing wrong.
Here is the code. Thanks in advance.
Train.java
package org.example.mybatis.domain;
public class Train implements Serializable
{
private int id;
private String type;
// getters and setters
}
TrainMapper.java
package org.example.mybatis.persistence;
public interface TrainMapper {
List<Train> getAllTrains();
}
TrainSelector.java
package org.example.mybatis.test;
public class TrainSelector implements TrainMapper {
private static String resource = "mybatis-config.xml";
private static SqlSessionFactory factory = null;
private SqlSessionFactory getSqlSessionFactory()
{
if (factory == null)
{
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
return factory;
}
#Override
public List<Train> getAllTrains()
{
List<Train> trains = null;
SqlSession session = getSqlSessionFactory().openSession();
try {
TrainMapper mapper = session.getMapper(TrainMapper.class);
trains = mapper.getAllTrains();
} finally {
session.close();
}
return trains;
}
public static void main(String[] args) {
List<Train> trains = null;
TrainSelector trainSelector = new TrainSelector();
trains = trainSelector.getAllTrains();
System.out.println(trains);
}
}
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="database.properties" />
<typeAliases>
<typeAlias alias="Train" type="org.example.mybatis.domain.Train" />
<!--package name="org.example.mybatis.domain" />-->
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${database.driver}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/example/mybatis/persistence/TrainMapper.xml" />
</mappers>
</configuration>
TrainMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mybatis.persistence.TrainMapper">
<cache />
<select id="getAllTrains" parameterType="list" resultType="Train">
SELECT *
FROM trains
</select>
</mapper>
JdbcStatementExample.java
package org.example.mybatis.test;
public class JdbcStatementExample {
private static void selectAllTrains() throws SQLException
{
String sql = "SELECT * FROM trains";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String url = "jdbc:mysql://localhost/testing";
String user = "test";
String password = "test";
try {
conn = DriverManager.getConnection(url, user, password);
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
String id = rs.getString("train_id");
String type = rs.getString("train_type");
System.out.println("id: " + id);
System.out.println("type: " + type);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
}
}
public static void main(String[] args)
{
try {
selectAllTrains();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
The names of the columns in the result set are different from the names of the properties in the Train object. You need an explicit result map to let Mybatis know which column is to be mapped to which property.
<resultMap id="trainMap" type="Train>
<id property="id" column="train_id" javaType="java.lang.Integer" jdbcType="INTEGER"/>
<result property="type" column="train_type" javaType="java.lang.String" jdbcType="VARCHAR"/>
</resultMap>
Making your select element into
<select id="getAllTrains" parameterType="list" resultType="trainMap">
SELECT * FROM trains
</select>
Other option is to use column names an aliases.
The column names will be your database's and the aliases will be set to match with your Train object properties:
<select id="getAllTrains" parameterType="list" resultType="trainMap">
SELECT
train_id as id,
train_type as type
FROM trains
</select>
I had the same problem, but only for fields with multiple words. Of course my naming convention in SQL was user_id and in java was userId. This piece of config inside my mybatis-config.xml file saved the day:
<settings>
<setting name="mapUnderscoreToCamelCase" value="false"/>
</settings>
or for properties file:
mybatis.configuration.map-underscore-to-camel-case=true
credit: https://chois9105.github.io/spring/2017/12/31/configuring-mybatis-underscore-to-camel-case.html
Results can be mapped as described by Seeta or in the official docs here:
https://mybatis.org/mybatis-3/sqlmap-xml.html
In MyBatis 3.x the example doesn't work as you need to set resultMap rather than resultType. And you must not set both at the same time! Working example looks like:
<select id="getAllTrains" parameterType="list" resultMap="trainMap">
SELECT * FROM trains
</select>
if you are using spring boot, you can change the map-underscore-to-camel-case property as true like below. because most if the time we use _ (user_id) when create the table attributes. but in java we use camelCase (userId) for the variables. then mybatis don't know about that and when it tries to mapping, the error is thrown.
mybatis.configuration.map-underscore-to-camel-case=true

Spring init-method params

I am new to spring and I wanted to ask whether or not it is possible to pass params to the init and destroy methods of a bean.
Thanks.
No, you can't. If you need parameters, you will have to inject them as fields beforehand.
Sample Bean
public class Foo{
#Autowired
private Bar bar;
public void init(){
bar.doSomething();
}
}
Sample XML:
<bean class="Foo" init-method="init" />
This method is especially useful when you cannot change the class you are trying to create like in the previous answer but you are rather working with an API and must use the provided bean as it is.
You could always create a class (MyObjectFactory) that implements FactoryBean and inside the getObject() method you should write :
#Autowired
private MyReferenceObject myRef;
public Object getObject()
{
MyObject myObj = new MyObject();
myObj.init(myRef);
return myObj;
}
And in the spring context.xml you would have a simple :
<bean id="myObject" class="MyObjectFactory"/>
protected void invokeCustomInitMethod(String beanName, Object bean, String initMethodName)
throws Throwable {
if (logger.isDebugEnabled()) {
logger.debug("Invoking custom init method '" + initMethodName +
"' on bean with beanName '" + beanName + "'");
}
try {
Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName, null);
if (initMethod == null) {
throw new NoSuchMethodException("Couldn't find an init method named '" + initMethodName +
"' on bean with name '" + beanName + "'");
}
if (!Modifier.isPublic(initMethod.getModifiers())) {
initMethod.setAccessible(true);
}
initMethod.invoke(bean, (Object[]) null);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
see spring soruce code in Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName, null);
the init method is find and param is null
You cannot pass params to init-method but you can still achieve the same effect using this way:
<bean id="beanToInitialize" class="com.xyz.Test"/>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="beanToInitialize" />
<property name="targetMethod" value="init"/> <!-- you can use any name -->
<property name="arguments" ref="parameter" /> <!-- reference to init parameter, can be value as well -->
</bean>
Note: you can also pass multiple arguments as a list using this
<property name="arguments">
<list>
<ref local="param1" />
<ref local="param2" />
</list>
</property>

Resources