Search code examples
javahibernatehibernate-criteria

Hibernate inheritance with hibernate criteria (TABLE_PER_CLASS)


I have a Cell with list of widgets. Widgets is an abstract entity and it is used to store common attributes with all future Entities like button, search, criteria etc.. Please do not think about ClassNamed interface - it has no impact on my result - I checked it twice.

Cell (with list of Widgets):

@Entity
@Table(name = "DASHBOARD_CELL")
public class DashboardCell {

    private Integer id;
    private Integer rowPosition;
    private Integer columnPosition;
    private Integer columnWeight;
    private DashboardBox dashboardBox;
    private List<DashboardWidget> dashboardWidgets;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "ROW_POSITION")
    public Integer getRowPosition() {
        return rowPosition;
    }

    public void setRowPosition(Integer rowPosition) {
        this.rowPosition = rowPosition;
    }

    @Column(name = "COLUMN_POSITION")
    public Integer getColumnPosition() {
        return columnPosition;
    }

    public void setColumnPosition(Integer columnPosition) {
        this.columnPosition = columnPosition;
    }

    @Column(name = "COLUMN_WEIGHT")
    public Integer getColumnWeight() {
        return columnWeight;
    }

    public void setColumnWeight(Integer columnWeight) {
        this.columnWeight = columnWeight;
    }

    @ManyToOne(optional = false, fetch=FetchType.LAZY)
    @JoinColumn(name="DASHBOARD_BOX_ID")
    @JsonBackReference
    public DashboardBox getDashboardBox() {
        return dashboardBox;
    }

    public void setDashboardBox(DashboardBox dashboardBox) {
        this.dashboardBox = dashboardBox;
    }

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "dashboardCell", fetch=FetchType.EAGER)
    @JsonManagedReference
    public List<DashboardWidget> getDashboardWidgets() {
        return dashboardWidgets;
    }

    public void setDashboardWidgets(List<DashboardWidget> dashboardWidgets) {
        this.dashboardWidgets = dashboardWidgets;
    }

}

Widget:

@Entity
@Table(name = "DASHBOARD_WIDGET")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class DashboardWidget implements ClassNamed{

    private Integer id;
    private String title;
    private Integer backgroundColor;
    private Integer orderNumber;
    private DashboardCell dashboardCell;
    private String widgetType = getWidgetType();

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    @Column(name = "ID")
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "TITLE")
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @Column(name = "BACKGROUND_COLOR_VALUE")
    public Integer getBackgroundColor() {
        return backgroundColor;
    }

    public void setBackgroundColor(Integer backgroundColor) {
        this.backgroundColor = backgroundColor;
    }

    @Column(name = "ORDER_NUMBER")
    public Integer getOrderNumber() {
        return orderNumber;
    }

    public void setOrderNumber(Integer orderNumber) {
        this.orderNumber = orderNumber;
    }

    @ManyToOne(optional = false)
    @JoinColumn(name = "DASHBOARD_CELL_ID")
    @JsonBackReference
    public DashboardCell getDashboardCell() {
        return dashboardCell;
    }

    public void setDashboardCell(DashboardCell dashboardCell) {
        this.dashboardCell = dashboardCell;
    }

    public String getWidgetType() {
        return getClassName();
    }

    public void setWidgetType(String widgetType) {
        this.widgetType = widgetType;
    }
}

CriteriaWidget:

@Entity
@Table(name = "DASHBOARD_CRITERIA")
public class DashboardCriteria extends DashboardWidget {
    private Integer value;
    private Integer maximumValue;

    @Column(name = "ACTUAL_VALUE")
    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

    @Column(name = "MAX_VALUE")
    public Integer getMaximumValue() {
        return maximumValue;
    }

    public void setMaximumValue(Integer maximumValue) {
        this.maximumValue = maximumValue;
    }

    @Transient
    @Override
    public String getClassName() {
        return DashboardCriteria.class.getName();
    }

}

When I try to query all cells with widgets using following code:

sessionFactory.getCurrentSession().createCriteria(DashboardCell.class).list();

I got 4 records: 1 Cell with 4 Criterias, 1 (same cell) with 4 criterias, 1 (same cell) with 4 Criterias, 1 (same cell) with 4 Criterias. What I have in database is 1 cell with 4 DashBoardCriteria's (H2 database):

insert into DASHBOARD_CELL (ID, ROW_POSITION, COLUMN_POSITION, COLUMN_WEIGHT, DASHBOARD_BOX_ID) values (100, 0, 0, 12, 6);

insert into DASHBOARD_CRITERIA (ID, TITLE, BACKGROUND_COLOR_VALUE, ORDER_NUMBER, DASHBOARD_CELL_ID, ACTUAL_VALUE, MAX_VALUE) values (200, 'Registered', 16769408, 0, 100, 21, 30);
insert into DASHBOARD_CRITERIA (ID, TITLE, BACKGROUND_COLOR_VALUE, ORDER_NUMBER, DASHBOARD_CELL_ID, ACTUAL_VALUE, MAX_VALUE) values (201, 'Completed', 11723766, 1, 100, 22, 30);
insert into DASHBOARD_CRITERIA (ID, TITLE, BACKGROUND_COLOR_VALUE, ORDER_NUMBER, DASHBOARD_CELL_ID, ACTUAL_VALUE, MAX_VALUE) values (202, 'Approved', 11921353, 2, 100, 5, 30);
insert into DASHBOARD_CRITERIA (ID, TITLE, BACKGROUND_COLOR_VALUE, ORDER_NUMBER, DASHBOARD_CELL_ID, ACTUAL_VALUE, MAX_VALUE) values (203, 'Cancelled', 16755884, 3, 100, 17, 30);

I am assuming this query gives me (number of cells * number of widgets). My question is why? I think hibernate should autojoin childs, shouldn't it? Am I doing something wrong or what might be a reason?

HQL query:

SELECT this_.ID AS ID1_1_1_,
       this_.COLUMN_POSITION AS COLUMN_P2_1_1_,
       this_.COLUMN_WEIGHT AS COLUMN_W3_1_1_,
       this_.DASHBOARD_BOX_ID AS DASHBOAR5_1_1_,
       this_.ROW_POSITION AS ROW_POSI4_1_1_,
       dashboardw2_.DASHBOARD_CELL_ID AS DASHBOAR6_3_3_,
       dashboardw2_.ID AS ID1_3_3_,
       dashboardw2_.ID AS ID1_3_0_,
       dashboardw2_.BACKGROUND_COLOR_VALUE AS BACKGROU2_3_0_,
       dashboardw2_.DASHBOARD_CELL_ID AS DASHBOAR6_3_0_,
       dashboardw2_.ORDER_NUMBER AS ORDER_NU3_3_0_,
       dashboardw2_.TITLE AS TITLE4_3_0_,
       dashboardw2_.widgetType AS widgetTy5_3_0_,
       dashboardw2_.MAX_VALUE AS MAX_VALU1_2_0_,
       dashboardw2_.ACTUAL_VALUE AS ACTUAL_V2_2_0_,
       dashboardw2_.clazz_ AS clazz_0_
FROM DASHBOARD_CELL this_
LEFT OUTER JOIN
  (SELECT ID,
          BACKGROUND_COLOR_VALUE,
          ORDER_NUMBER,
          TITLE,
          widgetType,
          DASHBOARD_CELL_ID,
          MAX_VALUE,
          ACTUAL_VALUE,
          1 AS clazz_
   FROM DASHBOARD_CRITERIA) dashboardw2_ ON this_.ID=dashboardw2_.DASHBOARD_CELL_ID
WHERE this_.DASHBOARD_BOX_ID=?

I moved from H2 to postgresql.. I executed query and it seems like result is correct - I recive 4 widgets linked to one cell.. Might it be a bug in hibernate 5 that I have multiple same objects 4 times?


Solution

  • It works as designed. Just like in HQL/JPQL, where you would need to use distinct, in Criteria, you need

    criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);