JPA / Hibernate Many to Many with associated columns and nullable join column - java

I have three tables setup using PostgreSQL 9.4.5. Some details removed.
Table: component
id | bigint | not null default nextval('component_seq'::regclass) |
Table: file
id | bigint | not null default nextval('file_seq'::regclass) |
Table: component_file
id | bigint | not null default nextval('component_file_seq'::regclass) |
component_id | bigint | not null |
file_id | bigint | |
usage | text | not null |
Essentially, it's a many-to-many relationship with additional columns in the many-to-many join table.
A file can be associated to one or more components.
A component can be associated to one or more files.
It is possible for a component to be associated with no files which is why the component_file.file_id is nullable.
I have modeled this using JPA with Hibernate as my implementation provider. I use OneToMany associations (Component and File) in order to have access to the associated join table metadata and two ManyToOne associations for the join table object representation (ComponentFile)
public class Component {
...
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "component_seq")
#Column(name = "id", nullable = false, insertable = true, updateable = false)
private Long id;
#OneToMany(mappedBy = "component", cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY, orphanRemoval = true)
private List<ComponentFile> componentFiles;
...
}
public class File {
...
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "file_seq")
#Column(name = "id", nullable = false, insertable = true, updateable = false)
private Long id;
#OneToMany(mappedBy = "file", cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY, orphanRemoval = true)
private List<ComponentFile> componentFiles;
...
}
public class ComponentFile {
...
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "component_id", referencedColumnName = "id", nullable = false, insertable = true, updateable = false)
private Component component;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "file_id", referencedColumnName = "id", nullable = true, insertable = true, updateable = false)
private File file;
...
}
All is working fine except I have indeterminite insert order.
If I insert a component without a file (1 component row and 1 component_file row), the persistence is fine.
If I insert a multiple components associated to a single file (1 component row, 2 file rows, 2 component_file rows), then an error occurs because Hibernate is inserting a component_file row with a null file_id reference. This causes a constraint violation due to a unique constraint as Hibernate is inserting two rows with the same component id and NULL file id which is not allowed (unique constraint on component_file.component_id where component_file.file_id IS NULL).
2016-01-26 10:59:30,506 ERROR [SqlExceptionHelper] - Batch entry 1 insert into component_file (usage, component_id, file_id, id) values ('INCLUDED', '180', NULL, '202') was aborted. Call getNextException to see the cause.
2016-01-26 10:59:30,506 WARN [SqlExceptionHelper] - SQL Error: 0, SQLState: 23505
2016-01-26 10:59:30,507 ERROR [SqlExceptionHelper] - ERROR: duplicate key value violates unique constraint "uidx_component_file_component_id" Detail: Key (component_id)=(180) already exists.
2016-01-26 10:59:30,509 ERROR [BatchingBatch] - HHH000315: Exception executing batch [could not execute batch]
2016-01-26 10:59:30,512 INFO [DbConstraintNameRetriever] - Constraint name retrieval results [Name: uidx_component_file_component_id | Original class: java.sql.BatchUpdateException | Message: ERROR: duplicate key value violates unique constraint "uidx_component_file_component_id" Detail: Key (component_id)=(180) already exists. | Postgres exception?: true | Batch update exception?: true].
Why is this occurring and what is the workaround or alternative methods for solving this type of relationship and persistence?

Try to add:
cascade = { CascadeType.PERSIST, CascadeType.MERGE }
for Component and File #ManyToOne annotations in ComponentFile.

Since you have specified mappedBy attribute in the OneToMany annotation, make sure that you are also setting the other end of the relationship by calling ComponentFile.setComponent(...) or ComponentFile.setFile(...) appropriately whenever you are adding ComponentFile to the arrayList in Component or File entity.
If you still see the same error or if you are already doing it, posting the Entity creation and association logic would help.

Related

Problem with ManyToMany relations in spring data. It changed my table

I had a db with tables SPEC and PARTS.Also I had a table for MANY TO MANY relations. In my project I used spring jdbs template and all works good. Then I decide to change jdbc on SPring data jpa.
My Entities:
#Entity
#Table(name = "PARTS")
public class PartsJpa {
#Id
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "ID_EXPORT", unique = false, nullable = false, updatable = true)
private ExportJpa exportJpa;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "ID_TYPE", unique = false, nullable = false, updatable = true)
private TypesJpa typesJpa;
#Column(name = "DESCRIPTION")
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
private Set<SpecJpa> specJpa;
////////
}
And Spec:
#Entity
#Table(name = "SPEC")
public class SpecJpa {
#Id
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "Creator_ID", unique = false, nullable = false, updatable = true)
private UsersJpa usersJpa;
#Column(name = "DESCRIPTION")
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
private Set<PartsJpa> partsJpa;
////////////////
}
I don't show getters and setters.
It works, but when I start a programm, something in my table was changed and now I can't add in table spec_parts values like(1,3)(1,2).
Mistake:
FK_123: PUBLIC.SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)" Referential integrity constraint violation: "FK_123: PUBLIC.SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)"; SQL statement: INSERT INTO "PUBLIC"."SPEC_PARTS"("ID_SPEC","ID_PARTS")VALUES(?,?)
Maybe I have mistake with creating relations between spec and parts? What problem it can be?
data in spec
ID NAME CREATOR_ID DESCRIPTION CHANGER_ID
1 pc 1 description 1
2 pc2 2 description2 2
data in parts
ID ▼ NAME ID_EXPORT ID_TYPE DESCRIPTION
1 intel core i5 1 1 d1
2 intel core i7 1 1 d2
3 ddr3 2 2 d3
4 ddr4 2 2 d4
5 asus 3 3 d5
data in spec_parts now:
ID_SPEC ID_PARTS
1 1
2 2
so I can't add 1,3 or 2,4
I find a problem, spring date change something and now in table SPEC_PARTS ID_SPEC mapping on PARTS.ID. Why?
As you are using ManyToMany relation, there is a mapping table created named SPEC_PARTS which have referenced columns ID_SPEC and ID_PARTS.These columns value come from SPEC.ID and PARTS.ID. So you can't insert in SPEC_PARTS without creating referenced value because you are trying to do foreign key constraint violation.
Suppose if there is a row in SPEC with id value 1 and there is a row in PARTS with id value 2. Then you can insert in SPEC_PARTS with value like (1,2).
So, first, add data in SPEC and PARTS then map them in SPEC_PARTS.
And you can remove #JoinTable from one side, you don't need to define it both side.
Update:
Problem is SpecJpa class relation. Here you are using SPEC_PARTS.ID_SPEC as foriegn key for PARTS.ID and SPEC_PARTS.ID_PARTS as foriegn key for SPEC.ID which is fully reversed what you do in PartsJpa class.
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
That's why this error say
SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)";
There is no SPEC.ID value 3 exist in the database.
Solution:
Remove #JoinTable from SpecJpa class as you don't need to specify both side.
And remove the wrong relation of the foreign key from database also.

#JoinColumn gives error "column with logical name ID not found" in Hibernate 4

I am having below annotation on column in my entity class.
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "INQUIRYID", referencedColumnName="ID", updatable=false, insertable=false)
private MyTable myInquiry;
It was giving below error on runtime.
column with logical name ID not found in entity class
As the referenced column is a primary key, I removed the referencedColumnName attribute and updated to below
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "INQUIRYID", updatable=false, insertable=false)
private MyTable myInquiry;
This works perfectly with Hibernate 5.3 but as soon as I go to hibernate 4 , I see some anomalies.
In hibernate 5 I get this issue only with columns which are referring some ID(PK) of another class. However, in hibernate 4 I see this error for non-pk columns as well.
I start to get the same error for columns which are referring non primary keys.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "AB", referencedColumnName = "AB")
private MyTable someData;
Above code gives me error in hibernate 4
Column with logical name AB not found in entity class.
Here, AB is a non primary key column .So I can't remove referencedColumnName attribute.
Is it purely because of hibernate version or there is a different reason for such behavior ?
Referred this : Similar Issue
There is a bug for hibernate 4.1.7
A workaround for this issue is surrounding the column name with gave accents.
Unable to find column with logical name: id in
org.hibernate.mapping.Table(template) and its related supertables and
secondary tables
#ManyToOne #JoinColumnsOrFormulas({ #JoinColumnOrFormula(column =
#JoinColumn(name = "template", referencedColumnName = "id")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "'custom'",
referencedColumnName = "type")) }) This is caused by identifying the
column names within the logicalToPhysical map of
TableColumnNameBinding. Within this map the column names are
surrounded by grave accents (`id`) while the check do a lookup to the
map with plain column name (id).
A workaround for this issue is surrounding the column name with gave
accents.
#ManyToOne #JoinColumnsOrFormulas({ #JoinColumnOrFormula(column =
#JoinColumn(name = "template", referencedColumnName = "`id`")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "'custom'",
referencedColumnName = "`type`")) })

Hibernate join using non-primary key column

I have two tables:
users:
user_id (primary)
ip (unique)
etc ..
services_to_ip
id (primary)
service_id
ip
etc ..
In class User:
#OneToMany()
#JoinColumn(name = "ip", insertable = false, nullable = false, updatable = false)
public List<QPlanService> getPlanServices() {
return planServices;
}
Using MySQL query log we get something like that:
SELECT *
FROM services_users planservic0_
LEFT OUTER JOIN services qservice1_
ON planservic0_.service_id = qservice1_.id
WHERE planservic0_.ip = 777
In WHERE condition the 'user_id' field used (the default field is primary key - users.id) (user_id=777).
How can I specify that I need to take the value of the 'ip' field from User entity, not 'user_id'?
I will be grateful for any help!
JoinColumn will only specify the name of the column holding the foreign key, so changing the name of joincolumn absolutely will not help.
Hibernate will be using the primary key by default for joining, if you want to override this you can simply use referencedColumnName in your relation, but the referenced column should be unique
As Amer Qarabsa mentioned above:
#OneToMany()
#JoinColumn(name = "ip", insertable = false, nullable = false, updatable = false, referencedColumnName="ipcolumnName")
public List<QPlanService> getPlanServices() {
return planServices;
}

Join 3 tables using javax.persistence in a OneToMany relationship

The 3 tables are "analyticalgroups", "labinstructions", "observedproperties". Each table has an "id" primary key column.
I'd like to use a 4th table ("analyticalgroups_observedproperties_labinstructions") to store the OneToMany relationship. Ultimately I'd like the output to be structured something like this:
analyticalGroup: {
id: "...",
observedPropertyLabInstructions: [
{observedProperty, labInstruction},
{observedProperty, labInstruction},
{observedProperty, labInstruction},
...etc...
]
}
I've followed some examples online, but can't get this to work. The problem is when I try this I get the following error:
"message" : "Error occurred at repository: PSQLException: ERROR: column observedpr0_.observedpropertyentitylabinstructionentitymap_id does not exist\n Position: 6550",
"errorCode" : "gaia.domain.exceptions.RepositoryException",
Here's the structure for the join table.
CREATE TABLE analyticalgroups_observedproperties_labinstructions
(
analyticalgroupid character varying(36) NOT NULL,
labinstructionid character varying(36) NOT NULL,
observedpropertyid character varying(36) NOT NULL,
CONSTRAINT fk_analyticalgroups_observedproperties_labinstructions_groupid FOREIGN KEY (analyticalgroupid)
REFERENCES analyticalgroups (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE,
CONSTRAINT fk_analyticalgroups_observedproperties_labinstructions_labinstr FOREIGN KEY (labinstructionid)
REFERENCES labinstructions (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE,
CONSTRAINT fk_analyticalgroups_observedproperties_labinstructions_observed FOREIGN KEY (observedpropertyid)
REFERENCES observedproperties (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE
)
#Entity
#Data
public class AnalyticalGroupEntity {
public static final String ENTITY_NAME = "analyticalGroups";
public static final String JOIN_OBSERVEDPROPERTIES_LABINSTRUCTIONS_TABLE_NAME =
ENTITY_NAME +
IDomainEntity.UNDERSCORE +
ObservedPropertyEntity.ENTITY_NAME +
IDomainEntity.UNDERSCORE +
LabInstructionEntity.ENTITY_NAME;
#Id
#Column(name = IDomainEntity.ID_KEY, nullable = false, columnDefinition = IDomainEntity.COLUMN_TYPE_UUID)
private String id;
#OneToMany
#JoinTable(
name = JOIN_OBSERVEDPROPERTIES_LABINSTRUCTIONS_TABLE_NAME,
joinColumns = #JoinColumn(name = LabInstructionEntity.ID_KEY, referencedColumnName = IDomainEntity.ID_KEY, table = "labinstructions")
)
#MapKeyJoinColumn(name = ObservedPropertyEntity.ID_KEY, referencedColumnName = IDomainEntity.ID_KEY, table = "observedproperties")
private Map<ObservedPropertyEntity, LabInstructionEntity> observedPropertyLabInstructions;
}
Hopefully I've laid this all out as clearly as necessary.
Your help is much appreciated. Thanks for reading!
edit Actually... it turns out this doesn't work. It successfully gets the data I want, buuuuut it also deletes every row in the join table whenever I make a GET request *flip table*
So bizarre!
#OneToMany
#JoinTable(
name = JOIN_OBSERVEDPROPERTIES_LABINSTRUCTIONS_TABLE_NAME,
joinColumns = #JoinColumn(name = "analyticalgroupid", referencedColumnName = IDomainEntity.ID_KEY, table = "labinstructions"),
inverseJoinColumns = #JoinColumn(name = LabInstructionEntity.ID_KEY, referencedColumnName = IDomainEntity.ID_KEY, table = "labinstructions")
)
#MapKeyJoinColumn(name = ObservedPropertyEntity.ID_KEY, referencedColumnName = IDomainEntity.ID_KEY, table = "observedproperties")
private Map<ObservedPropertyEntity, LabInstructionEntity> observedPropertyEntityLabInstructionEntityMap;

#UniqueConstraint and #Column(unique = true) in hibernate annotation

What is difference between #UniqueConstraint and #Column(unique = true)?
For example:
#Table(
name = "product_serial_group_mask",
uniqueConstraints = {#UniqueConstraint(columnNames = {"mask", "group"})}
)
And
#Column(unique = true)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private ProductSerialMask mask;
#Column(unique = true)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private Group group;
As said before, #Column(unique = true) is a shortcut to UniqueConstraint when it is only a single field.
From the example you gave, there is a huge difference between both.
#Column(unique = true)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private ProductSerialMask mask;
#Column(unique = true)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private Group group;
This code implies that both mask and group have to be unique, but separately. That means that if, for example, you have a record with a mask.id = 1 and tries to insert another record with mask.id = 1, you'll get an error, because that column should have unique values. The same aplies for group.
On the other hand,
#Table(
name = "product_serial_group_mask",
uniqueConstraints = {#UniqueConstraint(columnNames = {"mask", "group"})}
)
Implies that the values of mask + group combined should be unique. That means you can have, for example, a record with mask.id = 1 and group.id = 1, and if you try to insert another record with mask.id = 1 and group.id = 2, it'll be inserted successfully, whereas in the first case it wouldn't.
If you'd like to have both mask and group to be unique separately and to that at class level, you'd have to write the code as following:
#Table(
name = "product_serial_group_mask",
uniqueConstraints = {
#UniqueConstraint(columnNames = "mask"),
#UniqueConstraint(columnNames = "group")
}
)
This has the same effect as the first code block.
From the Java EE documentation:
public abstract boolean unique
(Optional) Whether the property is a unique key. This is a shortcut for the
UniqueConstraint annotation at the table level and is useful for when the unique key
constraint is only a single field. This constraint applies in addition to any constraint
entailed by primary key mapping and to constraints specified at the table level.
See doc
In addition to Boaz's answer ....
#UniqueConstraint allows you to name the constraint, while #Column(unique = true) generates a random name (e.g. UK_3u5h7y36qqa13y3mauc5xxayq).
Sometimes it can be helpful to know what table a constraint is associated with. E.g.:
#Table(
name = "product_serial_group_mask",
uniqueConstraints = {
#UniqueConstraint(
columnNames = {"mask", "group"},
name="uk_product_serial_group_mask"
)
}
)
In addition to #Boaz's and #vegemite4me's answers....
By implementing ImplicitNamingStrategy you may create rules for automatically naming the constraints. Note you add your naming strategy to the metadataBuilder during Hibernate's initialization:
metadataBuilder.applyImplicitNamingStrategy(new MyImplicitNamingStrategy());
It works for #UniqueConstraint, but not for #Column(unique = true), which always generates a random name (e.g. UK_3u5h7y36qqa13y3mauc5xxayq).
There is a bug report to solve this issue, so if you can, please vote there to have this implemented. Here:
https://hibernate.atlassian.net/browse/HHH-11586
Thanks.

Categories