say i have a java bean/an entity with 100 fields (inherited or not it is not relevant in this case). After update operations - in a transaction, i want to determine which fields are modified to track updates like a CVS. What is the easiest way to do this? Any Framework suggestion? Should i make two instances of this object and iterate over all fields and match the values of fields ? How would the best equals method seem in such situations ? The following equals() seems very awkward :
return (field1.equals(o.field1)) &&
(field2.equals(o.field2)) &&
(field3.equals(o.field3)) &&
...
(field100.equals(o.field100));
You could use Apache Commons Beanutils. Here's a simple example:
package at.percom.temp.zztests;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.beanutils.PropertyUtilsBean;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Main {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Main main = new Main();
main.start();
}
public void start() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
SampleBean oldSample = new SampleBean("John", "Doe", 1971);
SampleBean newSample = new SampleBean("John X.", "Doe", 1971);
SampleBean diffSample = (SampleBean) compareObjects(oldSample, newSample, new HashSet<>(Arrays.asList("lastName")), 10L);
}
public Object compareObjects(Object oldObject, Object newObject, Set<String> propertyNamesToAvoid, Long deep) {
return compareObjects(oldObject, newObject, propertyNamesToAvoid, deep, null);
}
private Object compareObjects(Object oldObject, Object newObject, Set<String> propertyNamesToAvoid, Long deep,
String parentPropertyPath) {
propertyNamesToAvoid = propertyNamesToAvoid != null ? propertyNamesToAvoid : new HashSet<>();
parentPropertyPath = parentPropertyPath != null ? parentPropertyPath : "";
Object diffObject = null;
try {
diffObject = oldObject.getClass().newInstance();
} catch (Exception e) {
return diffObject;
}
BeanMap map = new BeanMap(oldObject);
PropertyUtilsBean propUtils = new PropertyUtilsBean();
for (Object propNameObject : map.keySet()) {
String propertyName = (String) propNameObject;
String propertyPath = parentPropertyPath + propertyName;
if (!propUtils.isWriteable(diffObject, propertyName) || !propUtils.isReadable(newObject, propertyName)
|| propertyNamesToAvoid.contains(propertyPath)) {
continue;
}
Object property1 = null;
try {
property1 = propUtils.getProperty(oldObject, propertyName);
} catch (Exception e) {
}
Object property2 = null;
try {
property2 = propUtils.getProperty(newObject, propertyName);
} catch (Exception e) {
}
try {
if (property1 != null && property2 != null && property1.getClass().getName().startsWith("com.racing.company")
&& (deep == null || deep > 0)) {
Object diffProperty = compareObjects(property1, property2, propertyNamesToAvoid,
deep != null ? deep - 1 : null, propertyPath + ".");
propUtils.setProperty(diffObject, propertyName, diffProperty);
} else {
if (!Objects.deepEquals(property1, property2)) {
propUtils.setProperty(diffObject, propertyName, property2);
System.out.println("> " + propertyPath + " is different (oldValue=\"" + property1 + "\", newValue=\""
+ property2 + "\")");
} else {
System.out.println(" " + propertyPath + " is equal");
}
}
} catch (Exception e) {
}
}
return diffObject;
}
public class SampleBean {
public String firstName;
public String lastName;
public int yearOfBirth;
public SampleBean(String firstName, String lastName, int yearOfBirth) {
this.firstName = firstName;
this.lastName = lastName;
this.yearOfBirth = yearOfBirth;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getYearOfBirth() {
return yearOfBirth;
}
}
}
Hey look at Javers it's exactly what you need - objects auditing and diff framework . With Javers you can persist changes done on your domain objects with a single javers.commit() call after every update. When you persist some changes you can easily read them by javers.getChangeHistory, e.g.
public static void main(String... args) {
//get Javers instance
Javers javers = JaversBuilder.javers().build();
//create java bean
User user = new User(1, "John");
//commit current state
javers.commit("author", user);
//update operation
user.setUserName("David");
//commit change
javers.commit("author", user);
//read 100 last changes
List<Change> changes = javers.getChangeHistory(instanceId(1, User.class), 100);
//print change log
System.out.printf(javers.processChangeList(changes, new SimpleTextChangeLog()));
}
and the output is:
commit 2.0, author:author, 2015-01-07 23:00:10
changed object: org.javers.demo.User/1
value changed on 'userName' property: 'John' -> 'David'
commit 1.0, author:author, 2015-01-07 23:00:10
new object: 'org.javers.demo.User/1
You can use reflection to load the fields and then invoke them on each object and compare the result.
Example source code might look like this:
public static <T> void Compare(T source, T target) throws IllegalArgumentException, IllegalAccessException {
if(source == null) {
throw new IllegalArgumentException("Null argument not excepted at this point");
}
Field[] fields = source.getClass().getFields();
Object sourceObject;
Object targetObject;
for(Field field : fields){
sourceObject = field.get(source);
targetObject = field.get(target);
//Compare the object
}
}
FYI, this code will work only on public fields declared for class.
You can use Apache BeanUtils to checkout the properties.
Related
I want to keep a part of a JSON as String value.
As far as i know, there is no way with Annotations, but i could not find a way how to get the full Object/Array value as String.
There is a Workaround, which works, by reading it as an Object and instantly write it back as an String by using the ObjectMapper of Jackson.
You can imagine, this is a horrible solution for very big JSONs.
public class DeserializeTest {
private static ObjectMapper mapper;
public static void main(String[] args) throws IOException {
mapper = Jackson2ObjectMapperBuilder.json().build();
mapper.findAndRegisterModules();
SimpleModule module = new SimpleModule();
module.addDeserializer(TestClassWrapper.class, new TestDeserializer());
mapper.registerModule(module);
String json = "{\"name\":\"testprop\", \"data\":[{\"prop\":\"test\"},{\"prop\":\"test1\"},{\"prop\":\"test2\"}]}";
TestClassWrapper t = mapper.readValue(json, TestClassWrapper.class);
// later in program, when i know the expected class
TestClass o = unwrap(t, new TypeReference<ArrayList<Test2>>() {});
}
public static class TestClassWrapper {
String name;
String data;
// removed getter and setter
}
public static class TestClass {
String name;
List<Test2> data;
// removed getter and setter
}
public static class Test2 {
String prop;
// removed getter and setter
}
public static class TestDeserializer extends JsonDeserializer<TestClassWrapper> {
#Override
public TestClassWrapper deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
TestClassWrapper t = new TestClassWrapper();
String key = p.getCurrentName();
if (key == null) {
p.nextToken();
key = p.getCurrentName();
}
for (; key != null; key = p.nextFieldName()) {
p.nextToken();
switch (key) {
case "name":
t.name = p.getValueAsString();
break;
case "data":
// what i tried:
System.out.println(p.getText()); // [
System.out.println(p.getValueAsString()); // NULL
System.out.println(p.getCurrentValue()); //NULL
System.out.println(p.getCurrentToken()); // [ TOKEN
System.out.println(p.getParsingContext().getCurrentValue()); // NULL
System.out.println(p.getParsingContext().toString()); // [0]
System.out.println(p.getEmbeddedObject()); // NULL
System.out.println(p.getTextCharacters()); // [
try {
System.out.println(ctxt.readValue(p, String.class)); // MismatchedInputException
} catch (MismatchedInputException e){}
// The only way i could make it work.
// Parse to a object and write it back as string.
StringBuilder sb = new StringBuilder();
Iterator<Object> it = p.readValuesAs(Object.class);
while (it.hasNext()) {
sb.append(mapper.writeValueAsString(it.next()));
sb.append(it.hasNext() ? "," : "");
}
t.data = p.getCurrentToken() == JsonToken.END_ARRAY ? "[" + sb.toString() + "]" : sb.toString();
break;
}
}
return t;
}
}
public static TestClass unwrap(TestClassWrapper t, TypeReference targetClass) throws IOException {
TestClass o = new TestClass();
o.name = t.name;
o.data = mapper.readValue(t.data, targetClass);
return o;
}
}
How can i tell the JsonParser object, to just give me the String of the current value?
(For data this would be: "[{"prop":"test"}, {"prop":"test1"}, {"prop":"test2"}]")
I have requirement where I need to convert java object to json.
I am using Gson for that but i need the converter to only serialize the non null or not empty values.
For example:
//my java object looks like
class TestObject{
String test1;
String test2;
OtherObject otherObject = new OtherObject();
}
now my Gson instance to convert this object to json looks like
Gson gson = new Gson();
TestObject obj = new TestObject();
obj.test1 = "test1";
obj.test2 = "";
String jsonStr = gson.toJson(obj);
println jsonStr;
In the above print, the result is
{"test1":"test1", "test2":"", "otherObject":{}}
Here i just wanted the result to be
{"test1":"test1"}
Since the test2 is empty and otherObject is empty, i don't want them to be serialized to json data.
Btw, I am using Groovy/Grails so if there is any plugin for this that would be good, if not any suggestion to customize the gson serialization class would be good.
Create your own TypeAdapter
public class MyTypeAdapter extends TypeAdapter<TestObject>() {
#Override
public void write(JsonWriter out, TestObject value) throws IOException {
out.beginObject();
if (!Strings.isNullOrEmpty(value.test1)) {
out.name("test1");
out.value(value.test1);
}
if (!Strings.isNullOrEmpty(value.test2)) {
out.name("test2");
out.value(value.test1);
}
/* similar check for otherObject */
out.endObject();
}
#Override
public TestObject read(JsonReader in) throws IOException {
// do something similar, but the other way around
}
}
You can then register it with Gson.
Gson gson = new GsonBuilder().registerTypeAdapter(TestObject.class, new MyTypeAdapter()).create();
TestObject obj = new TestObject();
obj.test1 = "test1";
obj.test2 = "";
System.out.println(gson.toJson(obj));
produces
{"test1":"test1"}
The GsonBuilder class has a bunch of methods to create your own serialization/deserialization strategies, register type adapters, and set other parameters.
Strings is a Guava class. You can do your own check if you don't want that dependency.
What I personally don't like in TypeAdapter using answer is the fact you need to describe every field of your entire class which could have lets say 50 fields (which means 50 if blocks in TypeAdapter).
My solution is based on Reflection and a fact Gson will not serialize null values fields by default.
I have a special class which holds data for API to create document called DocumentModel, which has about 50 fields and I don't like to send String fields with "" (empty but not null) values or empty arrays to server. So I created a special method which returns me a copy of my object with all empty fields nulled. Note - by default all arrays in my DocumentModel instance are initialized as empty (zero length) arrays and thus they are never null, you should probably check your arrays for null before checking their length.
public DocumentModel getSerializableCopy() {
Field fields[] = new Field[]{};
try {
// returns the array of Field objects representing the public fields
fields = DocumentModel.class.getDeclaredFields();
} catch (Exception e) {
e.printStackTrace();
}
DocumentModel copy = new DocumentModel();
Object value;
for (Field field : fields) {
try {
value = field.get(this);
if (value instanceof String && TextUtils.isEmpty((String) value)) {
field.set(copy, null);
// note: here array is not being checked for null!
else if (value instanceof Object[] && ((Object[]) value).length == 0) {
field.set(copy, null);
} else
field.set(copy, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return copy;
}
Using this method I don't care if some fields was added or removed after this method was written or whatever. The only problem left - is checking custom type fields, which are not String or array, but this depends to particular class and should be extra coded in if/else blocks.
It seems to me the problem is not with gson. Gson correctly keeps track of the difference between null and an empty string. Are you sure you want to erase that distinction? Are you sure all classes that use TestObject don't care?
What you could do if you don't care about the difference is to change the empty strings to null within a TestObject before serializing it. Or better, make the setters in TestObject such that an empty string is set to null; that way you define rigidly within the class that an empty string is the same as null. You'll have to make sure the values cannot be set outside the setters.
I have ran into the same problem and found 2 distinct solutions
Write a custom TypeAdapter for each field class
TypeAdapter example for String class:
#SuppressWarnings("rawtypes")
public class JSONStringAdapter extends TypeAdapter {
#Override
public String read(JsonReader jsonReader) throws IOException {
String value = jsonReader.nextString();
if(value == null || value.trim().length() == 0) {
return null;
} else {
return value;
}
}
#Override
public void write(JsonWriter jsonWriter, Object object) throws IOException {
String value = String.valueOf(object);
if(value == null || value.trim().length() == 0) {
jsonWriter.nullValue();
} else {
jsonWriter.value(value);
}
}
}
Use:
public class Doggo {
#JsonAdapter(JSONStringAdapter.class)
private String name;
public Doggo(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Doggo aDoggo = new Doggo("");
String jsonString = new Gson().toJson(aDoggo);
}
}
Process the object manually before generating the JSON string
Seems to work on anything, haven't tested the performance:
public static boolean removeEmpty(JSONObject source) {
if (null == source || source.length() == 0) {
return true;
}
boolean isJsonObjectEmpty = false;
for (String key : JSONObject.getNames(source)) {
Object value = source.get(key);
boolean isValueEmpty = isValueEmpty(value);
if(isValueEmpty) {
source.remove(key);
}
}
if(source.length() == 0) {
isJsonObjectEmpty = true;
}
return isJsonObjectEmpty;
}
private static boolean isValueEmpty(Object value) {
if (null == value) {
return true;
}
if (value instanceof JSONArray) {
JSONArray arr = (JSONArray) value;
if(arr.length() > 0) {
List<Integer> indextesToRemove = new ArrayList<>();
for(int i = 0; i< arr.length(); i++) {
boolean isValueEmpty = isValueEmpty(arr.get(i));
if(isValueEmpty) {
indextesToRemove.add(i);
};
}
for(Integer index : indextesToRemove) {
arr.remove(index);
}
if(arr.length() == 0) {
return true;
}
} else {
return true;
}
} else if (value instanceof JSONObject) {
return removeEmpty((JSONObject) value);
} else {
if (JSONObject.NULL.equals(value)
|| null == value
|| value.toString().trim().length() == 0)
) {
return true;
}
}
return false;
}
Use:
public class Doggo {
private String name;
public Doggo(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Doggo aDoggo = new Doggo("");
// if you are not using Type Adapters for your fields
JSONObject aJSONObject1 = new JSONObject(aDoggo);
removeEmpty(aJSONObject1);
String jsonString1 = aJSONObject1.toString();
// if you are using Type Adapters for your fields
Gson gsonParser = new Gson();
JSONObject aJSONObject2 = new JSONObject(gsonParser .toJson(aDoggo));
removeEmpty(aJSONObject2);
String jsonString2 = aJSONObject2.toString();
}
}
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.
say i have a java bean/an entity with 100 fields (inherited or not it is not relevant in this case). After update operations - in a transaction, i want to determine which fields are modified to track updates like a CVS. What is the easiest way to do this? Any Framework suggestion? Should i make two instances of this object and iterate over all fields and match the values of fields ? How would the best equals method seem in such situations ? The following equals() seems very awkward :
return (field1.equals(o.field1)) &&
(field2.equals(o.field2)) &&
(field3.equals(o.field3)) &&
...
(field100.equals(o.field100));
You could use Apache Commons Beanutils. Here's a simple example:
package at.percom.temp.zztests;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.beanutils.PropertyUtilsBean;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Main {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Main main = new Main();
main.start();
}
public void start() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
SampleBean oldSample = new SampleBean("John", "Doe", 1971);
SampleBean newSample = new SampleBean("John X.", "Doe", 1971);
SampleBean diffSample = (SampleBean) compareObjects(oldSample, newSample, new HashSet<>(Arrays.asList("lastName")), 10L);
}
public Object compareObjects(Object oldObject, Object newObject, Set<String> propertyNamesToAvoid, Long deep) {
return compareObjects(oldObject, newObject, propertyNamesToAvoid, deep, null);
}
private Object compareObjects(Object oldObject, Object newObject, Set<String> propertyNamesToAvoid, Long deep,
String parentPropertyPath) {
propertyNamesToAvoid = propertyNamesToAvoid != null ? propertyNamesToAvoid : new HashSet<>();
parentPropertyPath = parentPropertyPath != null ? parentPropertyPath : "";
Object diffObject = null;
try {
diffObject = oldObject.getClass().newInstance();
} catch (Exception e) {
return diffObject;
}
BeanMap map = new BeanMap(oldObject);
PropertyUtilsBean propUtils = new PropertyUtilsBean();
for (Object propNameObject : map.keySet()) {
String propertyName = (String) propNameObject;
String propertyPath = parentPropertyPath + propertyName;
if (!propUtils.isWriteable(diffObject, propertyName) || !propUtils.isReadable(newObject, propertyName)
|| propertyNamesToAvoid.contains(propertyPath)) {
continue;
}
Object property1 = null;
try {
property1 = propUtils.getProperty(oldObject, propertyName);
} catch (Exception e) {
}
Object property2 = null;
try {
property2 = propUtils.getProperty(newObject, propertyName);
} catch (Exception e) {
}
try {
if (property1 != null && property2 != null && property1.getClass().getName().startsWith("com.racing.company")
&& (deep == null || deep > 0)) {
Object diffProperty = compareObjects(property1, property2, propertyNamesToAvoid,
deep != null ? deep - 1 : null, propertyPath + ".");
propUtils.setProperty(diffObject, propertyName, diffProperty);
} else {
if (!Objects.deepEquals(property1, property2)) {
propUtils.setProperty(diffObject, propertyName, property2);
System.out.println("> " + propertyPath + " is different (oldValue=\"" + property1 + "\", newValue=\""
+ property2 + "\")");
} else {
System.out.println(" " + propertyPath + " is equal");
}
}
} catch (Exception e) {
}
}
return diffObject;
}
public class SampleBean {
public String firstName;
public String lastName;
public int yearOfBirth;
public SampleBean(String firstName, String lastName, int yearOfBirth) {
this.firstName = firstName;
this.lastName = lastName;
this.yearOfBirth = yearOfBirth;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getYearOfBirth() {
return yearOfBirth;
}
}
}
Hey look at Javers it's exactly what you need - objects auditing and diff framework . With Javers you can persist changes done on your domain objects with a single javers.commit() call after every update. When you persist some changes you can easily read them by javers.getChangeHistory, e.g.
public static void main(String... args) {
//get Javers instance
Javers javers = JaversBuilder.javers().build();
//create java bean
User user = new User(1, "John");
//commit current state
javers.commit("author", user);
//update operation
user.setUserName("David");
//commit change
javers.commit("author", user);
//read 100 last changes
List<Change> changes = javers.getChangeHistory(instanceId(1, User.class), 100);
//print change log
System.out.printf(javers.processChangeList(changes, new SimpleTextChangeLog()));
}
and the output is:
commit 2.0, author:author, 2015-01-07 23:00:10
changed object: org.javers.demo.User/1
value changed on 'userName' property: 'John' -> 'David'
commit 1.0, author:author, 2015-01-07 23:00:10
new object: 'org.javers.demo.User/1
You can use reflection to load the fields and then invoke them on each object and compare the result.
Example source code might look like this:
public static <T> void Compare(T source, T target) throws IllegalArgumentException, IllegalAccessException {
if(source == null) {
throw new IllegalArgumentException("Null argument not excepted at this point");
}
Field[] fields = source.getClass().getFields();
Object sourceObject;
Object targetObject;
for(Field field : fields){
sourceObject = field.get(source);
targetObject = field.get(target);
//Compare the object
}
}
FYI, this code will work only on public fields declared for class.
You can use Apache BeanUtils to checkout the properties.
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