I want to reference a composite key (using #EmbeddedId) as a foreign key. I followed other tutorials but none seem to work in my case.
I have to create 3 entities, place, visit, and tourist. visit has a primary key composed of 2 foreign keys ( place_id, and tourist_id)
here is the "place" class, tourist is the written in the same way.
`
#Entity
#Table(name = "place")
public class Place {
#Id
#Generated
private Long id;
#OneToMany
#JoinColumns({
#JoinColumn(name = "visit_id1", referencedColumnName = "place_id"),
#JoinColumn(name = "visit_id2", referencedColumnName = "tourist_id")
})
private ArrayList<Visit> placeVisit;
}
second a visit class which has the composite key:
#Entity
#Table(name = "visit")
public class Visit {
#EmbeddedId
private VisitId id;
}
//the id class
#Embeddable
public class VisitId implements Serializable {
#ManyToOne
#JoinColumn(name = "place_id",referencedColumnName = "id")
private Place place;
#ManyToOne
#JoinColumn(name = "tourist_id",referencedColumnName = "id")
private Tourist tourist;
}
`
when executing, spring boot tells me that there is a #JoinColumn which refrences place_id but the target entity Place, does not have such an attribute.
I searched allot but I can't find a similar case.
when executing, spring boot tells me that there is a #JoinColumn which refrences place_id but the target entity Place, does not have such an attribute. I searched allot but I can't find a similar case.
Related
I have 2 tables with one-to-many relation on the owner class (Person) and many-to-one on the child class (Email)
My problem is that in the child class' foreign key is (person_id) is always null when I want to save my Person object. I tried different things using other questions' answers, but no luck.
I would like to solve this in an annotation approach, if it is possible.
Person Class:
#Entity
#Table(name="PERSON")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_PERSON")
#SequenceGenerator(name="SEQ_PERSON", sequenceName="SEQ_PERSON", allocationSize=1)
#Column(name = "person_id")
private Long personId;
#OneToMany(mappedBy="person", cascade=CascadeType.ALL)
private List<Email> email;
// getters and setters
}
Email class:
#Entity
#Table(name="EMAIL")
public class Email{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_EMAIL")
#SequenceGenerator(name="SEQ_EMAIL", sequenceName="SEQ_EMAIL", allocationSize=1)
#Column(name = "email_id")
private Long emailId;
#ManyToOne
#JoinColumn(name="person_id", referencedColumnName="person_id", insertable = true)
private Person person;
// getters and setters
}
I get no exception / errors when I use this.
When I change the JoinColumn to #JoinColumn(name="person_id", referencedColumnName="person_id", nullable = false, updatable = false, insertable = true) then I get this error: org.hibernate.PropertyValueException: not-null property references a null or transient value: com.test.Email.person
I tried to change the Person's email setter like this, nothing changed:
public synchronized void setEmail(List<Email> email) {
this.email=email;
for(Email em: email) {
em.setPerson(this);
}
}
source
I have a Person object, with 2 emails (as a test object to save, every column is filled, except the FK in Email table), do I have to set the FK everytime manually? (it doesn't look good, if I have multiple one-to-many variables)
Edit: I tried this Which is working, but my problem with that if I have a very deep data structure with a lot of One-To-Many relations, I have to implement this to every variable and then save.. So, is there a better solution with pure annotations / getters-setters ?
So, I have the following entity:
#Entity
public class EntityOne{
#EmbeddedId
private EntityOneIdentity entityOneIdentity;
#ManyToOne
#JoinColumns(value = {
#JoinColumn(name = "MerchantID", referencedColumnName = "MerchantID"),
#JoinColumn(name = "TenantID", referencedColumnName = "TenantID")})
private Merchant merchant;
.......
}
With Identity (Composite key):
#Embeddable
public class EntityOneIdentity implements Serializable {
#Column(name = "EntityID")
private String entityID;
#Column(name = "TenantID")
private String tenantID;
....
}
My challenge is that I need the #JoinColumn("TenantID") that comes from Merchant Entity to also be a PK in my EntityOne (just tenantId! The "MerchantId" will be left as it is). The only thing I found was the #MapId adnotation, but this adds both FK mentioned in #JoinColumns to my wanted Primary Keys.
Thank you in advance!
Solved it by marking #JoinColumns with insertable/updatebla false and by creating an extra merchantId field which value I set in the merchant setter
I can't propper map DB tables with JPA annotation.
Tables Subject and Place is ManyToMany through JoinTable.
Subject.java
#Entity
#Table(name = "SUBJECT")
public class Subject implements Serializable {
#Id
#Column(name = "SID")
private Integer sid;
#Column(name = "NAME")
private String name;
// getters and setters
}
SubjectPlace.java
#Entity
#Table(name = "SUBJECT_PLACE")
public class SubjectPlace implements Serializable {
#Id
#Column(name = "SPID")
private Integer spid;
#ManyToOne
#JoinColumn(name = "SUB_KEY") //Subject FK
private Subject subject;
#ManyToOne
#JoinColumn(name = "PLC_KEY") //Place FK
private Place place;
// getters and setters
}
Place.java
#Entity
#Table(name = "PLACE")
public class Place implements Serializable {
#Id
#Column(name = "PID")
private Integer pid;
#Column(name = "NAME")
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "SUBJECT_PLACE",
joinColumns = { #JoinColumn(name = "PLC_KEY", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "SUB_KEY", nullable = false, updatable = false) })
private Set<Subject> subjects;
// getters and setters
}
But than I need to link Person with Subject in selected Places. I mean that each Place has its own collection of Subject. And a Person have link to Subject whitch resides in particular Place.
like This:
Subject (M) -- (M) Place through JoinTable Subject (1) -- (M) Subject_Place (M) -- (1) Place
Person (M) -- (M) Subject_Place through JoinTable Person (1) -- (M) Person_Subject_Place (M) -- (1) Subject_Place
Person.java
#Entity
#Table(name = "PERSON")
public class Person implements Serializable {
#Id
#Column(name = "PRSID")
private Integer prsid;
#Column(name = "NAME")
private String name;
// How to annotate this code?
// I experience problem in this part of code
#OneToMany
#JoinColumn(name="SPID_KEY")
private List<SubjectPlace> subjectPlaces;
// getters and setters
}
PersonSubjectPlace.java
#Entity
#Table(name = "PERSON_SUBJECT_PLACE")
public class PersonSubjectPlace implements Serializable {
#Id
#Column(name = "PSPID") // Person_Subject_Place ID
private Integer pspid;
#ManyToOne
#JoinColumn(name = "PER_KEY") //Person FK
private Person person;
// How to annotate this code?
// I experience problem in this part of code
#ManyToOne
#JoinColumn(name = "SPID_KEY") //Subject_Place FK
private SubjectPlace subjectPlace;
// getters and setters
}
And when I try so get Persons and its Subjects, I get this error:
Caused by: org.hibernate.MappingException: Foreign key (FK2C3B79384AABC975:PERSON_SUBJECT_PLACE [SPID_KEY])) must have same number of columns as the referenced primary key (SUBJECT_PLACE [PLC_KEY,SUB_KEY])
What, How shoul I map?
In your OneToMany mapping you don't need to specify the foreign key, you just need to use mappedBy property to refer your mapping object, you can learn more about it in OneToMany Mapping Documentation, and here's what you need to map Person and PersonSubjectPlace entities:
In your Person class:
#OneToMany(mappedBy="person")
private List<PersonSubjectPlace> personsubjectPlaces;
In your PersonSubjectPlace class:
#ManyToOne
#JoinColumn(name="PRSID") //Specify the primary key of Person
private Person person;
For further information about the difference between JoinColumn and mappedBy you can take a look at this answer.
EDIT:
For the mapping between SubjectPlace and PersonSubjectPlace:
In your SubjectPlace class:
#OneToMany(mappedBy="subjectPlace")
private List<PersonSubjectPlace> personsubjectPlaces;
In your PersonSubjectPlace class:
#ManyToOne
#JoinColumn(name="SPID") //Specify the primary key of SubjectPerson
private SubjectPlace subjectPlace;
Note:
The best approach to map those classes is to use #JoinTable between Person and SubjectPlace, take a look at this #JoinTable example, because PersonSubjectPlace is pratically an asociation-entity between Person and SubjectPlace.
You should remove #Joincolumn annotation and add mappedBy variable to #OneToMany annotation.
#OneToMany(mappedBy = "spid")
You should have a variable in SubjectPlace that has a Person where you should put #JoinColumn annotation
Firstly, I am somewhat new with Hibernate. To get to know the technology I am using it in a project. I am trying to map the following database:
Campaign
campaignId(+)
name
Promotion
campaignId(+)
discount(+)
product
message
I've indicated the primary key in both cases with a (+). The 'campaignId' in Promotion is a foreign key to Campaign to model the 1:m mapping (A Campaign has many Promotions). Using annotations I am stuck on how to do this.
I do not really want to add a promotionId in the Promotion table as it makes working with the data cumbersome. This of course, makes the bridging table a bit tricky. I also have problems working with a foreign key that is also part of the primary key.
Is a mapping for this possible at all?
Ok, I got it working. Sort of. Have to check if persistence actually work. I did the following:
#Entity
#Table(name = "CAMPAIGNS")
#Audited
public class CampaignEntity {
private int campaignId;
private String name;
private List<PromotionEntity> promotions;
public CampaignEntity(int campaignId, String name) {
this.campaignId = campaignId;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "cmp_id")
public int getCampaignId() {
return campaignId;
}
public void setCampaignId(int campaignId) {
this.campaignId = campaignId;
}
// Campaign name here... left out to save space
#OneToMany
#JoinColumn(name = "cmp_id")
public List<PromotionEntity> getPromotions() {
return promotions;
}
public void setPromotions(List<PromotionEntity> promotions) {
this.promotions = promotions;
}
}
Promotion is a vanilla mapping (not using embedded after all), with the fields: campaignId, discount, message. (It also does not have a #ManyToOne annotation.)
Does that make sense?
Lastly, and this will be first prize: as you can see I'm using Envers to audit the whole thing. The above creates a rather ugly "CampaignEntity_PromotionEntity_AUD" table. I understand that it is needed, but how can I rename it to CAMPAIGN_PROMOTION_AUD rather?
Thanks guys!
I got an answer on a lonely website deeply hidden away in far-corners of the Hibernate's Jira error tracking website: https://hibernate.onjira.com/browse/HHH-3729.
The answer is to use #AuditJoinTable(name = "CAMPAIGN_PROMOTION_AUD") of course.
This is a basic example of a one-to-many relationship and its inverse.
public class Campaign
{
#OneToMany(mappedBy = "campaign)
private List<Promotion> promotions;
}
public class Promotion
{
#ManyToOne
private Campaign campaign;
}
You can use an EmbeddedId to create a multi-field PK.
Remove the PK fields from Promotion
Create a separate entity, say PromotionPK, without any annotations except for #Column on the PK fields
In Promotion, include that PK class as field, annotating it using #EmbeddedId, with getters and setters
The FK mapping is as Wouter indicated.
This is what I am now using. It works well and Hibernate handles the PKs of the Promotions for me. Thanks again.
#Entity
#Table(name = "CAMPAIGNS")
#Audited
public class CampaignEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer campaignId;
#Column(name = "name", nullable = false, unique = true)
private String campaignName;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "CAMPAIGN_PROMOTIONS",
joinColumns = { #JoinColumn(name = "campaign_id") },
inverseJoinColumns = { #JoinColumn(name = "promotion_id") })
private Set<PromotionEntity> promotions;
...
}
and then, PromotionEntity:
#Entity
#Table(name = "PROMOTIONS")
#Audited
public class PromotionEntity implements Comparable<PromotionEntity> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "discount", nullable = false)
private Integer discount;
#Column(name = "message", nullable = false)
private String message;
...
}
I also prefer annotating the fields rather than the getters as it is more compact and reads easier.
Can someone please explain to me #MapsId in hibernate? I'm having a hard time understanding it.
It would be great if one could explain it with an example and in what kind of use cases is it most applicable?
Here is a nice explanation from Object DB.
Designates a ManyToOne or OneToOne relationship attribute that provides the mapping for an EmbeddedId primary key, an attribute within an EmbeddedId primary key, or a simple primary key of the parent entity. The value element specifies the attribute within a composite key to which the relationship attribute corresponds. If the entity's primary key is of the same Java type as the primary key of the entity referenced by the relationship, the value attribute is not specified.
// parent entity has simple primary key
#Entity
public class Employee {
#Id long empId;
String name;
...
}
// dependent entity uses EmbeddedId for composite key
#Embeddable
public class DependentId {
String name;
long empid; // corresponds to primary key type of Employee
}
#Entity
public class Dependent {
#EmbeddedId DependentId id;
...
#MapsId("empid") // maps the empid attribute of embedded id
#ManyToOne Employee emp;
}
Read the API Docs here.
I found this note also useful: #MapsId in hibernate annotation maps a column with another table's column.
It can be used also to share the same primary key between 2 tables.
Example:
#Entity
#Table(name = "TRANSACTION_CANCEL")
public class CancelledTransaction {
#Id
private Long id; // the value in this pk will be the same as the
// transaction line from transaction table to which
// this cancelled transaction is related
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_TRANSACTION", nullable = false)
#MapsId
private Transaction transaction;
....
}
#Entity
#Table(name = "TRANSACTION")
#SequenceGenerator(name = "SQ_TRAN_ID", sequenceName = "SQ_TRAN_ID")
public class Transaction {
#Id
#GeneratedValue(generator = "SQ_TRAN_ID", strategy = GenerationType.SEQUENCE)
#Column(name = "ID_TRANSACTION", nullable = false)
private Long id;
...
}
IMHO, the best way to think about #MapsId is when you need to map a composite key in a n:m entity.
For instance, a customer can have one or more consultant and a consultant can have one or more customer:
And your entites would be something like this (pseudo Java code):
#Entity
public class Customer {
#Id
private Integer id;
private String name;
}
#Entity
public class Consultant {
#Id
private Integer id;
private String name;
#OneToMany
private List<CustomerByConsultant> customerByConsultants = new ArrayList<>();
public void add(CustomerByConsultant cbc) {
cbc.setConsultant(this);
this.customerByConsultant.add(cbc);
}
}
#Embeddable
public class CustomerByConsultantPk implements Serializable {
private Integer customerId;
private Integer consultantId;
}
#Entity
public class CustomerByConsultant{
#EmbeddedId
private CustomerByConsultantPk id = new CustomerByConsultantPk();
#MapsId("customerId")
#JoinColumn(insertable = false, updatable = false)
private Customer customer;
#MapsId("consultantId")
#JoinColumn(insertable = false, updatable = false)
private Consultant consultant;
}
Mapping this way, JPA automagically inserts Customer and Consultant ids in the EmbeddableId whenever you save a consultant. So you don't need to manually create the CustomerByConsultantPk.
As he explained Vladimir in his tutorial, The best way to map a #OneToOne relationship is to use #MapsId. This way, you don’t even need a bidirectional association since you can always fetch the Child entity by using the Parent entity identifier.
MapsId lets you use the same primary key between two different entities/tables. Note: when you use MapsId, the CASCADE.ALL flag becomes useless, and you will need to make sure that your entities are saved manually.