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.
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 ?
I have two Entitys.
#Entity
#Table(name = "EX.EXAMPLE")
public class Entity
{
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#OneToOne
private Entity2 stuff1;
#OneToOne
private Entity2 stuff2;
#OneToOne
private Entity2 stuff3;
}
And the second is the following
#Entity
#Table(name = "EX.EXAMPLE2")
public class Entity2
{
#Id
private long stuff;
}
Know i need to change the current id (Stuff) to a new One. I added a new columne ID to table. How to change/migrate the foreign Keys and make the new Columne ID a uniqe Key ?
Is it possibe to do it with Hibernate ? or is there a way with MYSQL ?
you are not allowed to modify the primary key. do it in a different approach. and why you have created three #OneToOne relations with the same entity? this is redundant and they are the same. it's not necessary to make relation for each object
Can someone please explain to me how to declare annotations on entities correctly?
There are two tables in the database. One table has a foreign key to a id of another one (#ManyToOne binding), both IDs are of Integer. And I created two entities to represent them in code.
How can I map these entities to DB? I had realized recently that Hibernate refers to classes. I mean that in #JoinColumn("???") I have to write the name of a column in DB, isn't it? What if the ID field's name in the entity is the same as in the DB? Thanks everyone in advance! Regards.
#Entity
#Table(name = "stat")
public class Statistic {
#Id
#Column( name = "statisticId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long statisticsId;
...............
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn (name="userId") //<------------ The same name.
private User userStat;
//getters and setters + constr + overriding of ToString()
}
#Entity
#Table(name = "usser")
public class User {
#Id
#Column(name = "userId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;`enter code here`
................
#OneToOne(mappedBy = "userStat")
private Statistic statisticUser;
}
I’m working with an existing schema that I’d rather not change. The schema has a one-to-one relationship between tables Person and VitalStats, where Person has a primary key and VitalStats uses the same field as both its primary key and its foreign key to Person, meaning its value is the value of the corresponding PK of Person.
These records are created by external processes, and my JPA code never needs to update VitalStats.
For my object model I’d like my Person class to contain a VitalStats member, BUT:
When I try
#Entity
public class Person{
private long id;
#Id
public long getId(){ return id; }
private VitalStats vs;
#OneToOne(mappedBy = “person”)
public VitalStats getVs() { return vs; }
}
#Entity
public class VitalStats{
private Person person;
#OneToOne
public Person getPerson() { return person; }
}
I have the problem that VitalStats lacks an #Id, which doesn’t work for an #Entity.\
If I try
#Id #OneToOne
public Person getPerson() { return person; }
that solves the #Id problem but requires that Person be Serializable. We’ll get back to that.
I could make VitalStats #Embeddable and connect it to Person via an #ElementCollection, but then it would have to be accessed as a collection, even though I know that there’s only one element. Doable, but both a little bit annoying and a little bit confusing.
So what’s preventing me from just saying that Person implements Serializable? Nothing, really, except that I like everything in my code to be there for a reason, and I can’t see any logic to this, which makes my code less readable.
In the meantime I just replaced the Person field in VitalStats with a long personId and made that VitalStats’s #Id, so now the #OneToOne works.
All of these solutions to what seems (to me) like a straightforward issue are a bit clunky, so I’m wondering whether I’m missing anything, or whether someone can at least explain to me why Person has to be Serializable.
TIA
To map one-to-one association using shared primary keys use #PrimaryKeyJoinColumn and #MapsId annotation.
Relevant sections of the Hibernate Reference Documentation:
PrimaryKeyJoinColumn
The PrimaryKeyJoinColumn annotation does say that the primary key of
the entity is used as the foreign key value to the associated entity.
MapsId
The MapsId annotation ask Hibernate to copy the identifier from
another associated entity. In the Hibernate jargon, it is known as a
foreign generator but the JPA mapping reads better and is encouraged
Person.java
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "person_id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private VitalStats vitalStats;
}
VitalStats.java
#Entity
public class VitalStats
{
#Id #Column(name="vitalstats_id") Long id;
#MapsId
#OneToOne(mappedBy = "vitalStats")
#JoinColumn(name = "vitalstats_id") //same name as id #Column
private Person person;
private String stats;
}
Person Database Table
CREATE TABLE person (
person_id bigint(20) NOT NULL auto_increment,
name varchar(255) default NULL,
PRIMARY KEY (`person_id`)
)
VitalStats Database Table
CREATE TABLE vitalstats
(
vitalstats_id bigint(20) NOT NULL,
stats varchar(255) default NULL,
PRIMARY KEY (`vitalstats_id`)
)
In my case this made the trick:
Parent class:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/** auto generated id (primary key) */
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
private Long id;
/** user settings */
#OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
private Setting setting;
}
Child class:
public class Setting implements Serializable {
private static final long serialVersionUID = 1L;
/** setting id = user id */
#Id
#Column(unique = true, nullable = false)
private Long id;
/** user with this associated settings */
#MapsId
#OneToOne
#JoinColumn(name = "id")
private User user;
}
I have a following problem that I need to solve.
The core issues is that I want to add additional column into JoinTable for ManyToMany relation in JPA. In my case I have following entities.
The Topic is a simple entity which has many RemoteDocument's (one RemoteDocument may be refered by many Topic's, hence it should be ManyToMany relation). Also RemoteDocument entity is read only because it may be read only from Oracle Materialized View moreover any altering of this Materialized View is forbidden. So I want to store order of RemoteDocuments related to some Topic. In fact I can do something like that with additional entity:
#Entity
public class Topic {
#Id
private Long id;
#Basic
private String name;
#OneToMany
private Set<TopicToRemoteDocument> association;
}
#Entity
public class RemoteDocument {
#Id
private Long id;
#Basic
private String description;
}
#Entity
public class TopicToRemoteDocument {
#OneToOne
private Topic topic;
#OneToOne
private RemoteDocument remoteDocument;
#Basic
private Integer order;
}
In this case additional entity TopicToRemoteDocument helps me to replace ManyToMany association with OneToMany and add extra field order.
But I want to have ManyToMany relation but with configured additional column in join table
Use list instead of set, together with the #OrderColumn annotation and JPA will automatically take care of the order:
#MappedSuperclass
public class BaseEntity{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public Long getId(){
return id;
}
public void setId(final Long id){
this.id = id;
}
}
#Entity
public class Topic extends BaseEntity{
#ManyToMany(mappedBy = "topics")
#OrderColumn
private List<Document> documents = new ArrayList<Document>();
public List<Document> getDocuments(){
return documents;
}
public void setDocuments(final List<Document> documents){
this.documents = documents;
}
}
#Entity
public class Document extends BaseEntity{
#ManyToMany
#OrderColumn
private List<Topic> topics = new ArrayList<Topic>();
public List<Topic> getTopics(){
return topics;
}
public void setTopics(final List<Topic> topics){
this.topics = topics;
}
}
Generated DDL (using hibernate and HSQL):
create table Document (
id bigint generated by default as identity (start with 1),
primary key (id)
);
create table Document_Topic (
documents_id bigint not null,
topics_id bigint not null,
topics_ORDER integer not null,
documents_ORDER integer not null,
primary key (documents_id, topics_ORDER)
);
create table Topic (
id bigint generated by default as identity (start with 1),
primary key (id)
);
alter table Document_Topic
add constraint FK343B5D0B481100B2
foreign key (documents_id)
references Document;
alter table Document_Topic
add constraint FK343B5D0B558627D0
foreign key (topics_id)
references Topic;
I would try to avoid using a List unless you allow duplicates.
There is a #OrderColumn annotation that automatically does this. Have you tried it?
#Entity
public class Topic {
#Id
private Long id;
#Basic
private String name;
#OneToMany
#OrderColumn
private Set<TopicToRemoteDocument> association;
}
One technique that is useful when creating the many-to-many mapping class entity is to attribute the id's in the class along with #ManyToOne designation which makes this class act as the composite key class:
#Entity
#Table(name = "market_vendor")
public class MarketVendor implements Serializable
{
#Id
#ManyToOne
#JoinColumn(name = "market_id")
private Market market;
#Id
#ManyToOne
#JoinColumn(name = "vendor_id")
private Vendor vendor;
#Basic
#Column(name="active")
private boolean active;
public MarketVendor(Market market, Vendor vendor, boolean active)
{
this.market = market;
this.vendor = vendor;
this.active = active;
}
}
This allows you to have the composite primary key defined within the same class without having to have a separate primary key class. You also need to make the class serializable.