I'm currently making some validations in my code and one of the main problem is I have a object list that has another object list and so on.
public class BigObject{
private Long idObject;
private String idLanguage;
private Date dateGeneration;
private List<FirstObject> firstObject;
//getters and setters
}
public class FirstObject{
private List<SecondObject> secondObject;
//getters and setters
}
public class SecondObject{
private Long order;
private String titol;
private int floatProperty;
//getters and setters
}
These are my classes and their are inside of another. I set up my Validator in the Main and created their respective class, now, in the validator class I have this:
public class BigObjectValidator implements Validator {
#Override
public boolean supports(Class clazz) {
return BigObject.class.equals(clazz)
|| FirstObject.class.equals(clazz)
|| SecondObject.class.equals(clazz);
}
#Override
public void validate(Object obj, Errors e) {
BigObject bigObject = (BigObject) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(e, "idObject", "empty.id");
ValidationUtils.rejectIfEmptyOrWhitespace(e, "idLanguage", "empty.id");
ValidationUtils.rejectIfEmptyOrWhitespace(e, "dateGeneration", "empty.id");
if (!(bigObject.getFirstObject().isEmpty())) {
for (FirstObject firstObject : bigObject.getFirstObject()) {
if (firstObject.getSecondObject() != null) {
for (SecondObject secondObject : firstObject.getSecondObject()) {
if (secondObject != null){
validateSecondObject(secondObject,e);
}
}
}
}
}
}
private void validateSecondObject(SecondObject secondObject, Errors e) {
ValidationUtils.rejectIfEmptyOrWhitespace(e, "order", "order.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(e, "titol", "order.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(e, "floatProperty", "order.empty");
}
}
The main problem is I'm getting a org.springframework.beans.NotReadablePropertyException: Invalid property 'order' of bean class I'm trying to guess why is that, its because the validator is set up in the BigObject class and not the other ones. Now I don't know if I have to create another class inside BigObjectValidator or something like that.
Edit:
Main
try{
BigObject object = new BigObject();
List<FirstObject> firstObj = ArrayList<FirstObject>;
SecondObject secondObj = new SecondObject();
object.getIdObject("something");
object.getIdLanguage("En");
object.getDateGeneration("05-18-2018");
secondObject.setOrder(null);
firstObj.set(1,secondObject);
BeanPropertyBindingResult result = new BeanPropertyBindingResult(je.getValue(), "Object");
BigObjectValidator validateObject = new BigObjectValidator();
validateObject.validate(object, result);
if (result.hasErrors()){
System.out.println(result.getAllErrors().toString());
}
}catch(Exception e){
System.out.println(e);
}
Please look here
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/Errors.html
public interface Errors Stores and exposes information about
data-binding and validation errors for a specific object. Field names
can be properties of the target object (e.g. "name" when binding to a
customer object), or nested fields in case of subobjects (e.g.
"address.street"). Supports subtree navigation via
setNestedPath(String): for example, an AddressValidator validates
"address", not being aware that this is a subobject of customer.
If you pass the same Errors object to your validateSecondObject method, it still references the original obj, not your firstObject.. You must validate this differently. Either get a new instance of Errors (eg. org.springframework.validation.BindException) or do it by manually throwing exceptions
Related
I have a Switch that contains 13 case, each case executes a different sql request. I got the result in an ArrayList<HashMap<String, String>>. This result is supposed to be displayed with angular , for now i'm using this this.respTest = JSON.stringify(response); so it displays a list of "key":"value" .
My problem is since each request gets me different database fields and values ,so I want to merge some fields .
I created this class :
public class DataCollect {
private String type ;
private String entity ;
private String modPar ;
private String dateModif ;
private String numVersion ;
public DataCollect(String type, String entity, String modPar, String dateModif, String numVersion) {
this.type = type;
this.entity = entity;
this.modPar = modPar;
this.dateModif = dateModif;
this.numVersion = numVersion;
}
public DataCollect() {
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public String getModPar() {
return modPar;
}
public void setModPar(String modPar) {
this.modPar = modPar;
}
public String getDateModif() {
return dateModif;
}
public void setDateModif(String dateModif) {
this.dateModif = dateModif;
}
public String getNumVersion() {
return numVersion;
}
public void setNumVersion(String numVersion) {
this.numVersion = numVersion;
} }
In this class I'm supposed to affect the fields' names to the variables that I created and as a return an arraylist of hashmap with the data I extracted from the data base.
I mean I used to return for example "field-name":"value" , I want to return "type":"value","entity":"value" ..etc
I'm using springboot for the backend and angular 5 for the front.
Any help would be appreciated.
What you essentially want is a way to map keys in [each of] your hashmap to the corresponding member variable in the "DataCollect" POJO.
If there is a one to one mapping between the key present and corresponding member variable, you can expose a public constructor in "DataCollect" that takes in the hash map and constructs the corresponding object.
public DataCollect(Map<String, String> result) {
this.type = result.get("type");
this.entity = result.get("db_entity_key");
...
}
If there is no one on one mapping, you'd have to create a factory class, which takes your Map as an input and some context, and returns you the constructed DataCollect object.
Once you have the constructor or the factory class, you only need to iterate over your results list and do the needful to convert each Map into the DataCollect object.
Your controller should automatically serialise the DataCollect objects to corresponding JSON, or you can even use Jackson's ObjectMapper to achieve the same.
I am using Jackson for de/serialization in my app.
I have a situation where I need to convert a JSON string to one of my 3 classes. In case the string can't be converted to either one of 3 classes, it will considered to be an unrecognized case.
However, if the schema of json string and the provided class in mapper.readValue(jsonString,MyClass1.class) does not match, it throws an UnrecognizedPropertyException.
Currently I am using something like below, but it seems to be pretty messy.
try {
obj = mapper.readValue(jsonString, MyClass1.class);
} catch (UnrecognizedPropertyException e1) {
try {
obj = mapper.readValue(jsonString, MyClass2.class);
} catch (UnrecognizedPropertyException e2) {
try {
obj = mapper.readValue(jsonString, MyClass3.class);
} catch (Exception e) {
//handle unrecognized string
}
} catch (Exception e) {
//handle unrecognized string
}
} catch (Exception e) {
//handle unrecognized string
}
Is this how it needs to be done or is there any other alternative? Is there any way to configure the mapper to return null in case of unrecognized properties, as that would result in creating a simple series if blocks instead of nested try-catch blocks?
You can try this method to do deserialization thing. this will return null on UnrecognizedPropertyException:
private <T> T deserialize(ObjectMapper mapper, Class<T> type, String jsonString) {
T t = null;
try {
t = mapper.readValue(jsonString, type);
} catch (UnrecognizedPropertyException e) {
//handle unrecognized string
}catch (IOException e) {
//handle under errors
}
return t;
}
If jsonString is generated by you, you can consider to add type info and then use it to convert deserialized object. You could refer to this post for how to do it.
If jsonString is generated by other services beyond your control, then there's no type info you can get so you can only try it one by one, #Sachin Gupta's answer would be a nice choice.
I'd like to provide an additional option: define an all-in-one entity including all fields of MyClass1, MyClass2 and MyClass3, and make MyClass1, MyClass2 and MyClass3 be separated wrapper and only expose related fields for each. Code as follows:
Class AllInOne:
public class AllInOne {
protected String a;
protected String b;
protected String c;
public A asA() {
return new A(this);
}
public B asB() {
return new B(this);
}
public C asC() {
return new C(this);
}
}
Class A:
public class A {
private AllInOne allInOne;
public A(AllInOne allInOne) {
this.allInOne = allInOne;
}
public String getA() {
return allInOne.a;
}
}
Class B:
public class B {
private AllInOne allInOne;
public B(AllInOne allInOne) {
this.allInOne = allInOne;
}
public String getB() {
return allInOne.b;
}
}
Class C:
public class C {
private AllInOne allInOne;
public C(AllInOne allInOne) {
this.allInOne = allInOne;
}
public String getC() {
return allInOne.c;
}
}
Test code:
public class Main {
public static void main(String[] args) throws IOException {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
String jsonA = "{\"a\":\"a value\"}";
String jsonB = "{\"b\":\"b value\"}";
String jsonC = "{\"c\":\"c value\"}";
needTypeA(om.readValue(jsonA, AllInOne.class).asA());
needTypeB(om.readValue(jsonB, AllInOne.class).asB());
needTypeC(om.readValue(jsonC, AllInOne.class).asC());
}
private static void needTypeA(A a) {
System.out.println(a.getA());
}
private static void needTypeB(B b) {
System.out.println(b.getB());
}
private static void needTypeC(C c) {
System.out.println(c.getC());
}
}
With implementation like this, we erased the specific type info at deserialization step, and bring it back at the moment we really need/use it. And as you can see there's not too much extra code, because what we actually did is just moving all fields declaration together, and added couple methods.
Notes:
I declare fields in AllInOne to be protected, putting all POJO class in the same package will make A, B and C be able to access them directly, but not for other classes outside.
Setting om.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); to make jackson deserialize by field, so that we can remove duplicate setter and getter from AllInOne class
If you do need to know the type info, you could add methods like isA inside AllInOne based on the fields info
If json contains some define property, than you can try to use #JsonTypeInfo and #JsonSubTypes. Classes MyClass1, ... must implement this interface. Also I don`t remember exactly how to map unknown implementations to null.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY, // level of define property
property = <property_name>,
visible = true,
defaultImpl = NoClass.class)
#JsonSubTypes({#JsonSubTypes.Type(value = <interface-impl>.class, name = <property_value>)})
private <interface> value;
// getters and setters
Consider the following situation:
public class A {
private String stringA;
public String getStringA() {
return stringA;
}
public void setStringA(String stringA) {
this.stringA = stringA;
}
}
public class B {
List<SomeObject> someObjects;
public List<SomeObject> getSomeObjects() {
if (someObjects == null) {
someObjects = new ArrayList<SomeObject>();
}
return someObjects;
}
}
public class SomeObject {
private String stringSomeObject;
public String getStringSomeObject() {
return stringSomeObject;
}
public void setStringSomeObject(String stringSomeObject) {
this.stringSomeObject = stringSomeObject;
}
}
I want to map from A to B. Whilst mapping these, stringA needs to be mapped to stringSomeObject in SomeObject. I tried to write a Orika-Mapper for this:
public class MyMapper extends ConfigurableMapper {
#Override
protected void configure(MapperFactory factory) {
ConverterFactory converterFactory = factory.getConverterFactory();
converterFactory.registerConverter(new StringToSomeObjectConverter());
factory.classMap(A.class, B.class) //
.field("stringA", "someObjects") //
.byDefault() //
.register();
}
}
It maps class A to B and whenever it encounters a conversion from String to List<SomeObject> it calls a custom-converter:
public class StringToSomeObjectConverter extends CustomConverter<String, List<SomeObject>> {
private static final String BORROWER_PARTY_TYP_CODE = "147";
#Override
public List<SomeObject> convert(String source, Type<? extends List<SomeObject>> destinationType) {
SomeObject someObject = new SomeObject();
someObject.setStringSomeObject(source);
return Arrays.asList(someObject);
}
}
I wrote an unit-test to ensure that this works:
#Test
public void testMap() throws Exception {
A a = new A();
a.setStringA("a");
B outcome = new MyMapper().map(a, B.class);
assertThat(outcome.getSomeObjects.size(), is(1));
}
Sadly this test fails with:
java.lang.AssertionError:
Expected: is <1>
but: was <0>
It seems like the Converter is never executed so I tried to debug it. And indeed: The debugger never reaches the converter. Am I doing something wrong? It seems like. I know there are more methods which one could go with like: mapAToB e.g...
Ok I found a solut...nah! It's not a solution, it's just a workaround. I defined the stringA as List<String> as well and defined a converter extending CustomConverter<String, LoanContrReqERPCrteReqLoanContrBrrwrPty>.
Because this feels a little "hacky", I am still interested in a nice solution. (Though I am just thinking that this solution might be fine: Now the datastructure of both objects is more equal than before. The problem is, that object B is coming from an external service, I can't modify it.)
You mapping doesn't work because you don't have setter for someObjects.
When Orika tries to generate code for mapper, it checks all fieldMaps in classMap for sourceProperty is readable and destinationProperty is assignable. If this checks passed, generator puts field conversion into generated mapper. If check failed, Orika just skip this field conversion.
Few options you can use to solve a problem:
You can add setter for someObjects field in class B:
public static class B {
List<SomeObject> someObjects;
public List<SomeObject> getSomeObjects() {
if (someObjects == null) {
someObjects = new ArrayList<SomeObject>();
}
return someObjects;
}
public void setSomeObjects(List<SomeObject> someObjects) {
this.someObjects = someObjects;
}
}
Use custom mapper instead of converter:
factory.classMap(A.class, B.class)
.customize(
new CustomMapper<A, B>() {
#Override
public void mapAtoB(A a, B b, MappingContext context) {
SomeObject someObject = new SomeObject();
someObject.setStringSomeObject(a.getStringA());
b.getSomeObjects().add(someObject);
}
}
)
.byDefault()
.register();
Orika will put invocation customMapper after resolving of field maps.
Generated mapper will looks like:
b.setOtherField(a.getOtherField());
if (customMapper != null) {
customMapper.map(source, destination); <-- Your mapper invocation
}
Use follow syntax for fields:
factory.classMap(A.class, B.class)
.field("stringA", "someObjects[0].stringSomeObject")
.byDefault()
.register();
Generated mapper will looks like:
if (source.getStringA() != null) {
if (((((java.util.List) destination.getSomeObjects()).size() <= 0 || ((List) destination.getSomeObjects()).get(0) == null))) {
((java.util.List) destination.getSomeObjects()).add(0, ((BoundMapperFacade) usedMapperFacades[0]).newObject(((String) source.getStringA()), mappingContext));
}
}
if (!(((java.lang.String) source.getStringA()) == null)) {
(((java.util.List) destination.getSomeObjects()).get(0)).setStringSomeObject(source.getStringA());
} else if (!(((java.util.List) destination.getSomeObjects()) == null) && !((((java.util.List) destination.getSomeObjects()).size() <= 0 || ((List) destination.getSomeObjects()).get(0) == null))) {
( ((java.util.List) destination.getSomeObjects()).get(0)).setStringSomeObject(null);
}
Also there was a bug in Orika to map from single property to property of collection using syntax .field("stringA", "elements{stringB}") ( Incorrect mapper code generated for mapping from a single property to property of collection element). Bug closed at 31 Dec 2016 here: Fix for bug
Basically, I marshall on one server into JSON, then send it to another server, where it should be unmarshalled. I use a response object called list wrapper, so that if there are any errors I can pass them along. With the JSON marshalled below by badgerfish(jettison), in a resteasy class, then returned to the other server, GSON will unmarshall to a listwrapper object, but the list inside is null. Any ideas?
Note: The list must remain generic because different objects may go into the list, though the list will always only have one type in it at a time.
Json
unmarshalling
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
Object List;
if (!JSON.equals("")) {
List = gson.fromJson(new BufferedReader(new StringReader(JSON)), ListWrapper.class);
}
Listwrapper type
#XmlRootElement(name = "ListWrapper")
public class ListWrapper {
private Vector<Object> objects;
private String status;
private int batch;
private ValidationException e;
public ListWrapper() {
this.setStatus("Success");
}
public ListWrapper(Vector<Object> list) {
this.setStatus("Success");
this.objects = list;
}
public ListWrapper(int x) {
this.setStatus("batch");
this.batch = x;
}
public Vector<Object> getList() {
return objects;
}
public void setList(Vector<Object> object) {
this.objects = object;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
#XmlJavaTypeAdapter(ThrowableAdapter.class)
public ValidationException getE() {
if (e != null) {
return e;
} else {
return null;
}
}
public void setE(ValidationException x) {
this.e = x;
}
public int getBatch() {
return batch;
}
public void setBatch(int batch) {
this.batch = batch;
}
}
You cannot deserialize using directly ListWrapper, you need a container class, this is why you list is empty. By the way, your list is not a list but a map instead (curly braces limit content of list).
I created a code that correctly parse your JSON and provides you with some simple functionality to extract data. Since you want to keep flexible the data you are passing between the servers, you will need strings to access to data.
Here is the code ready to copy and paste to try it by yourself. Keep in mind that accessor methods are based on structure you are showing in the example. I provided you in main 4 different kind of data you can extract from it. Let me know if you need more information about that.
package stackoverflow.questions.q19817221;
import java.util.*;
import com.google.gson.Gson;
public class Q19817221 {
public class ListWrapper {
private Map list;
private Map status;
private Map batch;
private Object extractValue(Map m) {
return m.get("$");
}
public Integer getBatch() {
return Integer.valueOf( (String) extractValue(batch));
}
public Object getValueFromList(String key) {
try {
Map m = (Map) list.get(key);
if (m != null)
return extractValue(m);
} catch (Exception e) {
return list.get(key);
}
return null;
}
public Object getValueFromList(String secondLevelKey, String key) {
Map secondLevelMap = (Map) list.get(secondLevelKey);
try {
Map m = (Map) secondLevelMap.get(key);
if (m != null)
return extractValue(m);
} catch (Exception e) {
return list.get(key);
}
return null;
}
}
public class Container {
public ListWrapper ListWrapper;
}
/**
* #param args
*/
public static void main(String[] args) {
String json = "{\"ListWrapper\":{\"batch\":{\"$\":\"0\"},\"list\":{\"#xmlns\":{\"xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},\"#xsi:type\":\"fidsUsers\",\"createdBy\":{\"$\":\"administrator\"},\"createdWhen\":{\"$\":\"2013-02-25T17:29:19-05:00\"},\"endDate\":{\"$\":\"2016-10-28T00:00:00-04:00\"}," +
"\"isDisabled\":{\"$\":\"N\"},\"previousPasswords\":{\"$\":\"HXQDa4WxTdBmZtvhMVTgnw==####zW6bdHkKdMN2p6CgRNjNHA==####Sim7JN3kaHoXnh3KUS2++Q==####Emz7zU0Wrm0lyb/K522O5A==##ZirxzRl28JqfjOzIaMzAog==\"}," +
"\"primaryKey\":{\"$\":\"David\"},\"pswdChgDate\":{\"$\":\"2013-07-12T08:27:46-04:00\"},\"pswdCount\":{\"$\":\"0\"},\"roleId\":{\"$\":\"Admin\"},\"roleIdFidsRoles\":{\"globalAccess\":{\"$\":\"Y\"},\"primaryKey\":{\"$\":\"Admin\"},\"roleDesc\":{\"$\":\"Administrator\"},\"roleId\":{\"$\":\"Admin\"}," +
"\"updatedBy\":{\"$\":\"David\"}},\"startDate\":{\"$\":\"1992-07-28T00:00:00-04:00\"},\"updatedBy\":{\"$\":\"David\"},\"updatedWhen\":{\"$\":\"2013-10-02T10:46:31-04:00\"},\"userId\":{\"$\":\"David\"},\"userName\":{\"$\":\"David3\"},\"userPassword\":{\"$\":\"HXQDa4WxTdBmZtvhMVTgnw==\"}},\"status\":{\"$\":\"Success\"}}}";
Container c = new Gson().fromJson(json, Container.class);
ListWrapper lw = c.ListWrapper;
System.out.println("batch:" + lw.getBatch());
System.out.println("createdBy:" + lw.getValueFromList("createdBy"));
System.out.println("#xsi:type: " + lw.getValueFromList("#xsi:type"));
System.out.println("roleIdFidsRoles\\primaryKey: " + lw.getValueFromList("roleIdFidsRoles", "primaryKey"));
}
}
This is execution result:
batch:0
createdBy:administrator
#xsi:type: fidsUsers
roleIdFidsRoles\primaryKey: Admin
By looking into your ListWrapper class and a json you have posted - you have a mismatch and that is probably why it failed to unmarshall.
For:
{"ListWrapper":{"batch":{"$":"0"},"list":{"#xmlns":{"xsi":"http:\/\/www.w3.org\/2001\/XMLSchema-instance"},"#xsi:type":"fidsUsers","createdBy":{"$":"administrator"},"createdWhen":{"$":"2013-02-25T17:29:19-05:00"},"endDate":{"$":"2016-10-28T00:00:00-04:00"},"isDisabled":{"$":"N"},"previousPasswords":{"$":"HXQDa4WxTdBmZtvhMVTgnw==####zW6bdHkKdMN2p6CgRNjNHA==####Sim7JN3kaHoXnh3KUS2++Q==####Emz7zU0Wrm0lyb\/K522O5A==##ZirxzRl28JqfjOzIaMzAog=="},"primaryKey":{"$":"David"},"pswdChgDate":{"$":"2013-07-12T08:27:46-04:00"},"pswdCount":{"$":"0"},"roleId":{"$":"Admin"},"roleIdFidsRoles":{"globalAccess":{"$":"Y"},"primaryKey":{"$":"Admin"},"roleDesc":{"$":"Administrator"},"roleId":{"$":"Admin"},"updatedBy":{"$":"David"}},"startDate":{"$":"1992-07-28T00:00:00-04:00"},"updatedBy":{"$":"David"},"updatedWhen":{"$":"2013-10-02T10:46:31-04:00"},"userId":{"$":"David"},"userName":{"$":"David3"},"userPassword":{"$":"HXQDa4WxTdBmZtvhMVTgnw=="}},"status":{"$":"Success"}}} to me it is a object ListWrapper that has 3 fields named: batch, list and status, where batch is an object with one field being a number, list is a map and status is an object having one string in it.
I am not familiar with badgerfish, having said that, in your ListWrapper try to change private Vector<Object> objects to private Map<String,Object> objects (and of course matching getters/setters) and see if that will work
The list must remain generic because different objects may go into
the list, though the list will always only have one type in it at a
time.
If you know what type is it before un-marshalling, then ONLY it can work through the use of TypeToken
Sample code :
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
Also, the definition will change to something like this :
public class ListWrapper {
private Vector<T> objects;
private String status;
private int batch;
}
or
public class ListWrapper {
private Vector<T extends someBaseObject> objects;
private String status;
private int batch;
}
depends on how exactly you decide to implement.
I was only aware of dynamic proxy being used for AOP.
However,it seems it can be used for lazy loading too.
The following example from an articles is intended to demonstrate that.
However I fail to understand how this is different from a normal accessor and what exactly is being 'lazily' loaded here?
Any help in understanding what the author intended to mean by lazy-loading is appreciated.
private Category tupleToObject(Serializable[] tuple) {
Category category = new Category((String)tuple[1],
(YearMonthDay) tuple[2]);
category.setId((Long) tuple[0]);
category.setParent(lazyGet((Long) tuple[3]));
return category;
}
protected CategoryItf lazyGet(Long id) {
if (id == null) {
return null;
}
return (CategoryItf)Proxy.newProxyInstance(
CategoryItf.class.getClassLoader(),
new Class[] { CategoryItf.class },
new LazyLoadedObject() {
protected Object loadObject() {
return get(id);
}
});
}
public abstract class LazyLoadedObject implements InvocationHandler {
private Object target;
public Object invoke(Object proxy,
Method method, Object[] args)
throws Throwable {
if (target == null) {
target = loadObject();
}
return method.invoke(target, args);
}
protected abstract Object loadObject();
}
How woul this be any different from the following:
private Category tupleToObject(Serializable[] tuple) {
Category category = new Category((String)tuple[1],
(YearMonthDay) tuple[2]);
category.setId((Long) tuple[0]);
category.setParent(get((Long) tuple[3]));
return category;
}
In both cases,the parent is created only when needed.
The following code snippet makes the implementation "lazy":
private Object target;
public Object invoke(Object proxy,
Method method, Object[] args)
throws Throwable {
if (target == null) {
target = loadObject();
}
You can see that no matter how many times you are calling this code you get the same object every time. So, practically it is singleton. However it is not created in the beginning of the program but only when it is needed first time. This is the meaning of "lazy" here.
Let me try to explain from how I understand the code:
In this code:
private Category tupleToObject(Serializable[] tuple) {
Category category = new Category((String)tuple[1],
(YearMonthDay) tuple[2]);
category.setId((Long) tuple[0]);
category.setParent(get((Long) tuple[3]));
return category;
}
the get() method will directly return the actual object, hence calling tupleToObject() will populate category parent with the actual object.
while in this code:
private Category tupleToObject(Serializable[] tuple) {
Category category = new Category((String)tuple[1],
(YearMonthDay) tuple[2]);
category.setId((Long) tuple[0]);
category.setParent(lazyGet((Long) tuple[3]));
return category;
}
the lazyGet() method actually returns a proxy (NOT the actual object). First method call on the proxy will actually trigger the loading of the object. The proxy here is used to delay the actual retrieval of the actual object until its actually needed i.e. lazy loading.
Hope this answers your question.