How to process multi-row result in a custom way? - java

For now I have method body of which looks like this:
jdbcTemplate.query(queryJoiningTwoTables, (rs, rowNum) -> {
final long id= rs.getLong("id");
MyObject obj = jobStatusBunchMap.get(id);
if (obj== null) {
OffsetDateTime offsetDateTime = rs.getObject("creation_timestamp", OffsetDateTime.class);
...
obj = new MyObject (offsetDateTime ...);
map.put(id, obj );
}
String jobId = rs.getString("job_id");
obj.getJobIds().add(jobId);
return null;
});
return map.values();
Looks like I use API in improper way.
Is there better method to achieve the same ?
P.S.
I tried to use jdbcTemplate#queryForRowSet but at this case rs.getObject("creation_timestamp", OffsetDateTime.class) throws exception that it is not supported operation.

There are many options to map the results using jdbcTemplate, including yours.
Maybe this will help you to understand it better:
public List<Action> findAllActions() {
final String selectStatement = "SELECT id,name FROM Actions"; // or your query
try {
return jdbcTemplate.query(selectStatement,(resultSet, rowNum) -> {
int actionId = resultSet.getInt("id");
String actionName = resultSet.getString("name");
Action action = new Action();
action.setId(actionId);
action.setName(actionName);
return action;
});
} catch (EmptyResultDataAccessException e) {
LOGGER.error("Get all actions - empty set", e);
return Collections.emptyList();
}
}
Without lambda expressions you could use jdbcTemplate query like this:
public List<Action> findAllActions() {
final String selectStatement = "SELECT id,name FROM Actions"; // or your query
try {
return jdbcTemplate.query(selectStatement, getActionRowMapper());
} catch (EmptyResultDataAccessException e) {
LOGGER.error("Get all actions - empty set", e);
return Collections.emptyList();
}
}
private RowMapper<Action> getActionRowMapper() {
return (resultSet, rowNum) -> {
int actionId = resultSet.getInt("id");
String actionName = resultSet.getString("name");
return action;
};
}
As you can see, the second parameter of jdbcTemplate.query method takes a RowMapper<Action> type but is hidden using lambdas expressions. This option is "restricting" you from making anything else besides mapping the resultSet for every row and return the result. The final result will be eventually a List of actions.
The second option is using a ResultSetExtractor which will let you to loop through the result set and gives you more flexibility. The query will be the same, just the second parameter of jdbcTemplate.query method will be changed. And for this, I would personally implement the ResultSetExtractor and override the extractData method, or you can do the same as above if you don't need anything else just to map the results
public List<Group> findGroups() {
final String selectStatement = "SELECT stud.id, stud.name, gr.id, gr.name FROM student stud INNER JOIN group gr ON gr.id=stud.group_id ORDER BY stud.id"; // or your query
try {
return jdbcTemplate.query(selectStatement, new GroupExtractor() );
} catch (EmptyResultDataAccessException e) {
LOGGER.error("Get all groups - empty set", e);
return Collections.emptyList();
}
}
public class GroupExtractor implements ResultSetExtractor<List<Group>> {
#Override
public List<Group> extractData(ResultSet resultSet) {
Map<Group, List<Student>> studentsGroup= new HashMap<>();
List<Group> groups = new ArrayList<>();
try {
while (resultSet.next()) {
int studentId = resultSet.getInt("stud.id");
String studentName = resultSet.getString("stud.name");
int groupId = resultSet.getInt("gr.id");
String groupName = resultSet.getString("gr.name");
Group group = createGroup(groupId, groupName);
Student student = createStudent(studentId, studentName);
studentsGroup.putIfAbsent(group, new ArrayList<>());
studentsGroup.get(group).add(student);
}
studentsGroup.forEach((group,students) ->{
group.setStudents(students);
groups.add(group);
}
return groups;
} catch (SQLException e) {
LOGGER.info("An error occured during extracting data", e);
}
return actions;
}
private Student createStudent(String studentId, String studentName)
{
Student student=new Student();
student.setId(studentId);
student.setName(studentName);
return student;
}
//idem for createGroup
}

Related

Return different type of object based on condition using Java

I've three queries which is
SELECT NAME FROM EMP WHERE EMPID=100; // THIS RETURNS A STRING
SELECT DEPTID FROM EMP WHERE EMPID=100; // THIS RETURNS AN INT
SELECT EMPID FROM DEPT WHERE DEPTID=101; // THIS RETURNS A LIST
Now I'm trying to write a generic code to return the object, but however I'm able to send only one type i.e. list of object at a high level. Is it possible to send result as it is based on the type?
Here is my code
public Object getObjectFromDB(final String query, final Map<Object, Object> inputMap, JdbcTemplate jdbcTemplate, final Object returnType) throws Exception {
final List<Object> obj = new ArrayList<Object>();
try {
jdbcTemplate.query(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement(query);
}
}, new PreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps) throws SQLException {
}
}, new ResultSetExtractor<Object>() {
#Override
public Object extractData(ResultSet rs) throws SQLException,DataAccessException {
while(rs.next()) {
if(returnType instanceof Integer) {
obj.add(rs.getInt(1));
break;
} else if(returnType instanceof String) {
obj.add(rs.getString(1));
break;
} else if(returnType instanceof List) {
obj.add(rs.getObject(1));
}
}
return obj;
}
});
} catch (Exception e) {
LOG.error(e);
}
return obj;
}
It always returns obj which is a list object even though if I want to trigger first query. Any ideas would be appreciated.
Have you tried writing a generic method? See Generic Methods for more info. This could easily work if you already know the return type you are expecting for any particular set of parameters that you pass to your method.
This post describes how to write a method with generic return type.
One possible function protoype can be :
public static <T> List<T> getObjectFromDB(final String query, final Map<Object, Object> inputMap, JdbcTemplate jdbcTemplate, T returnType){
}
It will always return a List<Object> as you are declaring it here
final List<Object> obj = new ArrayList<Object>();
try to check the first element of obj (as it's a list) and its type.

Java working with .class

I am creating java app which will allow storing objects in database. What I want to do is generic implementation so it could load json and create java class from it. This is what a code should look like:
SomeClass someObject= data.getValue(SomeClass.class);
Lets say that data would be a json object. How should I implement getValue() method so it will allow me to create class from it. I don't want SomeClass to extend anything other then Object. I think that this should be done using generic classes but so far I have not worked with generic classes like this. Can you please point to a best way on how to acomplish this? Example code would be best.
Many thanks
You can consult the source code of Jackson library and look inside (or debug) the method BeanDeserializer#vanillaDeserialize(), there you'll find the loop which traverse through all json tokens, finds the corresponding fields and sets their values.
As a proof of concept, I've extracted part of the logic from Jacskson and wrapped it inside a naive (and fragile) object mapper and a naive (and fragile) json parser:
public static class NaiveObjectMapper {
private Map<String, Object> fieldsAndMethods;
private NaiveJsonParser parser;
public <T> T readValue(String content, Class<T> valueType) {
parser = new NaiveJsonParser(content);
try {
// aggregate all value type fields and methods inside a map
fieldsAndMethods = new HashMap<>();
for (Field field : valueType.getDeclaredFields()) {
fieldsAndMethods.put(field.getName(), field);
}
for (Method method : valueType.getMethods()) {
fieldsAndMethods.put(method.getName(), method);
}
// create an instance of value type by calling its default constructor
Constructor<T> constructor = valueType.getConstructor();
Object bean = constructor.newInstance(new Object[0]);
// loop through all json nodes
String propName;
while ((propName = parser.nextFieldName()) != null) {
// find the corresponding field
Field prop = (Field) fieldsAndMethods.get(propName);
// get and set field value
deserializeAndSet(prop, bean);
}
return (T) bean;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private void deserializeAndSet(Field prop, Object bean) {
Class<?> propType = prop.getType();
Method setter = (Method) fieldsAndMethods.get(getFieldSetterName(prop));
try {
if (propType.isPrimitive()) {
if (propType.getName().equals("int")) {
setter.invoke(bean, parser.getIntValue());
}
} else if (propType == String.class) {
setter.invoke(bean, parser.getTextValue());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private String getFieldSetterName(Field prop) {
String propName = prop.getName();
return "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
}
}
class NaiveJsonParser {
String[] nodes;
int currentNodeIdx = -1;
String currentProperty;
String currentValueStr;
public NaiveJsonParser(String content) {
// split the content into 'property:value' nodes
nodes = content.replaceAll("[{}]", "").split(",");
}
public String nextFieldName() {
if ((++currentNodeIdx) >= nodes.length) {
return null;
}
String[] propertyAndValue = nodes[currentNodeIdx].split(":");
currentProperty = propertyAndValue[0].replace("\"", "").trim();
currentValueStr = propertyAndValue[1].replace("\"", "").trim();
return currentProperty;
}
public String getTextValue() {
return String.valueOf(currentValueStr);
}
public int getIntValue() {
return Integer.valueOf(currentValueStr).intValue();
}
}
public static class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "id = " + id + ", name = \"" + name + "\"";
}
}
To see the deserialization in action run:
String json = "{\"id\":1, \"name\":\"jsmith\"}";
NaiveObjectMapper objectMapper = new NaiveObjectMapper();
User user = objectMapper.readValue(json, User.class);
System.out.println(user);
Or try online.
However I recommend not to reinvent the wheel and use Jackson and in case you need some custom actions you can use custom deserialization, see here and here.

Convert object containing repeated fields to JSON

I have two table country and city in mysql dababase and i make a query to return records like that as List<myDTO> :
1,france,1,paris
1,france,2,marseille
1,france,3,lion
....
MyDTO
public class MyDTO {
public Integer idLvl1;
public String nameLvl1;
public Integer idLvl2;
public String nameLvl2;
public MyDTO(Integer idLvl1, String nameLvl1, Integer idLvl2, String nameLvl2) {
this.idNiv1 = idLvl1;
this.nomNiv1 = nameLvl1;
this.idNiv2 = idLvl2;
this.nomNiv2 = nameLvl2;
}
How can i convert it to json object to avoid the repeating country :
[
{"idNiv1" :1,"nameLvl1":"France","cities":[{"idNiv2":1,"nameLvl2":"paris"}]}
{"idNiv1" :1,"nameLvl1":"France","cities":[{"idNiv2":2,"nameLvl2":"marseille"}]}
{"idNiv1" :1,"nameLvl1":"France","cities":[{"idNiv2":3,"nameLvl2":"lion"}]}
....
]
to
[
{
"idNiv1" :1,
"nameLvl1":"France",
"cities":[
{ "idNiv2":1,"nameLvl2":"paris" } ,
{ "idNiv2":2,"nameLvl2":"marseille" } ,
{ "idNiv2":3,"nameLvl2":"lion" }
]
}
....
]
You can use Google's Gson in this case :
public String getJSONFromResultSet(ResultSet rs, String key) {
Map json = new HashMap();
List list = new ArrayList();
if (rs != null) {
try {
ResultSetMetaData mData = rs.getMetaData();
while (rs.next()) {
Map<String, Object> columns = new HashMap<String, Object>();
for (int columnIndex = 1; columnIndex <= mData.getColumnCount(); columnIndex++) {
if (rs.getString(mData.getColumnName(columnIndex)) != null) {
columns.put(mData.getColumnLabel(columnIndex),
rs.getString(mData.getColumnName(columnIndex)));
} else {
columns.put(mData.getColumnLabel(columnIndex), "");
}
}
list.add(columns);
}
} catch (SQLException e) {
e.printStackTrace();
}
json.put(key, list);
}
return new Gson().toJson(json);
}
Update:
You can call getJSONFromResultSet method like below :
Connection con = DBConnectionClass.myConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM Customer");
//as an example consider a table named Customer in your DB.
ResultSet rs = ps.executeQuery();
System.out.println(getJSONFromResultSet(rs, "customer"));
Create additional classes for country and city. Transform the flat structure to nested structure of country and cities as shown below:
public class Country {
Integer idLvl1;
String nameLvl1;
public Country(Integer idLvl1, String nameLvl1) {
}
List<City> cities;
}
public class City {
Integer idLvl2;
String nameLvl2;
public City(Integer idLvl2, String nameLvl2) {
}
}
public class MyDTOConverter {
public static Collection<Country> covert(List<MyDTO> dtos){
Map<Integer, Country> countries = new LinkedHashMap<Integer, Country>();
for (MyDTO myDTO : dtos) {
//First adding the country if it doesn't exist
if (!countries.containsKey(myDTO.idLvl1)){
countries.put(myDTO.idLvl1, new Country(myDTO.idLvl1, myDTO.nameLvl1));
}
//Adding city in the existing country.
countries.get(myDTO.idLvl1).cities.add(new City(myDTO.idLvl2, myDTO.nameLvl2));
}
return countries.values();
}
}
The final Collection of Country will result is the desired JSON.
You can write Wrapper DTO on top of Your MyDTO and then use any available json libraries like Google's Gson to convert to required JSON format.
Regards,
Sakumar

Applying a list of values as predicate using Collection Utils by the use of pedicates

I want to implement Database systems in functionality by using the predicate.
This is as like if SQL filter a recordset by in it cumbersome the results.
But if i pass the List as in predicate it takes only one value i.e. if i am passing 53 and 54 it filter the results for 53 only.
public class classNamePredicate implements Predicate<className> {
private Object expected1;
private String property;
private List<Object> listOfValues = new ArrayList<Object>();
public SalesOrderPredicate(Object expected1, String property) {
super();
this.expected1 = expected1;
this.property = property;
}
public SalesOrderPredicate(List<Object> listValues, String property) {
this.listOfValues = listValues;
this.property = property;
}
#Override
public boolean evaluate(SalesOrder object) {
try {
if (property.equals("volume")) {
return ((Integer) expected1 < object.getVolume());
}
if (property.equals("startDateId")) {
return (expected1.equals(object.getStartDateId()));
}
if (property.equals("endDateId")) {
return (expected1.equals(object.getEndDateId()));
}
if (property.equals("productIds")) {
for (Object value : listOfValues) {
return (object.getProductId() == (Integer) value);
}
}
if (property.equals("sourceIds")) {
for (Object value : listOfValues) {
return (object.getSourceId() == (Integer) value);
}
}
return false;
} catch (Exception e) {
return false;
}
}
}
I am trying to use this as per the following way:
List<Object> productIds = new ArrayList<Object>();
productIds.add(53);
productIds.add(54);
List<Object> sourceIds = new ArrayList<Object>();
sourceIds.add(122);
Predicate[] classnameOrderPredicate = { (Predicate) new classnamePredicate(4415, "startDateId"),
(Predicate) new classnamePredicate(4443, "endDateId"), (Predicate) new classnamePredicate(100000, "volume"),
(Predicate) new classnamePredicate(productIds, "productIds"), (Predicate) new classnamePredicate(sourceIds, "sourceIds") };
Predicate classnameallPredicateGeneric = (Predicate) PredicateUtils
.allPredicate((org.apache.commons.collections4.Predicate<? super classname>[]) classnamePredicate);
Collection<classname> classnamefilteredCollectionGeneric = GenericCollectionUtils.select(classname, classnameallPredicateGeneric);
Please suggest in design perspective too.
Thanks in advance
You're only evaluating the first item in the collection:
for (Object value : listOfValues) {
return (object.getProductId() == (Integer) value);
}
You want to evaluate all of them, and Java conveniently provides a contains() method for that:
return listOfValues.contains(object.getProductId());
Other than that, the code looks pretty awful, you should create smaller, targeted Predicates, instead of writing a generic one with lots of different cases. You could get rid of those casts at the same time.
You also failed at your obfuscation by failing to replace a few SalesOrder by className (which doesn't respect the Java coding standard and is distracting).

ORMLite JOINs, or rawQuery auto mapping

I am looking for a way to do a query that requires a JOIN. Is there any way to do this in a prepared statement, or is the rawQuery the only option that I have. If rawQuery is the only option, then is there some way to automatically map the returned objects to the objects of the Dao being implemented.
I've dug through the documents and examples but cannot find anything that will allow me to map the raw database result to an ORM object class.
I am looking for a way to do a query that requires a JOIN.
ORMLite supports simple JOIN queries. You can also use raw-queries to accomplish this.
You can use the Dao.getRawRowMapper() to map the queries as you found or you can create a custom mapper. The documentation has the following sample code which shows how to map the String[] into your object:
GenericRawResults<Foo> rawResults =
orderDao.queryRaw(
"select account_id,sum(amount) from orders group by account_id",
new RawRowMapper<Foo>() {
public Foo mapRow(String[] columnNames,
String[] resultColumns) {
return new Foo(Long.parseLong(resultColumns[0]),
Integer.parseInt(resultColumns[1]));
}
});
I've found a way to auto map a result set to a model object.
// return the orders with the sum of their amounts per account
GenericRawResults<Order> rawResults =
orderDao.queryRaw(query, orderDao.getRawRowMapper(), param1)
// page through the results
for (Order order : rawResults) {
System.out.println("Account-id " + order.accountId + " has "
+ order.totalOrders + " total orders");
}
rawResults.close();
The key is to pull the row mapper from your object Dao using getRawRowMapper(), which will handle the mapping for you. I hope this helps anyone who finds it.
I still would love the ability to do joins within the QueryBuilder but until that is supported, this is the next best thing in my opinion.
Raw query auto mapping
I had problem of mapping fields from custom SELECT which return columns that are not present in any table model. So I made custom RawRowMapper which can map fields from custom query to custom model. This is useful when you have query which has fields that doesn't corresponds to any table maping model.
This is RowMapper which performs query auto mapping:
public class GenericRowMapper<T> implements RawRowMapper<T> {
private Class<T> entityClass;
private Set<Field> fields = new HashSet<>();
private Map<String, Field> colNameFieldMap = new HashMap<>();
public GenericRowMapper(Class<T> entityClass) {
this.dbType = dbType;
this.entityClass = entityClass;
Class cl = entityClass;
do {
for (Field field : cl.getDeclaredFields()) {
if (field.isAnnotationPresent(DatabaseField.class)) {
DatabaseField an = field.getAnnotation(DatabaseField.class);
fields.add(field);
colNameFieldMap.put(an.columnName(), field);
}
}
cl = cl.getSuperclass();
} while (cl != Object.class);
}
#Override
public T mapRow(String[] columnNames, String[] resultColumns) throws SQLException {
try {
T entity = entityClass.newInstance();
for (int i = 0; i < columnNames.length; i++) {
Field f = colNameFieldMap.get(columnNames[i]);
boolean accessible = f.isAccessible();
f.setAccessible(true);
f.set(entity, stringToJavaObject(f.getType(), resultColumns[i]));
f.setAccessible(accessible);
}
return entity;
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public Object stringToJavaObject(Class cl, String result) {
if (result == null){
return null;
}else if (cl == Integer.class || int.class == cl) {
return Integer.parseInt(result);
} else if (cl == Float.class || float.class == cl) {
return Float.parseFloat(result);
} else if (cl == Double.class || double.class == cl) {
return Double.parseDouble(result);
} else if (cl == Boolean.class || cl == boolean.class) {
try{
return Integer.valueOf(result) > 0;
}catch (NumberFormatException e){
return Boolean.parseBoolean(result);
}
} else if (cl == Date.class) {
DateLongType lType = DateLongType.getSingleton();
DateStringType sType = DateStringType.getSingleton();
try {
return lType.resultStringToJava(null, result, -1);
} catch (NumberFormatException e) {
try {
return sType.resultStringToJava(null, result, -1);
} catch (SQLException e2) {
throw new RuntimeException(e);
}
}
} else {
return result;
}
}
}
And here is the usage:
class Model{
#DatabaseField(columnName = "account_id")
String accId;
#DatabaseField(columnName = "amount")
int amount;
}
String sql = "select account_id,sum(amount) amount from orders group by account_id"
return queryRaw(sql,new GenericRowMapper<>(Model.class)).getResults()
This will return List<Model> with mapped result rows to Model if query column names and #DatabaseField(columnName are the same

Categories

Resources