i have the following model (a very simplified version of a much bigger model)
public enum EntityType {
DOG, HAMMER
}
#Entity
public class Entity {
#EmbeddedId
private EntityKey key;
private String someProp;
}
#Embeddable
public class EntityKey implements Serializable{
private Long serial;
#Enumerated(EnumType.STRING)
private EntityType type;
}
#Entity
#IdClass(MetricKey.class)
public class Metric {
#Id
private EntityKey entityKey;
#Id
private Long timestamp;
private Double sampledValue;
}
public class MetricKey implements Serializable {
private EntityKey entityKey;
private Long timestamp;
}
Entity in reality is an abstract class at the root of a large hierarchy. Metric represents some data sampled on an entity at a particular point in time. Entity has a composite key consisting of type and serial number, and metric should have a composite key consisting of the entity key + timestamp.
the above code gets me this exception from hibernate:
org.hibernate.HibernateException: Unable to instantiate default tuplizer [org.hibernate.tuple.component.PojoComponentTuplizer]
...
Caused by: java.lang.reflect.InvocationTargetException
...
Caused by: org.hibernate.PropertyNotFoundException: field [serial] not found on Metric
at org.hibernate.property.DirectPropertyAccessor.getField(DirectPropertyAccessor.java:166)
... 45 more
it looks like it spotted the #Embeddable on EntityKey and is trying to break it apart here as well.
i know i can work around this by making MetricKey #Embeddable and rewriting some code:
#Entity
public class Metric {
#EmbeddedId
private MetricKey key;
private Double sampledValue;
}
#Embeddable
public class MetricKey implements Serializable{
private EntityKey entityKey;
private Long timeStamp;
}
but i'd much prefer the original design, since EntityKey is widely used across the system and having to convert back and forth between EntityKey and MetricKey would annoy developers.
what am i doing wrong?
The only working solution for I can propose you is changing the implementation of Metric to
#Entity
#IdClass(MetricKey.class)
public class Metric {
#Id
#ManyToOne
private Entity entity;
#Id
private Long timestamp;
private Double sampledValue;
}
You can also consult 2.4.1.1 Specification of Derived Identities of the JPA 2.1 spec.
If an Id attribute in the entity is a many-to-one or one-to-one relationship to a parent
entity, the corresponding attribute in the id class must be of the same Java type as the
id class or embedded id of the parent entity (if the parent entity has a composite primary
key) or the type of the Id attribute of the parent entity (if the parent entity has a
simple primary key).
My solution was tested against Hibernate 4.3.6
Related
I'm trying to do a JPA mapping for an existing database. I can't really change the existing structure.
I managed to make it works, but Intellij is telling me that some column doesn't exist even if it works. So I don't know if there's a better way to do this or if it's Intellij that doesn't support all the use cases.
I simplified my mapping and table for the question.
The 2 tables with primary composite keys are:
Table_A
some_id_a
some_seq_a
Table B
some_id_a
some_seq_a
some_seq_b
And my mapping is:
#Data
#Entity(name="Table_A")
public class TableA {
#EmbeddedId
private Key key;
#OneToMany
#JoinColumn(name = "someIdA")
#JoinColumn(name = "someSeqA")
private List<TableB> tableBs;
#Data
#Embeddable
public static final class Key implements Serializable {
private String someIdA;
private long someSeqA;
}
}
#Data
#Entity(name="Table_B")
public class TableB {
#EmbeddedId
private Key key;
#Data
#Embeddable
public static final class Key implements Serializable {
private String someIdA;
private long someSeqA;
private long someSeqB;
}
}
So like I said it works but I have an error in Intellij saying that the #JoinColumn(name ="someIdA") #JoinColumn(name = "someSeqA") don't exist and is expecting something like #JoinColumn(name ="some_id_a") #JoinColumn(name = "some_seq_a").
Using it the way Intellij is telling me, JPA has en error that says: Table [table_b] contains physical column name [some_id_a] referred to by multiple logical column names: [some_id_a], [someIdA].
My mapping is ok despite Intellij but is there's a better alternative ?
Thanks
You can use a "derived identity" and map your classes like this:
#Data
#Entity(name="Table_A")
public class TableA {
#EmbeddedId
private Key key;
#OneToMany(mappedBy = "tableA")
private List<TableB> tableBs;
#Data
#Embeddable
public static final class Key implements Serializable {
private String someIdA;
private long someSeqA;
}
}
#Data
#Entity(name="Table_B")
public class TableB {
#EmbeddedId
private Key key;
#MapsId("tableAKey") // maps tableAKey attribute of embedded id
#JoinColumns({
#JoinColumn(name="some_id_a", referencedColumnName="some_id_a"),
#JoinColumn(name="some_seq_a", referencedColumnName="some_seq_a")
})
#ManyToOne
private TableA tableA;
#Data
#Embeddable
public static final class Key implements Serializable {
private TableA.Key tableAKey; // corresponds to PK type of TableA
private long someSeqB;
}
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
I have a parent entity like so:
#Entity
public class Parent implements Serializable {
#Id
private String id;
#Convert(converter = ChildConverter.class)
private Collection<Child> children;
...
}
And the child entity is defined like so:
#Entity
public class Child implements Serializable {
#Id
private String id;
...
}
However, I want my Child entity to be saved not in its own table but as a (JSON) column in the Parent table. The reason I want to do this is because the Child entity contains little data and I feel as if it does not warrant its own table. Is this possible for JPA?
Update:
In the end, I've decided to simply not annotate the Child class with #Entity. But the trouble this brings is that I have to enforce entity constraints without JPA's help.
You still need a table to store the children of parents, because you use a Collection in the Parent class.
You may use #Embeddable annotation on Child class and #ElementCollection and #CollectionTable on children Collection in the Parent class.
#Embeddable
public class Child implements Serializable {
#Id
private String id;
...
}
#Entity
public class Parent implements Serializable {
#Id
private String id;
#Convert(converter = ChildConverter.class)
#ElementCollection
#CollectionTable(
name="CHILD",
joinColumns=#JoinColumn(name="PARENT_ID")
private Collection<Child> children;
...
}
I have an abstract parent class with two child classes, where I want the child classes to have their own tables. I also have another class that has a relationship to the parent class:
// Class that has the mapping to the abstract class
#Entity
#Table(name="telephone_numbers")
public class TelephoneNumber implements Serializable {
#Id
#Column(name="number")
private String number;
#Column(name="originating_carrier")
private String originatingCarrier;
#OneToOne(mappedBy = "number")
private TelephoneNumberAssignment assignment;
... getters and setters ...
}
// Classes involved in inheritance
public abstract class TelephoneNumberAssignment implements Serializable {
#Id
#OneToOne
#JoinColumn(name="number")
private TelephoneNumber number;
... getters and setters ...
}
#Entity
#Table(name="telephone_numbers_fixed_line")
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class FixedLineNumberAssignment extends TelephoneNumberAssignment {
#Column(name="recorded")
private Boolean recorded;
public FixedLineNumberAssignment() {
}
... getters and setters ...
}
#Entity
#Table(name="telephone_numbers_mobile")
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class MobileNumberAssignment extends TelephoneNumberAssignment {
#ManyToOne
#JoinColumn(name = "customer_id")
private Customer customer;
public MobileNumberAssignment() {
}
... getters and setters ...
}
According to the answer to this question, in order for my JPA annotations in the superclass to carry over to the child class I need to use #MappedSuperclass. However, due to the requirement of the mapping to the superclass, a following exception occurs:
Caused by: org.hibernate.AnnotationException: Unknown mappedBy in: com.vtsl.domain.numbering.TelephoneNumber.assignment, referenced property unknown: com.vtsl.domain.numbering.TelephoneNumberAssignment.number
According to the answer to this question I could use TABLE_PER_CLASS to solve this issue. However, if I do that my superclass JPA annotations don't seem to carry over; if I perform JPQL query
return entityManager.createQuery("SELECT mob FROM MobileNumberAssignment as mob INNER JOIN FETCH mob.number", MobileNumberAssignment.class).getResultList();
the results returned do not have their number field populated (upon further inspection I found that hibernate does not detect the number property when trying to resolve the properties on the result object). However, when I perform the following:
//Result set has 1 object
entityManager
.createQuery("SELECT mob FROM MobileNumberAssignment as mob INNER JOIN FETCH mob.number WHERE mob.number.number = :number", MobileNumberAssignment.class)
.setParameter("number", "number that exists")
.getResultList()
//Result set has 0 object
entityManager
.createQuery("SELECT mob FROM MobileNumberAssignment as mob INNER JOIN FETCH mob.number WHERE mob.number.number = :number", MobileNumberAssignment.class)
.setParameter("number", "number that does not exist")
.getResultList()
the results seem to indicate that the parameter got successfully resolved after all.
Why is it that the number property does not get populated?
It appears I have drawn all the wrong conclusions from the symptoms. The issue was not at all with the inheritance specification, but rather with the specification of the mapped primary key. According to every resource I found
#Id
#OneToOne
#JoinColumn(name="number")
private TelephoneNumber number;
should work just fine, however it seems that in the version of hibernate I am using (4.10) it does not. Changing the mapping to use #MapsId resolved the issues I was observing, making the new (and now working) TelephoneNumberAssignment to look like:
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class TelephoneNumberAssignment implements Serializable {
#Id String numberString;
#MapsId
#OneToOne
#JoinColumn(name="number")
private TelephoneNumber number;
... getters and setters ...
}
Am I doing something wrong or is this not supported in JPA2/eclipselink, let me explain by code;
#Embeddable
public class MemberID implements Serializable {
private String e_mail;
private String password;
//...no-arg constructor, getter and setter
the entity below uses MemberID as composite key
#Entity
#Table(name="MEMBER_DETAILS")
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="GROUPNAME", discriminatorType=DiscriminatorType.STRING, length=20)
public class Member_Details implements Serializable {
#EmbeddedId
private MemberID memberIdentity;
...other code
the entity below extends Member_Details and therefore inherits its key
#Entity
#Table(name="INDIVIDUAL_USER")
#DiscriminatorValue("INDIVIDUAL_USER")
public class Individual_User extends Member_Details implements Serializable {
#OneToMany(mappedBy="userinfo", fetch=FetchType.EAGER)
private List<UserComment> userComments = new ArrayList<UserComment>();
... other code
the following is a composite key that contains MemberID as part of it.
#Embeddable
public class CommentID implements Serializable {
private MemberID memberId;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="TIME_POSTED")
private Date timeOfComment;
...other code
the entity below uses CommentID as its composite key. I want it to be dependent on the entity Individual_User, and therefore use a derived id.That is why MemberID is part of its composite key.
#Entity
#Table(name="USER_COMMENTS")
public class UserComment implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private CommentID commentIdentity;
#MapsId("memberId")
#ManyToOne
#JoinColumns({
#JoinColumn(name="E_MAIL", referencedColumnName="E_MAIL"),
#JoinColumn(name="PASSWORD", referencedColumnName="PASSWORD")
})
private Individual_User userinfo;
...other code
The problem comes when I try to deploy, the following exception is thrown:
Caused by: Exception [EclipseLink-7321] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The field [MEMBER_DETAILS.PASSWORD] from the derived id mapping [userinfo] from class [kariro.semaplace.talk.entities.UserComment] is an invalid id field from the reference class [kariro.semaplace.registration.entity.Individual_User]. Ensure there is a corresponding id mapping to that field.
But when I change the #ManyToOne relationship from UserComment to reffer to type Member_Details instead of its subtype Individual_User, it works with no problems, but I am afraid this will bring in more problems later, or compromise the functioning of the app.
I really don't know whether eclipselink does not allow inherited ids to be used as derived ids or am doing something wrong. someone please help me out.
I'm not sure why you are getting the error, but you could avoid the issue entirely by simplifying your entities.
The first thing that jumps out to me is the fact that your user's PK includes password. Passwords are generally changeable, primary keys are not. Also, would you ever expect to have two distinct users with the same email but different passwords? Probably not. Drop MemberID and change Member_Details to have a simple #Id of just e_mail instead:
#Entity
#Table(name="MEMBER_DETAILS")
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="GROUPNAME", discriminatorType=DiscriminatorType.STRING, length=20)
public class Member_Details implements Serializable
{
#Id
private String e_mail;
private String password;
// ...
}
CommentID would change also:
#Embeddable
public class CommentID implements Serializable
{
private String e_mail;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="TIME_POSTED")
private Date timeOfComment;
}
And the #MapsId on UserComment would change to #MapsId("e_mail").
The changes above should be enough to avoid your issue, but if it were me, I would get rid of the composite primary key on UserComment as well. To simply things, you could give it a UUID and then put a unique constraint on e_mail and timeOfComment.
We are having a hard time persisting data in our Google App Engine project, we have the classes "Customer", "Reservation", and "Room".
Our goal is to map a relation between these, with a one-to-many relation from Customer to Reservation and a one-to-many relation from Room to the same Reservation.
The exception we get is:
Error in meta-data for no.hib.mod250.asm2.model.Reservation.id: Cannot have a java.lang.Long primary key and be a child object (owning field is no.hib.mod250.asm2.model.Customer.res).
Our code is as follows:
Customer.java
#PersistenceCapable(identityType=IdentityType.APPLICATION)
public class Customer implements Serializable {
#PrimaryKey
#Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
private Long id;
(...)
//an customer has one or more reservations.
#Persistent(mappedBy="customer")
private List <Reservation> res;
(...)
}
Room.java
#PersistenceCapable(identityType=IdentityType.APPLICATION)
public class Room implements Serializable {
#PrimaryKey
#Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
private Long id;
(...)
//a room has one or more reservations
#Persistent(mappedBy="room")
private List<Reservation> res;
#Persistent
private Hotel hotel;
(...)
}
Reservation.java
#PersistenceCapable(identityType=IdentityType.APPLICATION)
public class Reservation implements Serializable {
#PrimaryKey
#Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
private Long id;
(...)
#Persistent
private Room room;
#Persistent
private Customer customer;
(...)
}
As the message suggests, you can't use a long as your primary key if your entity is a child entity, which is true in this case. Instead, use a key or encoded string as your primary key - see here for details.
You should probably also read up on child objects and relationships.