Incorrect metamodel for basic collections
Description
Attachments
- 06 Mar 2018, 09:17 AM
causes
is followed up by
Activity
Yanming Zhou (quaff@github) September 13, 2021 at 6:43 AM
It should be reopen since reverted by https://github.com/vladmihalcea/hibernate-orm/commit/df6d44f646d05f0fa3460887d0d05e9717031209
Vlad Mihalcea February 21, 2020 at 7:52 AMEdited
@Chris Cranford You are right that the spec does not cover this issue, but the spec does not take into consideration the use case when you’d want to map a List
, Set
, or Map
to a JSON column in the database or when you want to map a List
entity attribute to a PostgreSQL ARRAY column type.
Currently, this issue affects the Hibernate Types project as, even if the basic attribute is a List
, it’s still a basic attribute, not a parent-child association that can be used in a JOIN.
Here’s the Hibernate Type issue for more context about the problem I’m talking about.
I will provide a PR to address this issue, and I’d appreciate it if you take a look at it.
Chris Cranford May 22, 2018 at 4:12 PM
I just added a note to the PR related to this fix and I'll amend this JIRA with the same:
I'm not convinced that the code added for this is necessarily spec compliant.
In JPA 2.1 section 3.8:
The attribute conversion facility allows the developer to specify methods to convert between the entity attribute representation and the database representation for attributes of basic types.
So when you have an entity which has a collection of basic types such as:
@Convert(converter = StringToListConverter.class)
private List<String> values;
The attribute persistent type here is a `List<String>` regardless of the fact that it is stored in the database a single `String` column data type.
Also from the spec in section 6.2.1.1:
For every persistent collection-valued attribute z declared by class X, where the element type of z is Z, the metamodel class must contain a declaration as follows:
if the collection type of z is java.util.List,
then public static volatile ListAttribute<X, Z> z;
So I don't agree that this should be represented as a SingularAttribute as this PR does. I'm inclined to revert this fix in order to address HHH-12581.
Former user May 7, 2018 at 11:22 AM
The fix applied for this issue causes a regression when @ElementCollection and @CollectionTable is used. The following mapping taken from an entity used to work until Hibernate 5.2.16. It causes a NPE for criteria queries starting from 5.2.17.
@ElementCollection
@CollectionTable(
name = "doc_reader",
joinColumns = @JoinColumn(name = "doc_id", nullable = false), foreignKey = @ForeignKey(CONSTRAINT)
)
@Column(name = "reader", nullable = false)
@Convert(converter = RoleConverter.class)
private Set<Role> readers;
// Role is an interface whose instances are represented in the database by names (strings), converted to/from by RoleConverter.
Until 5.2.16, the JPA meta model class generated for this entity contains a SetAttribute for `readers`:
public static volatile SetAttribute<Doc, Role> readers;
Using 5.2.17, this becomes a SingularAttribute instead, which is obviously wrong (and it seems that @ElementCollection is not taken into account).
public static volatile SingularAttribute<Doc, Set<Role>> readers;
As a result, the following code then throws a NPE when creating a fetch join (last line). Debugging showed that `Doc_.readers` is null.
CriteriaQuery<Doc> q = b.createQuery(Doc.class);
Root<Doc> docR = q.from(Doc.class);
docR.fetch(Doc_.readers);
Vlad Mihalcea March 29, 2018 at 2:51 PM
Applied PR upstream.
we have
@Entity public class Goods { @Id private Long id; @OneToMany private List<Product> productList; @Convert(converter = StringToListConverter.class) private List<String> tags; }
which create Goods_ after compile:
@StaticMetamodel(Goods.class) public abstract class Goods_ { public static volatile SingularAttribute<Goods, Long> id; public static volatile ListAttribute<Goods, Product> productList; public static volatile ListAttribute<Goods, String> tags; }
when we use Goods_.productList to create a path
root.get(Goods_.tags)
get the NullPointerException error:
java.lang.NullPointerException at org.eclipse.persistence.internal.jpa.metamodel.proxy.AttributeProxyImpl.getName(AttributeProxyImpl.java:73) at org.eclipse.persistence.internal.jpa.querydef.FromImpl.get(FromImpl.java:285)
but if we use root.get("tags") ,it runs correct.
In fact, Goods.tags has no reationship with other entity, we should treat it as a basic attribute,and get this:
public static volatile SingularAttribute<Goods, List<String>> tags;