Sunday, December 15, 2013

How to add Criterion to @OneToMany mapping in Hibernate

Hi All,

Yesterday I faced a problem with load some data based on the Criterion which I wrote to @OneToMany mapping. I Google all over the day but I couldn't come up with a proper solution. Finally I found an answer by my self. I thought of sharing with you in my blog.

Here is the problem explanation. Result which I want to get mapped with couple of Hibernate model classes but here I take 3 classes which is basically involve in add Criterion to the @OneToMany mapping.

One Assessment can have many Property and one Property can have many PropertyOwner.

Code snippet of model classes  as below. (These are not the complete model classes)

Assessment.java
@Entity
@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "tenantIdParam", type = "string"))
@Filters(@Filter(name = "tenantFilter", condition = "tenant_id = :tenantIdParam"))
@Table(name = "assessment")
public class Assessment implements Serializable {

 @Id
 @Column(name = "id")
 @GeneratedValue(strategy = GenerationType.AUTO)
 private Long id;
 @ManyToOne(fetch = FetchType.LAZY)
 @Cascade(value = org.hibernate.annotations.CascadeType.ALL)
 @JoinColumn(name = "property_id")
 private Property propertyAssessment;
 @ManyToOne(fetch = FetchType.LAZY)
 @JoinColumn(name = "elg_activity_id", nullable = false)
 private ElgActivity elgActivity;
 @Column(name = "tenant_id", nullable = false)
 private String tenantId;
 // other attributes
 // getters and setters
} 

Property.java
@Entity
@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "tenantIdParam", type = "string"))
@Filters(@Filter(name = "tenantFilter", condition = "tenant_id = :tenantIdParam"))
@Table(name = "property")
public class Property implements Serializable {

 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 @Column(name = "id", unique = true, nullable = false)
 private Long id;
 @OneToMany(fetch = FetchType.LAZY, mappedBy = "propertyAssessment")
 @Cascade(value = CascadeType.ALL)
 private List<assessment> assessments = new ArrayList<assessment>(0);
 @OneToMany(fetch = FetchType.LAZY, mappedBy = "property")
 @Cascade(value = CascadeType.ALL)
 @LazyCollection(LazyCollectionOption.FALSE)
 private List<propertyowner> propertyOwners = new ArrayList<propertyowner>(0);
 @Column(name = "tenant_id", nullable = false)
 private String tenantId;
 // other attributes
 // getters and setters
}

PropertyOwner.java
@Entity
@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "tenantIdParam", type = "string"))
@Filters(@Filter(name = "tenantFilter", condition = "tenant_id = :tenantIdParam"))
@Table(name = "property_owner")
public class PropertyOwner implements Serializable {

 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 @Column(name = "id", unique = true, nullable = false)
 private Long id;
 @OneToOne(fetch = FetchType.EAGER)
 @Cascade(value = CascadeType.ALL)
 @JoinColumn(name = "person_id")
 private Person person;
 @ManyToOne(fetch = FetchType.LAZY)
 @Cascade(value = CascadeType.ALL)
 @JoinColumn(name = "property_id")
 private Property property = new Property();
 @Column(name = "status")
 private String status;
 @Column(name = "tenant_id", nullable = false)
 private String tenantId;
 // other attributes
 // getters and setters
}

My query is to get the PropertyOwner by their status. PropertyOwner status can be either ACTIVE or INACTIVE. My requirement is to get all the ACTIVE PropertyOwner. Here is the method I wrote to get the result.

@Override
public Assessment getAssessmentById(Long assessmentId, Object tenantId) throws HibernateException {
Session session = getSession(tenantId);
Assessment result = (Assessment) session.createCriteria(Assessment.class)
.createAlias("propertyAssessment", "propertyAssessment")
.createAlias("propertyAssessment.propertyOwners", "propertyOwner", JoinType.LEFT_OUTER_JOIN)
.add(Restrictions.eq("id", assessmentId))
.add(Restrictions.ne("propertyOwner.status", ScandiumKeyBox.INACTIVE))
.uniqueResult();
return result;
}

The tricky point here is the JoinType.LEFT_OUTER_JOIN. By default if you wrote a createAlias and lazy load object then default JoinType would be JoinType.INNER_JOIN. This will load all matching objects regardless of your Criterion to that object. Here I overcome that problem with LEFT_OUTER_JOIN. Other thing is here I load the exact result by matching the id (primary key).

If you want to load list of objects, make sure to set below line to the criteria.
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) 

Hope this would be helpful you to get the exact result in @OneToMany mapping by adding Criterion.

Thank you.