javahibernatesingle-table-inheritance

Hibernate single table inheritance - base class nested as a property inside a subclass throws PropertyAccessException


I have the following class structure with 2 base classes (Filter and Map).


@Entity
public abstract class Filter {
}
@Entity
public class AFilter extends Filter {
}
@Entity
public class BFilter extends Filter {
}

@Entity
public abstract class Map {
    public abstract Filter getFilter();
}
@Entity
public class AMap extends Map {

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    private AFilter filter;

    @Override
    public AFilter getFilter() {
        return filter;
    }

    public void setFilter(AFilter filter) {
        this.filter = filter;
    }
    
}
@Entity
public class BMap extends Map {

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    private BFilter filter;

    @Override
    public BFilter getFilter() {
        return filter;
    }

    public void setFilter(BFilter filter) {
        this.filter = filter;
    }

}

When I query the database and the resultset has an instance of an AMap, I get the following exception:

@Override
public List<Map> getMaps() {
    Criteria criteria = dao.getCurrentSession().createCriteria(Map.class);
    return criteria.list();
}

org.hibernate.PropertyAccessException: Could not set field value [com.xxx.filter.BFilter@6197da84] value by reflection : [class com.xxx.map.AMap.filter] setter of com.xxx.map.AMap.filter

I tried to debug the Hibernate(v5.4.18) library a little bit, somehow it assumes Map.class's filter property is an instance of BFilter, rather than a dynamic subclass based on the Map subclass type.

This is the query generated by Hibernate (edited to remove extra fields and join tables):

select this_.id as id2_103_4_, this_.name as name15_103_4_, this_.status as status16_103_4_, this_.filter_id as filter_25_103_4_, this_.DTYPE as dtype1_103_4_, claimfilte5_.id as id2_89_3_, claimfilte5_.companyId as companyi3_89_3_, claimfilte5_.tableName as tablenam5_89_3_, claimfilte5_.zoomLevel as zoomleve6_89_3_ from public.Map this_ left outer join  public.Filter claimfilte5_ on this_.filter_id=claimfilte5_.id

Looking at the query, Hibernate does not select the dtype column for the filter table. => Causing the problem.

This is the result of the query with psql:

id2_103_4_ | name15_103_4_ | status16_103_4_ | filter_25_103_4_ | dtype1_103_4_ | id2_89_3_ | companyi3_89_3_ | tablenam5_89_3_ | zoomleve6_89_3_
------------+---------------+-----------------+------------------+---------------+-----------+-----------------+-----------------+-----------------
  245921700 | 123123        | t               |        245921702 | BMap          | 245921702 |              16 | B               |
  250077365 | Test2         | t               |        250077367 | BMap          | 250077367 |               4 | B               |
  250365744 | Test          | t               |        250365746 | BMap          | 250365746 |               0 | B               |
  250367720 | test3         | f               |        250367722 | BMap          | 250367722 |               0 | B               |
  254371277 | gdal new test | t               |        254371279 | BMap          | 254371279 |               0 | B               |
  254371748 | test4         | t               |        254371750 | AMap          | 254371750 |               0 | A               |
(6 rows)

When I add the dtype column manually (to show that the dtype column is set properly on the filter table):

id2_103_4_ | name15_103_4_ | status16_103_4_ | filter_25_103_4_ | dtype1_103_4_ | id2_89_3_ | companyi3_89_3_ | tablenam5_89_3_ | zoomleve6_89_3_ |    dtype
------------+---------------+-----------------+------------------+---------------+-----------+-----------------+-----------------+-----------------+--------------
  245921700 | 123123        | t               |        245921702 | BMap          | 245921702 |              16 | B               |                 | BFilter
  250077365 | Test1         | t               |        250077367 | BMap          | 250077367 |               4 | B               |                 | BFilter
  250365744 | Test          | t               |        250365746 | BMap          | 250365746 |               0 | B               |                 | BFilter
  250367720 | test3         | f               |        250367722 | BMap          | 250367722 |               0 | B               |                 | BFilter
  254371277 | gdal new test | t               |        254371279 | BMap          | 254371279 |               0 | B               |                 | BFilter
  254371748 | test4         | t               |        254371750 | AMap          | 254371750 |               0 | A               |                 | AFilter
(6 rows)

I can query the database without any problems if I create a criteria using subclasses: dao.getCurrentSession().createCriteria(AMap.class) or dao.getCurrentSession().createCriteria(BMap.class)

but that's not what I want.

How can I get Hibernate to recognize the correct subclass?


Solution

  • Please also show the queries that are generated. I guess that your data might be messed up i.e. you have a AMap that refers to a BFilter rather than an AFilter. Maybe you need to force the use of discriminators by annotating @DiscriminatorOptions(force = true) on Filter.

    UPDATE:

    The key point is that the fields have distinct names in the subtypes. Hibernate supports implicit downcasts i.e. it would be possible to use select m.filter from Map m and that would resolve to the downcasted association. Since there are multiple possible downcasts that have that property, there is a conflict. I actually implemented support for this part in Hibernate, but I guess that the discriminator is simply missing in that special case.