3

I cannot get JPA query to cast an AbstractAddress entity to its subclass AddressStd using the builder .treat() method.

builder.equal( builder.treat(contracts.get("address"), 
                             AddressStd.class
                            ).get("houseNo"), std.getHouseNo()); 

Background

I’ve used JPA 2.1 (with Hibernate 5.0.2 FINAL) to map a legacy database, where a Customer has 0:n Contacts, and each Contact has an AbstractAddress of subtype AddressStd or AddressPOB (PO box). The Contact has additional properties, but for the sake of simplicity they are not shown below.

Domain Model

@Entity
@Table(name = "Customer")
public class Customer

    @Id @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_CUSTOMER_ID")
    @SequenceGenerator(name="SEQ_CUSTOMER_ID", sequenceName="SEQ_CUSTOMER_ID")
    private Long   id;

    @OneToMany(fetch = FetchType.LAZY, mappedBy="Customer", orphanRemoval=true, cascade=CascadeType.ALL)
    private Set<Contact> contacts;   
    ...
}


@Entity
@Table(name = "CONTACT")
public class Contact {

    @Id @Column(name = "CONTACT_ID", nullable = false)
    private Long  id;

    @Basic @Column(name = "ROLE", length = 30)
    @Convert(converter = ProfileTypeConverter.class)
    private ContactType type;

    @OneToOne(fetch = FetchType.LAZY, mappedBy="contact", orphanRemoval=true, cascade=CascadeType.ALL)
    private AbstractAddress address;
}    

@Entity
@Table(name = "ADDRESS")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula( "case when PO_BOX_NUMBER is null then 'ADDRESS_STD' ELSE 'ADDRESS_POB' end" )
public abstract class AbstractAddress {

    @Id @Column(name = "ID", nullable = false, precision = 0)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_ADDRESS")
    @SequenceGenerator(name = "SEQ_ADDRESS", sequenceName = "SEQ_ADDRESS")
    private Long id;

    @OneToOne
    @PrimaryKeyJoinColumn(name = "CONTACT_ID", referencedColumnName = "ID")
    private Contact contact;

    @Basic @Column(name = "POSTAL_CODE", length = 10)
    private String postcode;

    @Basic @Column(name = "CITY", length = 40)
    private String city;

    @Basic @Column(name = "COUNTRY", length = 2)
    @Convert(converter = CountryConverter.class)
    private Country country;

    @Basic(optional=false)
    @Column(name = "LNG_CODE", length = 2)
    @Convert(converter = LanguageConverter.class)
    private Language language;

    @Basic(optional=false)
    @Column(name = "STATUS", length = 1)
    @Convert(converter = BooleanActiveConverter.class)
    private Boolean isActive = true;
}

@Entity
@DiscriminatorValue("ADDRESS_STD")
public class AddressStd extends AbstractAddress {
    @Basic @Column(name = "HOUSE_NUMBER", length = 8)
    private String houseNo;

    @Basic @Column(name = "STREET_NAME", length = 30)
    private String street;

    ...
} 

@Entity
@DiscriminatorValue("ADDRESS_POB")
public class AddressPOB extends AbstractAddress {
    @Basic
    @Column(name = "PO_BOX_NUMBER", length = 8)
    private String poBoxNo; 
    ... 
}

JPA query

The build query works to retrieve the AbstractAddress properties (city, postcode, country, language, isActive), but I cannot get it to retrieve the subclass properties of AddressStd (houseNo, street) and AddressPOB (poBoxNo) respectively, as the statements like

builder.equal(builder.treat(contracts.get("address"), 
                             AddressStd.class
                            ).get("houseNo"), std.getHouseNo());

throws a runtime exception

java.lang.IllegalArgumentException: Unable to locate Attribute  with the 
given name [houseNo] on this ManagedType [com.compX.appY.domain.contacts.AbstractAddress]

I've used builder .treat() method before to cast types and it works fine. But in this case .treat() fails to cast the address to an AddressStd.class? Do any of you JPA guru's have an idea why, Many thanks, DaveT.

See CriteriaQuery builder code here

private CriteriaQuery<Customer> createCriteria(CustomerSearchCriteria search) {

    CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
    CriteriaQuery<Customer> criteria = builder.createQuery(Customer.class);
    Root<Customer> root = criteria.from(Customer.class);

    List<Predicate> where = new ArrayList<Predicate>();

    ....

    if( search.getContactAddress() != null ) {
        AbstractAddress a = search.getContactAddress();

        Join<Customer, Contact> contracts = root.join("contacts"); // Customer.contacts

        if(a instanceof AddressStd){
            AddressStd std = (AddressStd)a;
            if(std.getHouseNo() != null) where.add(builder.equal(builder.treat(contracts.get("address"), AddressStd.class).get("houseNo"), std.getHouseNo()));
            if(std.getStreet()  != null) where.add(builder.equal(builder.treat(contracts.get("address"), AddressStd.class).get("street"),  std.getStreet()));
        }else if(a instanceof AddressPOB){
            AddressPOB pob = (AddressPOB)a;
            if(pob.getPoBoxNo() != null) where.add(builder.equal(builder.treat(contracts.get("address"), AddressPOB.class).get("poBoxNo"), pob.getPoBoxNo()));
        }

        if(a.getCity()     != null) where.add(builder.equal(contracts.get("address").get("city"), a.getCity()));
        if(a.getPostcode() != null) where.add(builder.equal(contracts.get("address").get("postcode"), a.getPostcode()));
        if(a.getCountry()  != null) where.add(builder.equal(contracts.get("address").get("country"), a.getCountry()));

        if(a.getLanguage() != null) where.add(builder.equal(contracts.get("address").get("language"), a.getLanguage()));
        if(a.isActive()    != null) where.add(builder.equal(contracts.get("address").get("isActive"), a.isActive()));
    }

    criteria.where( where.toArray(new Predicate[where.size()]) );

    return criteria;
} 
DaveT
  • 31
  • 4
  • Many thanks Neil. But I used builder.treat(contracts.get("address"), AddressStd.class) to treat the abstract address as subclass AddressStd. How do you suggest I should do it? An example would be much appreciated, DaveT – DaveT Nov 12 '15 at 15:52
  • If Hibernate works for TREAT normally, yet when you try to access a field of the TREATed object fails then raise a bug with them. The code is correct IMHO – Neil Stockton Nov 13 '15 at 08:06

0 Answers0