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;
}