ManyToMany Relation in JPA with Index possible? - java

I want to have a many to many relation between to Entities and I'm using a junction table for it right now on the MySql end. Now I need a JPA solution like an index which allows me to use the id's of both of those tables/ entities as a Key/ Index without the Entities itself to avoid some cross package references. When querying I have the ID of EntityA and want to find the ID of EntityB with it, nothing more. This is how I thought it might work:
(It doesn't because I don't have IDs for the JunctionEntity and if I use IDs then obviously the entries have to be unique when the only unique Thing should be both entries together. A PK Class isn't working either since it would still require said references to both entities)
EntityA:
#Entity
#Table(name = "EntityA")
})
public class EntityA {
#Id
private int id;
}
EntityB:
#Entity
#Table(name = "EntityB")
})
public class EntityB {
#Id
private int id;
}
JunctionEntity:
#Entity
#Table(name = "junction", indexes = {
#Index(name = "ix_a_b", columnList = "a_id, b_id")
})
public class JunctionEntity {
private int a_id;
private int b_id;
}
MySQL for JunctionTable:
CREATE TABLE junction (
a_id INT NOT NULL,
b_id INT NOT NULL,
CONSTRAINT junction_fk_a FOREIGN KEY (a_id) REFERENCES entityA (id),
CONSTRAINT junction_fk_b FOREIGN KEY (b_id) REFERENCES entityB (id)
);
CREATE UNIQUE INDEX ix_a_b
ON junction (a_id, b_id);

You can add a primary key and search in junction table using a_id, b_id or both.
CREATE TABLE junction (
id INT NOT NULL,
a_id INT NOT NULL,
b_id INT NOT NULL,
CONSTRAINT junction_fk_a FOREIGN KEY (a_id) REFERENCES entityA (id),
CONSTRAINT junction_fk_b FOREIGN KEY (b_id) REFERENCES entityB (id)
);
You don't need to know the id of junction table. You can query the junction like this:
select b_id from junction where a_id = ?;

Related

fk column created in child entity with ManyToOne in parent for InheritanceType.JOINED

I'm having a problem with Hibernate entities inheritance where it creates a copy of fk column of a many-to-one relation without updating it.
#Data
#Entity
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "vehicle")
private Set<Human> owner;
}
#Data
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer age;
#ManyToOne
private Vehicle vehicle;
}
#Data
#Entity
public class Human extends Person {
private String fullName;
}
With this entities it creates:
create table human (
full_name varchar(255),
id bigint not null,
vehicle_id bigint,
primary key (id)
) engine=InnoDB
create table person (
id bigint not null auto_increment,
age integer,
vehicle_id bigint,
primary key (id)
) engine=InnoDB
create table vehicle (
id bigint not null auto_increment,
primary key (id)
) engine=InnoDB
alter table human
add constraint FKqctp8ycdo4mm7fsou0v4jalx3
foreign key (id)
references person (id)
alter table person
add constraint FKgo297ke05qjubymwq6unmcnot
foreign key (vehicle_id)
references vehicle (id)
Why it declares Human.vehicle_id? I don't need it and hibernate dosn't sync it with Person.vehicle_id. The worst part is when hibernate Join Human with Vehicle it check Human.vehicle_id = Vehicle.id
How can I resolve this issue? I can't change InheritanceType because it will be huge migration.
Test case:
final var car = Vehicle.builder().build();
vehiclesRepo.save(car);
final var mario = Human.builder()
.fullName("Mario Rossi")
.age(20)
.vehicle(car)
.build();
humansRepo.save(mario);
Vehicle
id|
--+
1|
Person
id|age|vehicle_id|
--+---+----------+
2| 20| 1|
Human
full_name |id|vehicle_id|
-----------+--+----------+
Mario Rossi| 2| |
-- generated query for findAll
select
human0_.id as id1_1_,
human0_1_.age as age2_1_,
human0_1_.vehicle_id as vehicle_3_1_,
human0_.full_name as full_nam1_0_
from
human human0_
inner join
person human0_1_
on human0_.id=human0_1_.id
select
vehicle0_.id as id1_2_0_
from
vehicle vehicle0_
where
vehicle0_.id=?
select
owner0_.vehicle_id as vehicle_3_0_0_,
owner0_.id as id2_0_0_,
owner0_.id as id1_1_1_,
owner0_1_.age as age2_1_1_,
owner0_1_.vehicle_id as vehicle_3_1_1_,
owner0_.full_name as full_nam1_0_1_,
vehicle1_.id as id1_2_2_
from
human owner0_
inner join
person owner0_1_
on owner0_.id=owner0_1_.id
left outer join
vehicle vehicle1_
on owner0_1_.vehicle_id=vehicle1_.id
where
owner0_.vehicle_id=?
owner0_.vehicle_id=? here is where it fails the join because vehicle_id in human table is never populated, in this test scenario it's a bit better than real one because it actually made the join right and fails only in where conditions, in real use case it just use on 1=1 in join.
There is a discrepancy in database/system design. If only Humans can be Vehicle owners - you need to move Vehicle field into Human class (it solves your issue). Or (in case all Persons can do it) - you need to change Set{Human} to Set{Person} (it solves the issue too)
To avoid the vehicle_id in children tables the association in Vehicle must use Person as base type explicitly in field type or using OneToMany.targetEntity
This resolve my problem partially because it added Robot amoung owners, I don't need that. I can use a discriminator column with a #Where condition or leave children vehicle_id and sync it programatically.

Cannot add or update a child row: a foreign key constraint fails error when trying to save an entity and its relational entity

Well, i have the following tables in my database:
CREATE TABLE movie (
movie_id INTEGER NOT NULL AUTO_INCREMENT,
translated_title VARCHAR(150),
original_title VARCHAR(150),
plot MEDIUMTEXT,
genre VARCHAR(150),
country VARCHAR(250),
language VARCHAR(100),
upvotes INTEGER,
premiere_year INTEGER(4),
duration_minutes Integer,
CONSTRAINT movie_pk PRIMARY KEY (movie_id));
CREATE TABLE actor (
actor_id INTEGER NOT NULL AUTO_INCREMENT,
name VARCHAR(150),
nationality VARCHAR(150),
born_date DATE,
CONSTRAINT actor_pk PRIMARY KEY (actor_id));
CREATE TABLE actor_movie (
actor_id INTEGER,
movie_id INTEGER,
FOREIGN KEY (actor_id) REFERENCES actor (actor_id) ON DELETE CASCADE,
FOREIGN KEY (movie_id) REFERENCES movie (movie_id) ON DELETE CASCADE);
and the following entities:
#Entity(name = "movie")
public class Movie implements Serializable {
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "actor_movie",
inverseJoinColumns = { #JoinColumn(name = "actor_id", insertable = true, updatable = true)},
joinColumns = { #JoinColumn(name = "movie_id", insertable = true, updatable = true) })
private Set<Actor> actors;
and:
#Entity(name = "actor")
public class Actor implements Serializable {
#ManyToMany(mappedBy="actors")
private Set<Movie> movies;
When i create a Movie object and set all properties, including an Actor and call the save method, i get: "Cannot add or update a child row: a foreign key constraint fails (movieow.actor_movie, CONSTRAINT FKsc6u9gs0762qyrnyfwp9d5q2b FOREIGN KEY (movie_id) REFERENCES actor (actor_id))"
I want to when i save a Movie object, it saves in cascade the actor and actor_movie tables.
What am i doing wrong?
This sound like the problem that the different entities are not synchronised (I did not test it for your case). The idea is to create add and delete methods for your set which add or delete the entity in the respective associated object.
For a clear example of #ManyToMany relationships, you could have a look at Vlad Mihalcea's article about these relationships. About synchronisation and cascading, he also wrote an article.

Spring JPA OneToOne FK as PK Cascade.Remove

I've got two tables, b and a:
they have a one-to-one bidirectional relationship
a has a foreign key to b that defines this relationship
this foreign key is also considered as a primary key for a, and a JPA #ID
I want a cascade removal that deletes the related b when a is deleted
in MySQL, a's b_id is NOT NULL
The problem is that when I delete my A object with JPA repository, I get a ConstraintViolationException on its foreign key.
I would expect that both a and b rows are deleted (cleverly starting with a's one).
How could I work around this knowing that I want to keep:
my DB schema the same
the cascade removal from a to b
the b id being the JPA #Id for a
CREATE TABLE `b` (
`dbid` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`dbid`),
);
CREATE TABLE `a` (
`b_id` int(11) NOT NULL,
KEY `b_fk` (`b_id`),
CONSTRAINT `b_fk` FOREIGN KEY (`b_id`) REFERENCES `b` (`dbid`),
);
#Entity
#Table(name = "a")
public class A {
#Id
#Column(name = "b_id")
#GeneratedValue(generator = "gen")
#GenericGenerator(name = "gen", strategy = "foreign", parameters = #Parameter(name="property", value="b"))
private Integer bId;
#OneToOne(cascade = CascadeType.REMOVE)
#PrimaryKeyJoinColumn
private B b;
}
#Entity
#Table(name = "b")
public class B {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name = "dbid")
private Integer id;
#OneToOne(mappedBy = "b")
private A a;
}
[EDIT] After all discussions in answer comments and re-reading my question, the proposals with orphanRemoval indeed are in scope and work.
If you want to delete object of B, whenever the associated A is deleted (it's the fourt point of your wishlist:
I want a cascade removal that deletes the related b when a is deleted
then you need to change your mapping in A to:
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#PrimaryKeyJoinColumn
private B b;
In terms of just the MySQL side of your implementation, the records in table B have no 'knowledge' of any record in table A. In the database the relationship is unidirectional
The native cascade functionality exists to prevent foreign key errors, by telling the DB what to do when deleting a record would leave a foreign key pointing nowhere. Deleting a table A record would not cause a foreign key error in any table B records, so any native cascade functionality would not be triggered
To reiterate; You cannot keep the schema the same, and the cascade removal from a to b, because you don't actually have the cascade removal from a to b
You also mentioned in the comments that some table B records can exist without a table A records which isn't in the original question
To obtain the automatic deletion of table B records you describe, you have a few options with regards to the DB:
Swap the relation over - Remove the current foreign key and add a nullable foreign key column in table B that references the primary key of table A. You can then put a cascade delete on this foreign key. Keep the new column null for the table B records that do not 'belong' to a table A record. You could also add a unique index to this column to secure a one to one relationship
Add a DB trigger - On deletion of a table A record, add a DB trigger that removes the referenced table B record
Add a DB procedure - Add a procedure that deletes a table A record and then the referenced table B record in turn, probably within a transaction. Going forwards, only delete table A records using the procedure
Don't solve the problem at the DB level - Basically the same as option 3, but move the procedure logic out of the DB layer into the application logic
There may be something in JPA that solves your dilemma out of the box, but under the hood it will be doing one of the above (not option 1 and probably option 4)
In order to achieve what you have asked, I have tweaked your tables as follows:
CREATE TABLE b (
dbid INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE a (
b_id int(11) NOT NULL PRIMARY KEY REFERENCES b(dbid) ON DELETE CASCADE
);
CASCADE DELETE wasn't added in your DDL.
This will enable cascade delete. To delete the b record on deletion of a I made following changes in class A:
#Entity
#Table(name = "a")
public class A {
#Id
#Column(name = "b_id")
#GeneratedValue(generator = "gen")
#GenericGenerator(name = "gen", strategy = "foreign", parameters = #Parameter(name="property", value="b"))
private Integer bId;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#PrimaryKeyJoinColumn
private B b;
}
Find link here to the working solution.
Can you try in class B to add the following
#OneToOne(mappedBy = "b", cascade = CascadeType.REMOVE)
private A a;
In addition, if in the database you have only a foreign key "a has a foreign key to b" can you also make a foreign key from b to a as well.
#OneToOne(mappedBy = "b",cascade = CascadeType.ALL,fetch = FetchType.LAZY,orphanRemoval=true )
private A a;

Hibernate #SecondaryTable - Specifying foreign key of primary table

I have two tables:
language
CREATE TABLE language (
id BIGSERIAL,
name TEXT NOT NULL UNIQUE,
PRIMARY KEY (id)
);
translation
CREATE TABLE translation (
id BIGSERIAL,
language_id BIGINT REFERENCES language (id),
translation_key TEXT NOT NULL,
translation_value TEXT NOT NULL,
PRIMARY KEY (id)
);
And I would like to get such entity, where translation table (primary table) joins language table by language_id from primary table. Problem: at the moment it joins by translation PK(id).
#Entity
#Table(name = "translation")
#SecondaryTable(name = "language", pkJoinColumns = #PrimaryKeyJoinColumn(name = "id"))
public class Translation {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(table = "language", name = "name")
// ON translation.language_id = language.id
private String language;
#Column(name = "translation_key")
private String translationKey;
#Column(name = "translation_value")
private String translationValue;
// getters and setters
}
Where I should specify it in my code to do it correctly?
SQL example: SELECT t.id, l.name, translation_key, translation_value FROM translation t INNER JOIN language l on t.language_id = l.id;
You cannot use #SecondaryTable for the purpose you describe.
#SecondaryTable is used when a single entity is spread across multiple tables. Each of these 'pieces' of the entity must be privately owned by the entity, and is in a one-to-one relation with every other 'piece'.
If you want a many-to-one relation between translations and languages, you need to use #ManyToOne (and create a separate Language entity) instead.
#SecondaryTable(name = "language")
this way it is going to generate value for the translation id and insert it to the language foreign key automatically if you specify pkJoinColumn it is going to relate the tables through the primary key while if you don't mention that, it would do it through the foreign key. After that you need to create a trigger and sequence for the language table id column. It should work.

JPA OneToOne unidirectional mapping

I have 2 entities : Field and ViewOptions
fields and methods not related to the problem are omitted
Table structure:
CREATE TABLE FIELD (
ID INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (ID),
);
CREATE TABLE VIEW_OPTIONS (
ID INT NOT NULL AUTO_INCREMENT,
FIELD_ID INT NOT NULL,
PRIMARY KEY (ID),
INDEX (FIELD_ID ASC),
CONSTRAINT
FOREIGN KEY (FIELD_ID)
REFERENCES FIELD (ID)
ON DELETE NO ACTION
ON UPDATE NO ACTION);
Mapping:
#Table(name = "FIELD")
#Entity
public class Field {
#OneToOne(mappedBy ="field")
ViewOptions viewOptions;
#Table(name = "VIEW_OPTIONS")
#Entity
public class ViewOptions {
#OneToOne
#JoinColumn(name = "FIELD_ID")
Field field;
In this relation the owner side is ViewOptions and the inverse side is Field.
What i want to do is to swap sides - make Field the owner side.
But if i mark viewOptions like this
#OneToOne
#JoinColumn(name = "FIELD_ID")
ViewOptions viewOptions;
Hibernate expecting join column in Field table.
Is there any way to tell hibernate to search for join column in VIEW_OPTIONS?
No, that's just not how it works. If you want the Field object to be the owner of the relationship, then the FIELD table will have to have a column for the VIEW_OPTIONS id.
In a bidirectional relationship, one of the sides (and only one) has to be the owner: the owner is responsible for the association column(s) update. To declare a side as not responsible for the relationship, the attribute mappedBy is used. mappedBy refers to the property name of the association on the owner side.
The join column annotation here is optional an if no #JoinColumn is declared on the owner side, the defaults apply. A join column(s) will be created in the owner table and its name will be the concatenation of the name of the relationship in the owner side, _ (underscore), and the name of the primary key column(s) in the owned side.
So, if you want to make Field the owner side :
#Table(name = "FIELD")
#Entity
public class Field {
#OneToOne
ViewOptions viewOptions;
#Table(name = "VIEW_OPTIONS")
#Entity
public class ViewOptions {
#OneToOne(mappedBy ="viewOptions")
Field field;

Categories

Resources