I have 2 simple tables as follows:
Property
id
value
Uid_property
id
property_id
Each row in uid_property is unique. A property can be assigned to multiple properties.
What is the best way to map this in hibernate? I am trying to decide whether this is onetomany or manytomany. Please help!
Well, you'll have to think the problem through. In order to have a ManyToMany, you need to have both tables hold references to each other. You have a property_id in Uid_property, so there can be many Uid_property records with the same property_id, but you do not have anything that allows Property to reference Uid_property.
From the Uid_property entity point of view there can be Many records for One Property. In order to reproduce the database in JPA you could simply write the following code:
#Entity
public class Property {
#Id #GeneratedValue private Long id;
}
#Entity
public class Uid_property {
#Id #GeneratedValue private Long id;
#ManyToOne
Property property;
}
This would produce the following tables:
create table Property (id bigint not null, primary key (id))
create table Uid_property (id bigint not null, property_id bigint, primary key (id))
This matches what you have said, but there is an efficiency problem. If several Uid_property records are retrieved, duplicate Property entites are created:
List<Uid_property> pl = em.createQuery("select up from Uid_property up", Uid_property.class).getResultList();
pl.forEach(uid_p->{ System.out.println(uid_p + ":" + uid_p.getProperty()); });
Gives:
model.Uid_property#5b58ed3c:model.Property#592e843a
model.Uid_property#24faea88:model.Property#592e843a
It would be more efficient if there was one Property that referenced many Uid_property entities.
#Entity
public class Property {
#Id #GeneratedValue private Long id;
#OneToMany(mappedBy="property")
private Set<Uid_property> uid_properties;
}
// This hasn't changed
#Entity
public class Uid_property {
#Id #GeneratedValue private Long id;
#ManyToOne
private Property property;
}
Now getting the entities from the database is more efficient. Properties can be joined to Uid_properties:
List<Property> pl = em.createQuery("select distinct p from Property p left join fetch p.uid_properties", Property.class).getResultList();
pl.forEach(p->{ System.out.println(p + ":" + p.getUid_properties()); });
Which gives a single Property with a Set of uid_properties for the same database schema.
model.Property#61078690:[model.Uid_property#37ebc9d8, model.Uid_property#1cb3ec38]
Related
According to the Spring JPA documentation, in the Many-To-Many relationship (student - course) we must create a new table (student_course)
class student ---> class student_course <--- class course
According to the documentation, if we want to add a new property to the table (student_course) we must create a new class that will contain the compound keys of the student class and the course class
#Embeddable
class CourseStudentKey implements Serializable {
#Column(name="student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
}
_ Then to the Student_Course class we assign the id of type CourseStudentKey that contains the compound keys:
#Entity
class StudentCourse {
#EmbeddedId
CourseRatingKey id;
#ManyToOne
#MapsId("studentId")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("courseId")
#JoinColumn(name = "course_id")
Course course;
}
My question is: What is the difference in creating only the StudentCourse class and doing the #ManyToOne mapping to the Student class and the Course class??... in this way we can also add attributes to the StudentCourse class
_Clase Student
#Entity
class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private idStudent;
#JsonIgnore
#OneToMany(mappedBy = "student")
List<StudentCourse> studentCourses = new ArrayList<>();
_Clase Course
#Entity
class Course{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private idCourse;
#JsonIgnore
#OneToMany(mappedBy = "course")
List<StudentCourse> studentCourses = new ArrayList<>();
}
_Clase StudentCourse
#Entity
class StudentCourse {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private idStudentCourse;
#ManyToOne
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#JoinColumn(name = "course_id")
Course course;
}
The only difference in the examples posted by you, is, in case of Embeddable, the student_id course_id would be a composite key, so there would only be one row allowed per student_id course_id combination. Whereas, in the second example, you have used generated primary key, ensuring multiple rows for each student_id course_id combination. This would be particularly useful if the student fails the course for the first time and attempts it again. You can then add parameters like attemped_on, is_completed, etc. to the student_course entity
Your examples show differences in the key, and as Chetan's answer states, this affects the key used in the table. The choices here isn't necessarily in using a separate class/embbeded class, but in using a single generated Identifier vs using a composite primary key for the entity.
In the embedded example you've posted, you have a composite primary key based on foreign key mappings. There are many other ways to map this same setup though, but the common parts will be:
composite PKs need an ID class. It doesn't have to be embedded in your class (see JPA derived IDs) but does need to exist. This is part of the JPA spec and allows em.find operations to deal with a single object.
ID values are immutable. They cannot change without remove/persist operations as per the JPA specification. Many providers don't like you even attempting to modify them in an Entity instance. In your embeddable example, you cannot change the references, while in the generated id example, you can.
It also affects what JPA requires you to use in foreign keys. If you use a composite ID, any references to that entity (*ToOne) that require foreign keys to that table are required to use its defined IDs - all columns that make up that ID. Some providers don't enforce this, but it will affect entity caching; since entities are cached on their IDs, using something else as the target of FKs might mean database hits for entities already in the cache.
I have an entity class which uses auto generated id from database (PostgreSQL). It has been persisting fine without requiring me to specify an id to it. e.g.
#Entity public class MyEntity {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// ... other columns
}
Now I want to add a List of associated entities owned by this entity class with uni-directional association. e.g.
#Entity public class MyEntity {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(cascade = CascadeType.ALL) #JoinColumn(name="pid")
private List<SubEntity> subEntities;
// ... other columns
}
#Entity public class SubEntity implements Serializable {
#Id private Integer pid; // refer to id of MyEntity
#Id private String name; // pid, name forms a composite key for SubEntity
// ... other columns
}
Then I bumped into an issue that JPA (Hibernate in this case) was generating SQLs like:
INSERT INTO MYENTITY (...) VALUES (...)
INSERT INTO SUBENTITY (pid, ...) VALUES (null, ...)
It failed when trying to insert a null value to pid as it has not null constraint in the database schema. If I bypass this, Hibernate then generates an update statement to update the null value with the generated id from MyEntity:
UPDATE SUBENTITY SET pid = ? WHERE pid = null AND name = ?
I get that the auto generated id is not known until after the insert to MyEntity, so it updates afterward. But I wonder if there is a solution so that Hibernate does the insert to MyEntity ONLY first, get the generated id THEN does the inserts to SubEntity with the correct pid and no update afterward?
This should be possible. Please create an issue in the Hibernate issue tracker with a test case that reproduces this issue. Apart from that, I would suggest you try using a sequence generator as that is more scalable anyway.
I have a table and a view in the database (Postgres). The business relationship between them is one-to-one. However, I CANNOT modify the database to create any constraint.
The table is something like:
CREATE TABLE invoice
(
invoiceid text NOT NULL,
date_created timestamp without time zone NOT NULL DEFAULT now(),
CONSTRAINT invoice_pkey PRIMARY KEY (invoiceid)
)
WITH (
OIDS=FALSE
);
and the view is something like:
CREATE OR REPLACE VIEW invoice_view AS
SELECT invoice.invoiceid, invoice.date_created,
.......
FROM invoice;
Can I create a custom one-to-one relationship in Hibernate so I can use something like myInvoice.getInvoiceView();? If it is possible, How to do it?
I am using the XML configuraiton. If anyone can provide me an answer using annotations, it will be also great.
Thanks in advance.
Yes, Hibernate does not care whether the entities are mapped to tables or views:
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#Column(name = "invoiceid")
private String id;
#OneToOne
#PrimaryKeyJoinColumn
private InvoiceView invoiceView;
}
#Entity
#Table(name = "invoice_view")
public class InvoiceView {
#Id
#Column(name = "invoiceid")
private String id;
}
My data model consists of items with a history. I'll call a point in time an "instant"; all tables therefore have an "instant_id" that specifies how that item was configured at that instant. The "instant_id" is rolled into a composite primary key for all tables. Imagine the following example:
Table Computer
============
PK int instant_id <-- Shared id
PK int computer_id <-- Child id
int computer_type_id <-- Parent id
varchar foo
Table ComputerType
==================
PK int instant_id <-- Shared id
PK int computer_type_id <-- Parent id
varchar bar
There is a foreign key in Computer mapping (instant_id, computer_type_id) to the ComputerType primary key.
We use something like
#Embeddable ComputerId {
#Column(name='instant_id', nullable=false) int instant_id,
#Column(name='computer_id', nullable=false) int computer_id
}
Then:
Computer {
#EmbeddedId ComputerId id;
#MapsId('instant_id')
#ManyToOne
#JoinColumns({
#JoinColumn(name='instant_id',...),
#JoinColumn(name='computer_type_id',...)
})
ComputerType computerType;
}
No matter how I combine MapsId with JoinColumns, I can't seem to get this to work. Any ideas?
I don't see a ManyToOne association. You are not showing us how ComputerType is declared, I am assuming it's an Entity. If that's the case, per table definition you provided, both Computer and ComputerType share a composite primary key: instant_id and computer_type_id.
If that is true and they share the same primary key, you might be better off normalizing those two tables into one table.
I think I understand the issue now. You need to consider the computer_type_id as part of the composite key for Computer table as well. The column computer_type_id by itself is not quite meaningful; in the ComputerType table it is a part of the primary key, the other part being instant_id. So if that's the case, you need to include it as part of the primary key for Computer table as well, because you will never have a case where Computer.instant_id = ComputerType.instant_id AND Computer.computer_type_id <> ComputerType.computer_type_id, for a given related association. (If I understand this case correctly)
If you agree with that, then here is the solution:
#Embeddable
public class ComputerId implements Serializable {
int computer_id;
#ManyToOne
#JoinColumns({#JoinColumn(name = "instant_id", insertable=false, updatable=false),
#JoinColumn(name = "computer_type_id", insertable=false, updatable=false) })
ComputerType computerType;
// getters and setters
}
#Entity
public class Computer {
#EmbeddedId
ComputerId computerId;
// getters and setters
}
public class ComputerTypeId implements Serializable {
#Column(name="instant_id", nullable=false) int instant_id;
#Column(name="computer_type_id", nullable=false) int computer_type_id;
// getters and setters
}
#Entity
public class ComputerType {
#EmbeddedId
ComputerTypeId computerTypeId;
String bar;
// getters and setters
}
Finally, you might want to consider Hibernate Envers for Entity versioning.
Hope this helps.
Looked in many places and found that the hibernate with postgresql can use an IDENTITY primary key generator which maps into serial/bigserial table column. Suppose i have follow entity:
#Entity
class A {
long id;
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
public long getId() { return id; }
}
Works good and ddl looks like:
create table A (id bigserial)
Unfortunatelly any attempt to reference to 'a' via #ManyToOne produces the foreign key column which is also bigserial.
#Entity
class B {
// id ommitted ...
A a;
#ManyToOne
public A getA() {
return a;
}
}
generates ddl like follow:
create table B (..., a_id bigserial)
In most cases this will work ok. But the logically it is completly wrong. a_id has nothing to do with bigserial "datatype" either.
It there any way to tell hibernate to use bigint for column a_id in the table B ?
Tried to override with columnDefinition in the #JoinColumn or #Column annotations for a getter with no luck. Hibernate completly ignores these annotation attributes.