JPA / Hibernate OneToMany Mapping, using a composite PrimaryKey - jpa

I'm currently struggling with the right mapping annotations for a scenario using a composite Primary Key Class. First, what I am trying to achieve in words:
I have 2 classes: group and FieldAccessRule. A Group can have many FieldAccessRules, while a FieldAccessRule only has ONE Group assigned. Modling this is not a problem so far (simplified):
public class Group{
...
#OneToMany(mappedBy = "group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
and for the FieldAccessRule:
public class FieldAccessRule {
...
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
...
}
Now, I decided to use a Composite PK for the FieldAccessRule, because a Rule should be unique for ONE Group and ONE Field. It looks like this:
#Embeddable
public class FieldAccessRulePK implements Serializable{
private String fieldKey;
private Group group;
...
}
And ofc. the FieldAccessRule needs to change to
public class FieldAccessRule {
...
#EmbeddedId
private FieldAccessRulePK fieldAccessRulePK;
...
}
How do I create the right Mapping for the FieldAccessRuleSet of Group now?
Doing it like this, I get :
In attribute 'fieldAccessRules', the "mapped by" value 'group' cannot be resolved to an >attribute on the target entity.
Whats the right way of creating the mapping from Group to A PART of the PrimaryKey?
Edit:
I know found out, that using
public class Group{
...
#OneToMany(mappedBy = "fieldAccessRolePK.group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
is EXACTLY working as expected. It compiles fine, it creates the DB fine and after loading a group with predefined Roles, they are available as expected.
However, Eclipse still says
In attribute 'fieldAccessRules', the "mapped by" value 'fieldAccessRulePK.group' cannot be resolved to an attribute on the target entity.
Im not sure, if it's good to ignore Error and "assume" everythings fine... (I found a post, where it has been said, that a mapping of the pattern attr1.attr2 is supported by Hibernate but not JPA-confirm.)

In your code, EntityManager cannot resolve mappedBy attribute fieldAccessRulePK.group.
Reason
EntityManager assume that FieldAccessRule entity have an attribute name with fieldAccessRulePK.group during the FieldInjection.
According to Java Naming Variables Rule, you cannot name fieldAccessRulePK.group by using characters dot > '.'
Java Naming Variables Rule Reference
All variable names must begin with a letter of the alphabet, an underscore ( _ ), or a dollar sign ($). The rest of the characters may be any of those previously mentioned plus the digits 0-9.
The convention is to always use a letter of the alphabet. The dollar sign and the underscore are discouraged.
One more thing:
Don't use group instance in your FieldAccessRulePK embeddable class. For more reference here.
Try as below :
#Embeddable
public class FieldAccessRulePK implements Serializable{
#Column(name = "FIELD_KEY")
private String fieldKey;
#Column(name = "GROUP_ID")
private String groupId;
}
public class FieldAccessRule {
#EmbeddedId
private FieldAccessRulePK id;
#ManyToOne
#JoinColumn(name = "GROUP_ID", referencedColumnName = "ID")
private Group group;
}
public class Group{
#OneToMany(mappedBy = "group")
private Set<FieldAccessRule> fieldAccessRules;
}

I've fixed a similar problem by modifying the mappedBy attribute of the parent class (using dotted notation)
public class Group{
...
#OneToMany(mappedBy = "fieldAccessRulePK.group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
and by keeping the annotations in the PK class:
#Embeddable
public class FieldAccessRulePK implements Serializable{
private String fieldKey;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
...
}

Related

How to use multiple foreign keys in JPA?

I'm starting a project to know more in detail JPA.
Context:
At the end of his internship, the student has a report to make and a presentation in front of his professor to do about the internship.
I've a database, which is called "grade_management". It must contains a "student", "presentation", "report", "professor" and a "mark" (there are several rating criteria such as expression, quality of powerpoint ...) table. But now it's empty, since I want to make it throught JPA.
I've a "Presentation" class. Which countain this:
#Entity
public class Presentation implements Serializable {
#Id
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
private mark_id;
private int professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
}
But the Presentation table contain 2 foreign key: professor_id and mark_id.
My question is: How can I indicate that both of them are foreign key ?
I'm sorry if I'm not clear, don't hesitation to ask question.
Cordially
You shouldn't reference other entities by their ID, but by a direct reference to the entity.
Something like that :
#ManyToOne
#JoinColumn(name = "mark_id", referencedColumnName = "id")
private Mark mark; // supposed here that mark_id if link to entity `Mark`
#ManyToOne
#JoinColumn(name = "professor_id", referencedColumnName = "id") // suppose "id" is the column name of the PK inside the table Professor.
private Professor professor; // supposed here that professor_id if link to entity `Professor`
This code is supposing that you use an unidirectional relation.
For bidirectional you have to define this in the other side (Mark/Professor type)
#OneToMany(mappedBy = "professor")
private Presentation presentation;
From your explanation, it looks like you have a Database named grade_management and in that database you have "student", "presentation", "report", "professor" and a "mark" tables (i.e: which are #Entity by themselves defined in their separate respective classes )
I'm not sure whether you have defined them or not. If not then you have to define them first and then use the refactored code mentioned below.
So, you will have many-to-one relation mapping. You can annotate your foreign keys belonging to different tables using #ManyToOne annotation to indicate relation type and #JoinColumn annotation to indicate that this entity has a foreign key to the referenced table.
You can redefine your Presentation class show below:
#Entity
#Table(name = "Presentation")
public class Presentation implements Serializable {
#Id
#Column(name="presentation_id")
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
#ManyToOne
#JoinColumn(name = "mark_id")
private Mark mark_id;
#ManyToOne
#JoinColumn(name = "professor_id")
private Professor professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
//getter and setter
}
Also, if you need more information to read upon for yourself you can always checkout this Hibernate Documentation that explains everything you'll need to know.

spring data jpa fine granular auditing, custom audit

I have requirement where I need to insert user name and group name to which the user belongs (both available in SecurityContext) in the same table.
class Entity
{
#createdBy
String username
#createdBy
String groupname
other fields ...
}
As per requirement. I cant solve this issue by making a user class and referencing it through a foreign key.
With current implementation of AuditingHandler both fields are getting the same value. How do I make sure they get respective values.
Can this be achieved using current implementation ?
If not thn how can I provide custom implementation of AuditingHandler ?
You could make a separate embeddable class and annotate it with #CreatedBy in your parent class. One way is to define a bean implementing AuditorAware, then you can make it return custom object, containing your two required fields. For example, your parent class would look like this (note the listener annotation):
#Entity
#EntityListeners(AuditingEntityListener.class)
public class AuditedEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
#Embedded
#CreatedBy
private AuditorDetails createdBy;
// setters and getters
}
where AuditorDetails is:
#Embeddable
public class AuditorDetails {
private String username;
private String groupname;
// setters and getters
}
and finally, your AuditorAware bean:
#Component
class AuditorAwareImpl implements AuditorAware<AuditorDetails> {
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setUsername("someUser")
.setGroupname("someGroup");
}
}
AuditingHandler fetches your custom AuditorDetails from your AuditorAware bean (it must be single bean implementing it) and sets it in your auditable entity.

Cannot use an #IdClass attribute for a #ManyToOne relationship

I have a Gfh_i18n entity, with a composite key (#IdClass):
#Entity #IdClass(es.caib.gesma.petcom.data.entity.id.Gfh_i18n_id.class)
public class Gfh_i18n implements Serializable {
#Id #Column(length=10, nullable = false)
private String localeId = null;
#Id <-- This is the attribute causing issues
private Gfh gfh = null;
....
}
And the id class
public class Gfh_i18n_id implements Serializable {
private String localeId = null;
private Gfh gfh = null;
...
}
As this is written, this works. The issue is that I also have a Gfh class which will have a #OneToMany relationship to Gfh_i18n:
#OneToMany(mappedBy="gfh")
#MapKey(name="localeId")
private Map<String, Gfh_i18n> descriptions = null;
Using Eclipse Dali, this gives me the following error:
In attribute 'descriptions', the "mapped by" attribute 'gfh' has an invalid mapping type for this relationship.
If I just try to do, in Gfh_1i8n
#Id #ManyToOne
private Gfh gfh = null;
it solves the previous error but gives one in Gfh_i18n, stating that
The attribute matching the ID class attribute gfh does not have the correct type es.caib.gesma.petcom.data.entity.Gfh
This question is similar to mine, but I do not fully understand why I should be using #EmbeddedId (or if there is some way to use #IdClass with #ManyToOne).
I am using JPA 2.0 over Hibernate (JBoss 6.1)
Any ideas? Thanks in advance.
You are dealing with a "derived identity" (described in the JPA 2.0 spec, section 2.4.1).
You need to change your ID class so the field corresponding to the "parent" entity field in the "child" entity (in your case gfh) has a type that corresponds to either the "parent" entity's single #Id field (e.g. String) or, if the "parent" entity uses an IdClass, the IdClass (e.g. Gfh_id).
In Gfh_1i8n, you should declare gfh like this:
#Id #ManyToOne
private Gfh gfh = null;
Assuming GFH has a single #Id field of type String, your ID class should look like this:
public class Gfh_i18n_id implements Serializable {
private String localeId = null;
private String gfh = null;
...
}

Single Table Inheritance Query

i have an existing table for TransactionLogs which is either links to a External or to a InternalType. the id's corresponding to the cash adjustment & game transaction are stored in a single column called transaction id and a separate column called type indicates which table is it linked to
Because of the nature of the existing table, i mapped it in a single table inheritance:
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.INTEGER)
public class TransLog implements Serializable {
#Id
#GeneratedValue
private Long id;
private Integer type;
// getters and setters
}
#Entity
public class InternalAdjustmentTransLog extends TransLog {
#ManyToOne
#JoinColumn(name = "TransID", nullable = false)
private InternalAdjustmentRecord internalAdjustmentRecord;
// getters and setters
}
#Entity
public class ExternalTransLog extends TransLog {
#ManyToOne
#JoinColumn(name = "TransID", nullable = false)
private ExternalAdjustmentRecord externalAdjustmentRecord;
}
each of these two subclasses has their subclasses with defined descriminator values..
With the setup given above, there are instances that i need to get a unified data of both
internal and external records. What is the best way to accomplish this? at first i thought it would be enough to use the TransLog as the root class for the query (i'm using jpa criteria). however, i need to get TransId (which are defined in the subclasses and points to 2 different objects of no relationship).
Thanks.
You can make abstract method in TransLog that returns what you need and implement it in both subclasses.

jpa Using WHERE clause

I have 2 entities:
#Entity
public class Elements implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Owner owner;
}
#Entity
public class Owner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(fetch=FetchType.LAZY)
List<Elements> elements;
}
Suppose I want to fetch all elemets bellonging to the owner from Elements Table and therfore I use:
TypedQuery query=em.createQuery("SELECT elem FROM Elements elem WHERE
elem.owner:=elemOwner", Elements.class);
query.setParameter("elemOwner", ownerObjectFetchFromDataBase);
List<TrendUsers> userList=query.getResultList();
But I get the following error:
Comparisons between 'BLOB' and 'BLOB' are not supported. Types must be comparable. String types must also have matching collation.
If collation does not match, a possible solution is to cast operands to force them to the default collation...
Is there any way I can Select from Elements Table and in the WHERE clause use object (and not just String,int...)?
(p.s I also tried the query below and it didn't work:
TypedQuery query=em.createQuery("SELECT elem FROM Elements elem WHERE elem.owner.id:=elemOwner", Elements.class);
query.setParameter("elemOwner", ownerObjectFetchFromDataBase.id);
List userList=query.getResultList();
)
Thanks
You need to mark the Owner als a ManyToOne.
#Entity public class Elements implements Serializable {
...snip ...
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="OWNER_ID")
private Owner owner;
}
#Entity public class Owner implements Serializable {
.. snip ...
#OneToMany(fetch=FetchType.LAZY, mappedBy="owner")
List<Elements> elements;
}
Right now you try to store the serialized owner in Blob. Thats not what you want ;-)
enjoy
Edit: included fix by xatavt