2

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?

nilgun
  • 10,460
  • 4
  • 46
  • 57

1 Answers1

0

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.

Christian Beikov
  • 15,141
  • 2
  • 32
  • 58
  • I added the queries. You can also see the data. You were right that the problem was in the query that it does not bring the `dtype` column from the filter in the first place. Adding the `@DiscriminatorOptions(force = true)` on the `Filter` class didn't solve the issue. – nilgun May 31 '21 at 20:53
  • Can you try to rename the fields in the `AMap` and `BMap` class to e.g. `filterA` and `filterB`? – Christian Beikov Jun 01 '21 at 11:15
  • I had tried it (renaming the `filter` field and the getters/setters in the `AMap` and `BMap` subclasses) and it works. But then I can not use the abstract Map.getFilter method. – nilgun Jun 01 '21 at 12:34
  • You can just add that method in addition. The key point is that the fields have distinct names in the subtypes. – Christian Beikov Jun 01 '21 at 14:15
  • Changing the field name but keeping the getters and setters as getFilter/setFilter solved the problem. I also had to create two more columns afilter_id, bfilter_id and set them properly for each subtype. I still don't understand why the existing valid Java code would not work. Do you think it is a bug with Hibernate? – nilgun Jun 01 '21 at 21:24
  • It might be a bug, but this is a pretty complex matter because 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. It would be awesome if you could submit a bug report with a reproducer! – Christian Beikov Jun 02 '21 at 07:05
  • I will submit a bug report, thank you. Can you also edit your answer with the solution so that I can accept it. – nilgun Jun 02 '21 at 20:13