Hibernate OneToMany and related deletion constraints - java

I have two related classes:
#Entity
#Table(name = "projects")
public class Project {
#Expose
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
...
#Expose
#OneToMany(mappedBy = "project", fetch = FetchType.EAGER)
private Set<WorkPackage> workPackages;
}
and
#Entity
#Table(name = "work_packages")
public class WorkPackage {
#Expose
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Expose(serialize = false)
#ManyToOne
#JoinColumn(name = "project_id")
private Project project;
}
Now the issue:
i have Project object which contains one or more WorkPackage objects stored in DB.
When i delete the project there is no any violation. Project is deleted, but related WPs are still in DB and referring to not existing (after deletion) project. This isn't behavior i expect. I need a violation when i try to delete project that contains at least one WP.
Apparently I can do it in DB directly but i wonder if there is a way to do it through Hibernate and Annotations?
Thanks!
SOLUTION
The problem was Hibernate created Tables with MyISAM engine, which doesn't allow to generate FK apparently.
So i just changed
property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"
to
property name="hibernate.dialect" value="org.hibernate.dialect.MySQL55Dialect"
and it works ( now Hibernate uses InnoDB engine)

Try this:
...
#Expose
#OneToMany(mappedBy = "project", fetch = FetchType.EAGER,
cascade=CascadeType.ALL,orphanRemoval=true)
With orphanRemoval=true all child records of a parent record in a database will be deleted after the parent gets deleted.

I'm assuming you added the Fk after some test run
The problem lies within the property hibernate.hbm2ddl.auto = update
as it won't modify existing table column definitions.
It will only add a column that doesn't already exist.
It will not modify or delete a column that is already present in db.
If you drop the table in db and try it again (if you are working on a test database obviously) you should find the foreign key correctly created.

Related

How to save spring entities solely using the foreign key of an associated object

I have two spring entities, job and employer, which have a bidirectional association.
Job Entity
#Entity
#Table(name = "job")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Job {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "job_id", nullable = false)
private Integer id;
#Column(name = "title", nullable = false)
private String title;
#ManyToOne(optional = false)
#JoinColumn(name = "employer_employer_id", nullable = false)
#JsonBackReference
private Employer employer;
}
Employer Entity
#Entity
#Table(name = "employer")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Employer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "employer_id", nullable = false)
private Integer employerId;
#Column(name = "name", nullable = false)
private String name;
//Mapped by indicates the inverse side of the relationship.
#OneToMany(mappedBy = "employer", orphanRemoval = true)
#JsonManagedReference
private List<Job> jobs = new ArrayList<>();
}
I also have two simple CRUD repositories
Let's say that I have an existing employer object saved in the database. In my jobs service, I want to have a method that creates a new job. My question is, what is the correct Spring boot way to save a new job entry in the database where the employee id foreign key relates back to that existing job in the database.
Here is my first attempt, and it works, however it doesn't seem very efficient. Why should I have to retrieve the entire employer object from the database, when I really just want to specify the employer ID of the job I am trying to save? Is there a way I can avoid making this extra database call, and when we are saving the job to the database, just easily specify an existing employer ID on that new job we are saving to the database? Or is it Spring best practice to have to save the entire employee object here, even if it already exists?
Employer e = employerRepository.findById(0).orElseThrow(IllegalArgumentException::new);
job1.setEmployer(e);
jobRepository.save(job1);
Best way is use getOne so you don't even have to fetch the empoyer
Employer e = employerRepository.getOne(id);
job1.setEmployer(e);
jobRepository.save(job1);
If employer does t exist an exception will be thrown when you save job.
getOne is deprecated in later versions of jpa so use this instead
JpaRepository#getReferenceById(ID)
Good/progressive question:
Why should I have to retrieve the entire employer object from the database, when I really just want to specify the employer ID of the job I am trying to save?
We don't have to! We can:
Employer empler =
entityManager.getReference(Employer.class, 0L);
// handle exception...
Resp. with spring-data:
JPARepository.getReferenceById(...);
"must read" article
jpa-javadoc
spring-data-javadoc
Regarding "how to do it best" (and in which queries it results), i can additionally very recommend:
https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
(Where Employer is analogous to Post and Job analogous to PostComment;)

Jhipster returns org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1

I'm using JHipster in a microservice architecture (registry, gateway, uaa server). I extended the default jhipster user inside my uaa server with a profile entity (using the #mapsId annotation and a one-to-one relationship according to this article: https://www.jhipster.tech/tips/022_tip_registering_user_with_additional_information.html).
My problem is the following: if I register a new user in the jhipster gateway my profile is created and written to the database with a shared id between the user and profile, everything works fine. Now, if I want to delete the profile entity, the user entity should be deleted too (because no user without a profile) but I get the following exception:
org.springframework.orm.ObjectOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
If I delete the user entity, my profile gets deleted too, so the cascading should work.
User Entity:
#Entity
#Table(name = "jhi_user")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class User extends AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
...
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private Profile profile;
Profile Entity
#Entity(name = "Profile")
#Table(name = "profile")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Profile implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id", referencedColumnName = "id")
#MapsId
private User user;
Foreignkey constraint for the profile table using liquibase:
<addForeignKeyConstraint baseColumnNames="id"
baseTableName="profile"
constraintName="fk_profile_user_id"
referencedColumnNames="id"
referencedTableName="jhi_user"
onDelete="CASCADE"
/>
Do I miss something here? I also tried using the hibernate annotations instead of the JPA ones, but didn't change anything, so I'm thinking this might be a problem with Hibernate itself.
I resolved my issue by adding a new "user_id" column to my profile table. I added a foreign key constraint to the id column (user table) and my user_id column (profile table). not exactly what i wanted, but it works. Just make sure, to enable cascading on both sides of the relationship, so profiles are automatically deleted when you delete it's user.

OpenJPA - Nested OneToMany relationships merge issue

Posting this here as I wasn't seeing much interest here: http://www.java-forums.org/jpa/96175-openjpa-one-many-within-one-many-merge-problems.html
Trying to figure out if this is a problem with OpenJPA or something I may be doing wrong...
I'm facing a problem when trying to use OpenJPA to update an Entity that contains a One to Many relationship to another Entity, that has a One to Many relationship to another. Here's a quick example of what I'm talking about:
#Entity
#Table(name = "school")
public class School {
#Column(name = "id")
protected Long id;
#Column(name = "name")
protected String name;
#OneToMany(mappedBy = "school", orphanRemoval = true, cascade = CascadeType.ALL)
protected Collection<ClassRoom> classRooms;
}
#Entity
#Table(name = "classroom")
public class ClassRoom {
#Column(name = "id")
protected Long id;
#Column(name = "room_number")
protected String roomNumber;
#ManyToOne
#JoinColumn(name = "school_id")
protected School school;
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
protected Collection<Desk> desks;
}
#Entity
#Table(name = "desk")
public class Desk {
#Column(name = "id")
protected Long id;
#ManyToOne
#JoinColumn(name = "classroom_id")
protected ClassRoom classRoom;
}
In the SchoolService class, I have the following update method:
#Transactional
public void update(School school) {
em.merge(school);
}
I'm trying to remove a Class Room from the School. I remove it from the classRooms collection and call update. I'm noticing if the Class Room has no desks, there are no issues. But if the Class Room has desks, it throws a constraint error as it seems to try to delete the Class Room first, then the Desks. (There is a foreign key constraint for the classroom_id column)
Am I going about this the wrong way? Is there some setting I'm missing to get it to delete the interior "Desk" instances first before deleting the Class Room instance that was removed?
Any help would be appreciated. If you need any more info, please just let me know.
Thanks,
There are various bug reports around FK violations in OpenJPA when cascading remove operations to child entities:
The OpenJPA FAQ notes that the following:
http://openjpa.apache.org/faq.html#reorder
Can OpenJPA reorder SQL statements to satisfy database foreign key
constraints?
Yes. OpenJPA can reorder and/or batch the SQL statements using
different configurable strategies. The default strategy is capable of
reordering the SQL statements to satisfy foreign key constraints.
However ,you must tell OpenJPA to read the existing foreign key
information from the database schema:
It would seem you can force the correct ordering of the statements by either setting the following property in your OpenJPA config
<property name="openjpa.jdbc.SchemaFactory"> value="native(ForeignKeys=true)"/>
or by adding the org.apache.openjpa.persistence.jdbc.ForeignKey annotation to the mapping:
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#org.apache.openjpa.persistence.jdbc.ForeignKey
protected Collection<Desk> desks;
See also:
https://issues.apache.org/jira/browse/OPENJPA-1936

Spring JPA: Prevent deleting if relations still exist

I have two entities:
Table activity:
|id|milestone_id|..|
| 1|3 |..|
| 2|3 |..|
Class:
#Entity
#Table(name="activity")
public class Activity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#ManyToOne(optional = false)
private Milestone milestone;
}
and table milestone:
|id|..|
| 3|..|
Class:
#Entity
#Table(name="milestone")
public class Milestone implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY, mappedBy = "milestone")
private List<Activity> activities = new ArrayList<>();
}
Now, when I delete a milestone 3, my Activity's are kept intact. However, the milestone_id still points to the related Milestone that was deleted already.
Now when I retrieve the activities again I get this error:
javax.persistence.EntityNotFoundException: Unable to find nl.geodan.vergunningen.manager.domain.Milestone with id 3
Seems logical to me.
What I want however is that the Milestone can't get deleted, because the milestone_id on the activity table is optional = false.
What did I miss?
Should I reload the activities? Should I use some kind of CascadeType?
Your problem is related to your database schema - I've tried reproducing your problem and I am sure you don't use application generated schema, but already existing one without foreign keys defined. See the output.txt file where ConstraintViolationException occurs.
You can add foreign key to your database by executing proper SQL command:
ALTER TABLE Activity
ADD FOREIGN KEY (milestone_id)
REFERENCES Milestone(id)
Or just change spring.jpa.hibernate.ddl-auto property (in case of spring boot) to create. If you use raw Spring then it would be property of Hibernate itself - hibernate.hbm2ddl.auto=create.
If you don't have an access or privileges to schema you can handle it programically, but this is certainly workaround and just smells bad - one solution would be to check for dependencies everytime you delete Milestone entity, the other one would involve putting #PreDelete listener in Milestone entity:
#PreRemove
private void preRemove() {
if (!activities.isEmpty()) {
throw new LinkedActivityExistsException();
}
}

EJB 3 persistence with JPA relational mapping

I have two entities:
Corpus entity:
#Entity(name = "CORPUS")
public class Corpus implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#OneToMany(mappedBy = "corpus", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Collection<CorpusHistory> corpusHistories;
//Setters and getters...
}
Corpus history entity:
#Entity(name = "CORPUS_HISTORY")
public class CorpusHistory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="CORPUS_ID")
private Corpus corpus;
//Setters and getters...
}
The corpus entity can have many records of corpus history so I am annotating it with #OneToMany. I want the mapping to be done using the corpus id so I am using #JoinColumn(name="CORPUS_ID") and #ManyToOne annotation in corpus history entity.
Before persisting the corpus object to database I set the corpus history collection to it:
LinkedList<CorpusHistory> corpusHistories = new LinkedList<CorpusHistory>();
for (Change change : changes) {
CorpusHistory corpusHistory = new CorpusHistory();
//corpusHistory.setCorpusId(String.valueOf(corpusId)); ?????
corpusHistory.setRevisionAuthor(change.getName());
corpusHistory.setRevisionDate(change.getWhen());
corpusHistory.setRevisionNote(change.getNote());
//corpusHistoryFacade.create(corpusHistory);
corpusHistories.add(corpusHistory);
}
corpus.setCorpusHistories(corpusHistories);
Records are created all it is ok but in the corpus history table the CORPUS_ID column is always null. And when I am retrieving corpus from database the history list is empty. I do not understand how can I specify the corpus id to corpus history if the corpus record is not created yet?
Isn't this an EJB job to do? With #OneToMany and #ManyToOne mapping the appropriate ID's should be mapped and stored into appropriate columns (in this case the corpus id should be stored in every record of corpus history column CORPUS_ID).
Or I am misunderstanding something here? I have tried many tutorials, no success... I am stuck here.
for (Change change : changes) {
CorpusHistory corpusHistory = new CorpusHistory();
corpusHistory.setCorpus(corpus);
...
}
The owner side is the one without the mappedBy attribute. You must initialize the owner side in order to tell JPA that the association exists. Since you have a cascade on corpus.histories, when you persist the corpus, JPA will also persist the histories.

Categories

Resources