SimpleJdbcCall executeObject() usage in Spring Framework - spring

I usually do this in my repository package:
public void setDataSource(DataSource dataSource) {
procGetDeviceDetails = new SimpleJdbcCall(dataSource)
.withProcedureName("web_Device_Details")
.returningResultSet("results", BeanPropertyRowMapper.newInstance(Device.class));
}
public Device deviceDetails(Map input) {
Map m = procGetDeviceDetails.execute(input);
List<Device> list = (List<Device>) m.get("results");
if (list.size() > 0) {
return list.get(0);
}
return null;
}
I wish to remove boilerplate in second method so I've switched to executeObject(). I tried this. Both failed.
public Device deviceDetails(Map input) {
Device device = procGetDeviceDetails.executeObject(Device.class, input);
return device;
}
public Device deviceDetails(Map input) {
List<Device> device = procGetDeviceDetails.executeObject(List.class, input);
return device.get(0);
}
Problem is that my device is null. I tried also declaring just Object instead of Device or List and it's still null. What am I doing wrong?
I know that result is correctly mapped into Device class because I see that procGetDeviceDetails (SimpleJdbcCall) has declaredRowParameters equal to {#result-set-1=org.springframework.jdbc.core.BeanPropertyRowMapper#79095fd7} and BeanPropertyRowMapper has mappedClass set to Device.
Seems that generics or something else troubles me. I found example here. https://jira.spring.io/browse/SPR-7270
Thanks!

The problem was with stored procedure design. These methods in SimpleJdbcCall uses OUTPUT parameters while my stored procedures returns data normally as result set.
#Override
#SuppressWarnings("unchecked")
public <T> T executeObject(Class<T> returnType, Map<String, ?> args) {
return (T) doExecute(args).get(getScalarOutParameterName());
}
Because of this I've created subclass of SimpleJdbcCall with two additional methods executeObjectSingle and executeObjectList which enables me simple usage.
public <T> T executeObjectSingle(Class<T> returnType, Map<String, ?> args) {
List<T> o = executeObjectList(returnType, args);
return o.get(0);
}
public <T> List<T> executeObjectList(Class<T> returnType, Map<String, ?> args) {
Map<String, Object> m = doExecute(args);
List<T> o = (List<T>) m.get(parameterNameResultSet);
return o;
}
Of course this should be extended with size and null checking, but it shows the idea.
Stored procedure example in SQL Server:
ALTER PROCEDURE [dbo].[Device_something]
#device varchar(50),
#username varchar(50),
#password varchar(50)
AS
BEGIN
SET NOCOUNT ON;
select 'foo' as token
union
select 'bar' as token
END

Related

Spring Batch Dynamic Insert and Update with DB to DB

There is scenario where we want to load data from DB to DB .
But we want to check if data is already present in target system then update it otherwise insert it into DB.
We are using below approach:
#Bean
ItemWriter<Student> onosItemWriter1() {
JdbcBatchItemWriter<Student> databaseItemWriter = new JdbcBatchItemWriter<>();
databaseItemWriter.setDataSource(dataSource);
databaseItemWriter.setJdbcTemplate(namedParameterJdbcTemplate);
databaseItemWriter.setSql(INSERT_QUERY);
ItemPreparedStatementSetter<Student> valueSetter = new ItemPreparedStatementSetter<Student>() {
#Override
public void setValues(Student student, PreparedStatement statement) throws SQLException {
if (Student.getId() < 0) {
log.info("Inserting!");
databaseItemWriter.setSql(INSERT_QUERY);
statement.setString(1, student.getName());
statement.setString(2, student.getEmail());
} else {
log.info("updateing!!!!");
databaseItemWriter.setSql(UPDATE_QUERY);
statement.setString(1, student.getName());
statement.setString(2, student.getEmail());
}
}
};
databaseItemWriter.setItemPreparedStatementSetter(valueSetter);
return databaseItemWriter;
}
above its working only for insert. How I can do it with one JDBC batch Item writer to update it as well dynamically. if record is already present in chunks.
The code you shared won't work, because you are configuring the query on the item writer based on a runtime information from the item itself (Student.getId() < 0).
we want to check if data is already present in target system then update it otherwise insert it into DB
I would keep it simple and write a custom item writer like follows:
public class StudentItemWriter implements ItemWriter<Student> {
private static final String INSERT_QUERY = "insert into student ...";
private static final String UPDATE_QUERY = "update student set ... where id = ?";
private JdbcTemplate jdbcTemplate;
public StudentItemWriter(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void write(List<? extends Student> students) throws Exception {
for(Student student : students) {
int updated = jdbcTemplate.update(UPDATE_QUERY,...);
if(updated == 0) {
jdbcTemplate.update(INSERT_QUERY,...);
}
}
}
}

Jmockit/Spring mocked dependency still calls the Real dependency

so I've been stuck on this problem all day.
I'm testing a class of type JdbcSupportDao in Spring 3.2. The problem is very self-explanatory if you just read the code, but I will briefly summarize:
I use the #Mocked annotation on a JdbcTemplate to mock querying the database. The problem is, after writing the Expectations block, the actual JdbcTemplate method is still being called, with JMockit apparently not entering in to the equation at all.
The following unit test fails:
/*#RunWith(SpringJUnit4ClassRunner.class)*/
#RunWith(JMockit.class)
#ContextConfiguration(locations={"classpath:studentAggregateReport-servlet.xml", "classpath:applicationContext-hibernate.xml"})
public class JdbcSSODaoTest extends AbstractTransactionalJUnit4SpringContextTests {
#Mocked
JdbcTemplate jdbcTemplate;
List<String> unameList;
SSODao ssoDao;
String DUMMY_ALCID = "yattayattayatta";
#Before
public void constructDao() {
this.ssoDao = new JdbcSSODao();
((JdbcSSODao) ssoDao).setJdbcTemplate(jdbcTemplate);
}
#Test
public void testGetUnameFromAlcId() {
unameList = new ArrayList<String>() {{
add("PEEPEE");
}};
//((JdbcSSODao) ssoDao).setJdbcTemplate(jdbcTemplate);
new Expectations() {{
jdbcTemplate.query(anyString, (ResultSetExtractor<String>)any); result = unameList;
}};
String uname = ssoDao.getUnameFromAlcId(DUMMY_ALCID);
assertNotNull(uname);
}
}
and here is the code for the class being tested:
public class JdbcSSODao extends JdbcDaoSupport implements SSODao {
#Override
public String getUnameFromAlcId(String alcid) {
String sql = SSOSqlUtil.createGetUnameByAlcIdSql(alcid);
logger.debug(sql);
List<String> resultLst = getJdbcTemplate().query(sql, new RowMapper<String>() {
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getString(1);
}
});
if(resultLst.isEmpty()) return null;
return resultLst.get(0);
}
}
Please help :(
Sweet mother of God..
Apparently, you have to cast parameters of mocked methods to the exact type used in the call. This fixed it for me:
new Expectations() {{
jdbcTemplate.query(anyString, (RowMapper<String>)any); result = unameList;
}};

How to convert Oracle user defined Type into java object in spring jdbc stored procedure

I am working on springjdbcTemplate, and all db call will be done through stored procedures. In Oracle 11g I have created one user defined type containing with other type as field inside it as below.
create or replace type WORKER AS Object (NAME VARCHAR2(30),
age NUMBER);
create or replace type WORKER_LIST IS TABLE OF WORKER;
create or replace type MANAGER AS Object(
NAME VARCHAR2(30),
workers WORKER_LIST
);
And at Java side I have created the classes as follows.
public class Worker implements SQLData {
private String name;
private int age;
#Override
public String getSQLTypeName() throws SQLException {
return "WORKER";
}
#Override
public void readSQL(SQLInput stream, String typeName) throws SQLException {
setName(stream.readString());
setAge(stream.readInt());
}
#Override
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeString(getName());
stream.writeInt(getAge());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Manager implements SQLData {
private String name;
private List<Worker> workers;
#Override
public String getSQLTypeName() throws SQLException {
return "Manager";
}
#Override
public void readSQL(SQLInput stream, String typeName) throws SQLException {
setName(stream.readString());
setWorkers((List<Worker>) stream.readObject());
}
#Override
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeString(getName());
stream.writeObject((SQLData) getWorkers());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Worker> getWorkers() {
return workers;
}
public void setWorkers(List<Worker> workers) {
this.workers = workers;
}
}
I have mentioned in typeMap about the mappings.
But I am not getting expected results.
Worker type is returned as Struct and List<Worker> is returned as array.
Please let me know what should I have do and what is the standard protocol to get the expected object as I mentioned above. I'm new to JDBCTemplate. Please suggest.
Thanks
Ram
I think I've managed to get something working.
You mentioned something about the connection's type map. When using Spring it's difficult to get hold of the database connection in order to add the types to the connection's type map, so I'm not sure what you mean when you write 'I have mentioned in typeMap about the mappings'.
Spring offers one way to add an entry to the connection's type map, in the form of the class SqlReturnSqlData. This can be used to call a stored procedure or function which returns a user-defined type. It adds an entry to the connection's type map to specify the database type of the object and the class to map this object to just before it retrieves a value from a CallableStatement. However, this only works if you only need to map a single type. You have two such types that need mapping: MANAGER and WORKER.
Fortunately, it's not difficult to come up with a replacement for SqlReturnSqlData that can add more than one entry to the connection's type map:
import org.springframework.jdbc.core.SqlReturnType;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
public class SqlReturnSqlDataWithAuxiliaryTypes implements SqlReturnType {
private Class<?> targetClass;
private Map<String, Class<?>> auxiliaryTypes;
public SqlReturnSqlDataWithAuxiliaryTypes(Class<?> targetClass, Map<String, Class<?>> auxiliaryTypes) {
this.targetClass = targetClass;
this.auxiliaryTypes = auxiliaryTypes;
}
#Override
public Object getTypeValue(CallableStatement cs, int paramIndex, int sqlType, String typeName) throws SQLException {
Connection con = cs.getConnection();
Map<String, Class<?>> typeMap = con.getTypeMap();
typeMap.put(typeName, this.targetClass);
typeMap.putAll(auxiliaryTypes);
Object o = cs.getObject(paramIndex);
return o;
}
}
The above has been adapted from the source of SqlReturnSqlData. All I've really done is added an extra field auxiliaryTypes, the contents of which gets added into the connection's type map in the call to getTypeValue().
I also needed to adjust the readSQL method of your Manager class. The object you read back from the stream will be an implementation of java.sql.Array. You can't just cast this to a list. Sadly, getting this out is a little fiddly:
#Override
public void readSQL(SQLInput stream, String typeName) throws SQLException {
setName(stream.readString());
Array array = (Array) stream.readObject();
Object[] objects = (Object[]) array.getArray();
List<Worker> workers = Arrays.stream(objects).map(o -> (Worker)o).collect(toList());
setWorkers(workers);
}
(If you're not using Java 8, replace the line with Arrays.stream(...) with a loop.)
To test this I wrote a short stored function to return a MANAGER object:
CREATE OR REPLACE FUNCTION f_get_manager
RETURN manager
AS
BEGIN
RETURN manager('Big Boss Man', worker_list(worker('Bill', 40), worker('Fred', 36)));
END;
/
The code to call this stored function was then as follows:
Map<String, Class<?>> auxiliaryTypes = Collections.singletonMap("WORKER", Worker.class);
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate)
.withSchemaName("my_schema")
.withFunctionName("f_get_manager")
.declareParameters(
new SqlOutParameter(
"return",
OracleTypes.STRUCT,
"MANAGER",
new SqlReturnSqlDataWithAuxiliaryTypes(Manager.class, auxiliaryTypes)));
Manager manager = jdbcCall.executeFunction(Manager.class);
// ... do something with manager.
This worked, in that it returned a Manager object with two Workers in it.
Finally, if you have stored procedures that save a Manager object to the database, be aware that your Manager class's writeSQL method will not work. Unless you've written your own List implementation, List<Worker> cannot be casted to SQLData. Instead, you'll need to create an Oracle array object and put the entries in that. That however is awkward because you'll need the database connection to create the array, but that won't be available in the writeSQL method. See this question for one possible solution.

RowMapper returns the list , but execute returned values returns the list size as 1?

please find below my sample code.The Row mapper returns a list. When printed it give me the size in the DB but when i check
(List) employeeDaomap .get("allEmployees") i get the list size as 1 , and entire rows as one item? why what is the wrong in implementation
Also Spring doc says not to use rs.next(), how do we get the list of
values from the DB
public class MyTestDAO extends StoredProcedure {
/** The log. */
static Logger log = Logger.getLogger(MyTestDAO.class);
private static final String SPROC_NAME = "TestSchema.PKG_Test.prc_get_employee_list";
TestRowMapper mapper=new TestRowMapper();
public MyTestDAO(DataSource dataSource){
super(dataSource, SPROC_NAME);
declareParameter(new SqlOutParameter("allEmployees", OracleTypes.CURSOR, mapper));
compile();
}
/**
* Gets the myemplist data from the DB
*
*/
public List<EmployeeDAO> getEmployeeList()
throws Exception {
Map<String,Object> employeeDaomap =new HashMap<String,Object>();
employeeDaomap =execute();
log.info("employeeDaomap after execute ="+employeeDaomap);
log.info("employeeDaomap after execute size ="+employeeDaomap.size()); // expected 1
List<EmployeeDAO> list = (List<EmployeeDAO>) employeeDaomap .get("allEmployees");
log.info("size of the list ="+list.size()); // need to get the size of the list ,
return list;
}
private Map<String, Object> execute() {
return super.execute(new HashMap<String, Object>());
}
}
public class TestRowMapper implements RowMapper<List<EmployeeDAO>> {
static Logger log = Logger.getLogger(TestRowMapper.class);
#Override
public List<EmployeeDAO> mapRow(ResultSet rs, int rowNum)
throws SQLException {
// TODO Auto-generated method stub
rs.setFetchSize(3000);
List<EmployeeDAO> responseItems = new ArrayList<EmployeeDAO>();
EmployeeDAO responseItem = null;
log.info("row num "+rowNum);
while (rs.next()) {
responseItem = new EmployeeDAO();
responseItem.setID(rs.getString("id"));
responseItem.setName(rs.getString("name"));
responseItem.setDesc(rs.getString("desc"));
responseItems.add(responseItem);
}
log.info("TestRowMapper items ="+responseItems);
return responseItems;
}
}
The solution is to use the implements ResultSetExtractor instead of RowMapper and provide implementation for extractData.
public class TestRowMapper implements ResultSetExtractor<List<EmployeeDAO>> {
static Logger log = Logger.getLogger(TestRowMapper.class);
#Override
public List<EMAccountResponse> extractData(ResultSet rs)
throws SQLException, DataAccessException {
rs.setFetchSize(3000);
List<EmployeeDAO> responseItems = new ArrayList<EmployeeDAO>();
EmployeeDAO responseItem = null;
log.info("row num "+rowNum);
while (rs.next()) {
responseItem = new EmployeeDAO();
responseItem.setID(rs.getString("id"));
responseItem.setName(rs.getString("name"));
responseItem.setDesc(rs.getString("desc"));
responseItems.add(responseItem);
}
log.info("TestRowMapper items ="+responseItems);
return responseItems;
}
}

Spring -Mongodb storing/retrieving enums as int not string

My enums are stored as int in mongodb (from C# app). Now in Java, when I try to retrieve them, it throws an exception (it seems enum can be converted from string value only). Is there any way I can do it?
Also when I save some collections into mongodb (from Java), it converts enum values to string (not their value/cardinal). Is there any override available?
This can be achieved by writing mongodb-converter on class level but I don't want to write mondodb-converter for each class as these enums are in many different classes.
So do we have something on the field level?
After a long digging in the spring-mongodb converter code,
Ok i finished and now it's working :) here it is (if there is simpler solution i will be happy see as well, this is what i've done ) :
first define :
public interface IntEnumConvertable {
public int getValue();
}
and a simple enum that implements it :
public enum tester implements IntEnumConvertable{
vali(0),secondvali(1),thirdvali(5);
private final int val;
private tester(int num)
{
val = num;
}
public int getValue(){
return val;
}
}
Ok, now you will now need 2 converters , one is simple ,
the other is more complex. the simple one (this simple baby is also handling the simple convert and returns a string when cast is not possible, that is great if you want to have enum stored as strings and for enum that are numbers to be stored as integers) :
public class IntegerEnumConverters {
#WritingConverter
public static class EnumToIntegerConverter implements Converter<Enum<?>, Object> {
#Override
public Object convert(Enum<?> source) {
if(source instanceof IntEnumConvertable)
{
return ((IntEnumConvertable)(source)).getValue();
}
else
{
return source.name();
}
}
}
}
the more complex one , is actually a converter factory :
public class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
#Override
public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
if (enumType == null) {
throw new IllegalArgumentException(
"The target type " + targetType.getName() + " does not refer to an enum");
}
return new IntegerToEnum(enumType);
}
#ReadingConverter
public static class IntegerToEnum<T extends Enum> implements Converter<Integer, Enum> {
private final Class<T> enumType;
public IntegerToEnum(Class<T> enumType) {
this.enumType = enumType;
}
#Override
public Enum convert(Integer source) {
for(T t : enumType.getEnumConstants()) {
if(t instanceof IntEnumConvertable)
{
if(((IntEnumConvertable)t).getValue() == source.intValue()) {
return t;
}
}
}
return null;
}
}
}
and now for the hack part , i personnaly didnt find any "programmitacly" way to register a converter factory within a mongoConverter , so i digged in the code and with a little casting , here it is (put this 2 babies functions in your #Configuration class)
#Bean
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
converters.add(new IntegerEnumConverters.EnumToIntegerConverter());
// this is a dummy registration , actually it's a work-around because
// spring-mongodb doesnt has the option to reg converter factory.
// so we reg the converter that our factory uses.
converters.add(new IntegerToEnumConverterFactory.IntegerToEnum(null));
return new CustomConversions(converters);
}
#Bean
public MappingMongoConverter mappingMongoConverter() throws Exception {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(appContext);
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mappingContext);
mongoConverter.setCustomConversions(customConversions());
ConversionService convService = mongoConverter.getConversionService();
((GenericConversionService)convService).addConverterFactory(new IntegerToEnumConverterFactory());
mongoConverter.afterPropertiesSet();
return mongoConverter;
}
You will need to implement your custom converters and register it with spring.
http://static.springsource.org/spring-data/data-mongo/docs/current/reference/html/#mongo.custom-converters
Isn't it easier to use plain constants rather than an enum...
int SOMETHING = 33;
int OTHER_THING = 55;
or
public class Role {
public static final Stirng ROLE_USER = "ROLE_USER",
ROLE_LOOSER = "ROLE_LOOSER";
}
String yourRole = Role.ROLE_LOOSER

Resources