I have 2 entities in a OneToOne relationship:
A User entity :
#Entity
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne
#JoinColumn()
private StudentInfo studentInfo;
}
And a StudentInfo entity:
#Entity
public class StudentInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne(cascade = CascadeType.REMOVE)
#JoinColumn()
private User user;
}
When I delete the first StudentInfo instance from the database (with id=1) using studentRepository.deleteById((long) 1) ; , the instance does get deleted, so does the User associated with it, but the problem is that the ids of the other objects in the database don't get updated.
After deleted a student, the users in the database look like this :
And the students remaining (only one):
How can I make so that the ids are automatically updated on delete ?
(2, 3, 4) -> (1, 2, 3).
Apparently this is quite a dumb question because while the ids in a database do have to be unique for each line in a table, they don't have to be consecutive.
As such, when a registry (a line) in a database table is deleted, the other registries's ids don't get updated because there is no need for it. This is true for all sql databases and has nothing to do with Spring or JPA.
Related
I'm learning lazy loading with JPA and Hibernate in a Spring Boot project. It is a simple project with 2 tables: student and passport. It is a One to One relationship and I see that the lazy loading is not working.
This is the code
student entity:
import javax.persistence.*;
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
#OneToOne(fetch = FetchType.LAZY)
private Passport passport;
public Student() {
}
}
Passport entity:
import javax.persistence.*;
#Entity
public class Passport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String number;
public Passport() {
}
}
I'm running this method using debugger from Intellij and It looks like an eager loading because it select students and passports when this entityManager.find(Student.class, 20001L) is called. How can I do this to be a lazy loadung? Thank you!
In LAZY loading hibernate will try to make either a proxy object containing the id of the LAZY loaded entity.In order to do it needs to get the Foreign key in the Student table,which is not available in your example because you didn't specify the #JoinColumn, and depending on the value it will decide to either make a proxy object wrapping the passeport object and ready to be queried ,or assign null to it.
Since there's no foreign key present in the Student table ,hibernate will always treat that OneToOne EAGER ,because it has no clue if a passeport associated with student exists or not.
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.class:
#Entity
#Data
public class A {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
Integer id;
String someStringInAclass;
#OneToMany(cascade = CascadeType.ALL)
List<SomeObject> someObjectsList;
}
I have SomeObject.class:
#Entity
#Data
public class SomeObject {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
Integer id;
String someString;
Integer someInteger;
}
I save A object in database:
SomeRepository.save(A);
And tables look like this:
Then I save A object again with different values in B object:
SomeRepository.save(A);
Then tables look like this:
Table B has extended correctly, but table A_B just overrode the B_id instead putting the new one 4 rows.
Then when I retrieve the object A from database I obtain only last 4 values from B object.
How to make it work with saving new values (adding new rows to A_B) without overriding?
EDIT:
I added A_id as a foreign key to B table. Again - first save is stored properly:
Then after second save with different values for B, it modifies values for A_id column for firstable inserted 4 values (to null). So the table looks like this:
Hibernate handle unidirectional relationships very inefficiently please try to use bidirectional relationship.
Regarding above issue, you don't need third table here, you can avoid creation of third table by adding #JoinColumn as below :
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_a_id")
List<SomeObject> someObjectsList;
this will add column "fk_a_id" in SOME_OBJECT table.
Since the object is already saved and has an assigned id, JPA assumes you mean the same entry in the database. You need to clone the object:
#Entity
#Data
public class A {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
Integer id;
String someStringInAclass;
#OneToMany(cascade = CascadeType.ALL)
List<SomeObject> someObjectsList;
public A(A source) {
this.someStringInAclass = source.someStringInAClass;
this.someObjectsList = new ArrayList<SomeObject>();
for (SomeObject o : source.someObjectsList) {
this.someObjectsList.add(new SomeObject(o));
}
}
}
Use it as follows:
SomeRepository.save(a);
A aClone = new A(a);
SomeRepository.save(aClone);
I added: (nullable = false) parameter for #JoinColumn the problem is solved.
So above the list there is now:
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(nullable = false)
List<SomeObject> someObjectsList;
I am absolutly new in Hibernate development and I have the following problem.
I have 2 entity classes that maps 2 DB tables:
1) The first entity class (the main one) is named KM_ProjectInfo and map a DB table named KM_PROJECT.
2) The second entity class is named KM_ProjectInfoStatus and map a DB table named KM_PROJECT_INFO_STATUS.
So the second one represent a specific field of the first one (a status of the row representd by an instance of the KM_ProjectInfo class). Infact I have something like this:
1) KM_ProjectInfo class:
#Entity
#Table(name = "KM_PROJECT")
public class KM_ProjectInfo implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfo;
#Column(name = "name")
private String name;
#Column(name = "technology")
private String technology;
#ManyToOne
#JoinColumn(name = "idCountry")
private KMCountry country;
#Column(name = "power")
private long power;
#Column(name = "cod")
private String cod;
#ManyToOne
#JoinColumn(name = "idProjectInfoStatus")
private KM_ProjectInfoStatus status;
// GETTERS & SETTERS
}
2) KM_ProjectInfoStatus:
#Entity
#Table(name = "KM_PROJECT_INFO_STATUS")
public class KM_ProjectInfoStatus implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfoStatus;
#Column(name = "foldertech")
private Long foldertech;
#Column(name = "folderproject")
private Long folderproject;
// GETTERS & SETTERS
}
So, as you can see in the previous snippet, the KM_ProjectInfoStatuss is a field of the KM_ProjectInfo because I want that it contains the primary key of this table as foreign key.
In the logic of my application I want that at one row of the KM_PROJECT table (so at one instance of the KM_ProjectInfo entity class) is associated a single row of the KM_PROJECT_INFO_STATUS (one instance of the KM_ProjectInfoStatus entity class) because it represent a specific status for the KM_PROJECT row.
In my code I have:
#ManyToOne
#JoinColumn(name = "idProjectInfoStatus")
private KM_ProjectInfoStatus status;
but I think that is wrong because at one row of my first table it is associated a specific single row of the second table. But maybe I am missing something about how Hibernate work.
Can you help me to understand what I am missing? What it work? Why I have #ManyToOne instead #OneToOne?
Tnx
It all depends on how you want to model things. In terms of Database structure, OneToOne and ManyToOne are implemented in the same way:
One or more JoinColumns which makes a foreign key pointing to the primary key of the other table.
So both solutions correctly map to your database, but it depends if you want to allow several KM_ProjectInfo to point to the same KM_ProjectInfoStatus, or only allow a single one.
Note that, even though you would declare a OneToOne, you could still end up with multiple KM_ProjectInfo pointing to the same KM_ProjectInfoStatus if you don't manipulate Hibernate properly.
Here you did not declare the reverse relationship, but if you did, the declaration would have to be different:
In case of a OneToOne, you would have a KM_ProjectInfo member
In case of a OneToMany (reverse of ManyToOne), you would have a Collection<KM_ProjectInfo> member
From the description it seems you want to have one-to-one relationship. That is the project entity should have its very own status not shared by any other project. You could achieve this by using #OneToOne as below.
#Entity
#Table(name = "KM_PROJECT")
public class KM_ProjectInfo implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfo;
#OneToOne
#JoinColumn(name = "idProjectInfoStatus")
private KM_ProjectInfoStatus status;
}
#Entity
#Table(name = "KM_PROJECT_INFO_STATUS")
public class KM_ProjectInfoStatus implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfoStatus;
#OneToOne(mappedBy="idProjectInfoStatus")
private KM_ProjectInfo project;
}
This way you can have specific status for the KM_PROJECT.
Coming back to #ManyToOne, you will want to have this if you want to share the same status with multiple projects, but that's not what you want in your case. I have tried to explain mappings in simple way here One-to-One mapping.
If I have this 3 entities :
#Entity
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
private String name;
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
public class Course {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
#OneToMany
private List<Student> students;
private String name;
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
public class Group {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
#OneToMany
private List<Student> students;
private String name;
}
How can I delete students with a JPQL query ?
I try
DELETE FROM Student s
WHERE s.name = "John Doe"
But I have
Cannot delete or update a parent row: a foreign key constraint fails (database, CONSTRAINT FK_course_student_students_ID FOREIGN KEY (students_ID) REFERENCES student (ID))
I need to do this in pure JPQL for performance, I can't do an entity.remove, because I have 10000 John doe and I need to delete them in a second.
Why JPQL doesn't say : "Hey, let's remove this john doe from this biology course, he doesn't exist" instead of "Hey, the biology course is so important that no student can be remove from this course ! "
What I am missing and what sort of annotation I have to use ?
Thanks !
Edit : Add a #JoinColumn to the OnToMany relationship could work, unless the students are referenced by different tables...
By default unidirectional one-to-many relationship is mapped via join table. If you don't have any special requirements about using join talbe you can use foreign key in Student instead, it can be configured as follows:
#OneToMany
#JoinColumn
private List<Student> students;
It also should solve your problem with constrain violation.
Sometimes you can clear the references to the objects being deleted in a deleted all using an update all query.
You can also configure you constraint on the database to cascade or null on delete to avoid constraint issues.