Hibernate identity primary key generator and foreign keys - java

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.

Related

JPA - How to persist an entity having auto generated id and its associated entities

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.

JPA (Hibernate) OneToOne Impedance Mismatch

I am studying JPA and Hibernate to build a Spring Boot webapp, and there's something that bugs me.
It is related to impedance mismatch in One To One relationships.
Let's say I have two domain entities, A and B, that have a one to one relationship.
This is what I would like to have:
in the Java Classes, I would like to have A hold a reference to B;
in the Database, I would like to have the table for "b" objects have a column with the foreign key to "a" keys.
Is there a way to do this with JPA and Hibernate in Spring Boot?
I report here the problem with real-world classes and code.
In my domain I have basically people and signatures.
Therefore, in my Java Code, I have Person #Entity and a Signature #Entity.
In Java, it makes sense to have the Person object own a Signature object.
So, here is the Person class:
#Entity
#Table(name = "people")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name="first_name")
#NotNull
#NotBlank
#Size(min = 3, max = 100)
private String firstName;
#Column(name="last_name")
#NotNull
#NotBlank
#Size(min = 3, max = 100)
private String lastName;
// ??? which annotations?
private Signature signature;
// I omit constructors, getters and setters for brevity
And this is the Signature class:
#Entity
#Table(name = "signatures")
public class Signature {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name="name")
#NotNull
#NotBlank
private String name;
#Column(name="type")
#NotNull
private String type;
#Column(name="image")
#NotNull
#NotEmpty
#Lob
#Type(type="org.hibernate.type.MaterializedBlobType")
private byte[] image;
// I omit constructors, getters and setters for brevity
As you can see, Ids should be generated automatically, and I would like my Person class to have a reference to its Signature, and not vice-versa.
On the contrary, this is the DB schema I'd like to use:
CREATE SCHEMA signatures;
CREATE TABLE signatures.people (
id BIGSERIAL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE signatures.signatures (
id BIGSERIAL,
type VARCHAR[16] NOT NULL,
name VARCHAR[100] NOT NULL,
image BYTEA NOT NULL,
person BIGINT NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_signature_people FOREIGN KEY (person) REFERENCES signatures.people (id) ON DELETE CASCADE ON UPDATE CASCADE
);
As you can see here, I would like the Signatures table to have a foreign key to the People table, and not vice-versa.
Is this possible?
The #OneToOne mapping is a bit of an odd-ball. When the relationship is bi-directional, you can decide the owning side, but in a unidirectional relationship the declaring entity will always be the one with the foreign key.
One option is to make the relationship bi-directional, but hide the other direction in code.
The other way is to use a #OneToMany mapping, which will create the foreign key in the "many" table. This is also consistent with the database schema, as multiple child table rows could then link to the same parent row at least theoretically, especially if there's not a constraint to make sure they're unique.

JPA: How can an #Embeddable object get a reference back to its owner, but the #Embeddable is in a lazy collection?

I know that if you want to reference back from #Embeddable to its parent you can set the parent "manually" in the setter and use #Access(AccessType.PROPERTY) for this embedded field as stated in this answer, but what if this embedded element is mapped in a collection, which is lazy loaded?
Actually not sure whether this is an issue, if not "manually" reference back from #embeddable to its parent, everything is fine.
#CollectionTable.JoinColumns() is used to set the foreign key columns of the collection table which reference the primary table of the entity, which means that once set this optional property, there is no necessary to "manually" reference back from #embeddable to its parent.
Use your case as example:
#Entity
public class Image {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "COMPUTERS", joinColumns = #JoinColumn(name = "ID_IMAGE"))
private List<Computer> computers;
}
#Embeddable
public class Computer {
#Column
private String ipAddress;
*****//This idImage field is not necessary
#Column(name = "ID_IMAGE", insertable = false, updatable = false)
private Long idImage;*****
}
Once comment out the field idImage and its #Column annotation, the generated SQL is:
create table IMAGES (
id bigint not null,
Name_Image varchar(255),
primary key (id)
)
create table COMPUTERS (
ID_IMAGE bigint not null,
ipAddress varchar(255)
)
alter table COMPUTERS
add constraint FKl1ucm93ttye8p8i9s5cgrurh
foreign key (ID_IMAGE)
references IMAGES
If "manually" declare the join column in the embeddable class, although the DDL are the same, the embeddable object will contain one extra field "imageId", which will cause the JDBC call parameter out of index when executing the INSERT operation.

Entity Mapping - Manytomany or OneToMany

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]

Map part of composite PK to composite FK

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.

Categories

Resources