Spring JPA: Prevent deleting if relations still exist - java

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();
}
}

Related

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.

Hibernate OneToMany and related deletion constraints

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.

JPA ManyToOne field not getting stored

I have a bean structure as shown below. The problem that I am facing is while trying to persist XBean, I am able to save all the data (i.e. xName, pBean, qBean, rBean, kBeans are all visible in storage) but there is no entry for Y_BEAN.
I am pretty much new with JPA annotations so not really sure if what I have done is correct. The idea is to have multiple entries of XBean (i.e. as List) with one instance of YBean
XBean also will hold an instance of YBean as its parent so when I retrieve XBean I should get all the data. Is there something wrong with #ManyToOne annotation?
#Entity
#Table (name = "X_BEAN")
public class XBean implements XInterface {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private String xName;
#OneToOne(cascade=CascadeType.ALL)
private PBean pBean;
#ManyToOne
#JoinColumn(name="y_id")
private YBean yBean;
#OneToOne(cascade=CascadeType.ALL)
private qBean qBean;
#OneToOne(cascade=CascadeType.ALL)
private RBean rBean;
#OneToMany (mappedBy="xBean", cascade=CascadeType.ALL)
private List<KBean> kBeans;
// getter setters for each are below ...
}
and structure of YBean is like below
#Entity
#Table (name = "Y_BEAN")
public class YBean implements XInterface {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#OneToOne(cascade=CascadeType.ALL)
private ZBean zName;
#OneToOne(cascade=CascadeType.ALL)
private PBean pBean;
#OneToOne(cascade=CascadeType.ALL)
private RBean rBean;
#OneToMany (mappedBy="yBean", cascade=CascadeType.ALL)
private List<XBean> xBeans;
// getter setter for each are below ...
}
I am using Google App Engine's storage
You need cascade=CascadeType.PERSIST on your ManyToOne, to tell Hibernate to persist the YBean when it persists the XBean.
You should also think about whether you want the cascade attribute on the inverse OneToMany. With CascadeType.ALL, if you were to delete an instance of YBean, Hibernate will delete all associated XBeans (of which there may be zero, one, or many), because CascadeType.ALL means "apply persistence operations, including deletion, to any other entities accessible via this property or collection". If you didn't have CascadeType.ALL and you deleted a YBean that was referred to by one or more XBeans, then those XBeans would now referenced a non-existent YBean ID, so you'd probably need to do some cleanup in that case.
Both options are irrelevant if your business logic never deletes a YBean until it is not referred to by any XBeans, but if your business logic doesn't prevent the case, then you should cascade or not based on whether you want to get rid of the XBeans or whether you want to clean them up (but not delete them) to no longer refer to the YBean that's being deleted.
#ManyToOne
#JoinColumn(name="y_id")
private YBean yBean;
what is the column y_id ?
what is the definition of it?
you can try removing #JoinColumn(name="y_id") and let JPA handle it.
and also add fetch = FetchType.EAGER like this.
#OneToMany (mappedBy="yBean", cascade=CascadeType.ALL, fetch = FetchType.EAGER)
private List<XBean> xBeans;

Lucene index not updated with Hibernate Search and Spring Data

I am getting started with Hibernate Search/Lucene using Spring Boot and Spring Data, but I am having an issue with the index not getting updated (Checked with Luke tool).
I have 3 classes in my domain. This is Datasheet, my root entity:
#Entity
#Indexed
public class Datasheet
{
#Id
#GeneratedValue()
private long m_id;
#Field(name="name")
private String m_name;
#Field(name="description")
private String m_description;
#IndexedEmbedded(prefix = "documents.")
#OneToMany(cascade = CascadeType.REMOVE)
private Set<DatasheetDocument> m_documents;
}
Then DatasheetDocument:
#Entity
public class DatasheetDocument
{
#Id
#GeneratedValue()
private long m_id;
private String m_originalFileName;
#Field(name="componentName")
private String m_componentName;
#IndexedEmbedded(prefix = "manufacturer.")
#ManyToOne
private Manufacturer m_manufacturer;
}
And finally Manufacturer:
#Entity
public class Manufacturer
{
#Id
#GeneratedValue()
private long m_id;
#Field(name="name", analyze = Analyze.NO)
private String m_name;
private String m_website;
}
When I explicitly call startAndWait() on the indexer (org.hibernate.search.MassIndexer), then everything is as expected in the index. It contains the fields name, description, documents.componentName and documents.manufacturer.name.
However, when I now do updates through my #RestController classes that call into Spring Data CrudRepository classes, the index only changes when changing a direct field of Datasheet (E.g. name or description). Changing something to the DatasheetDocument instances does not update the index. Any idea why this might be?
Note that I have tried to add backreferences to the parent. For DatasheetDocument:
#ManyToOne
#ContainedIn
private Datasheet m_datasheet;
And for Manufacturer:
#ManyToMany
#ContainedIn
private Set<DatasheetDocument> m_datasheetDocuments;
But that does not help.
I am using Spring boot 1.0.1 which includes Hibernate 4.3.1. I added Hibernate Search 4.5.1. I see that Lucense 3.6.2 gets added transitively as well.
You need the back references for sure. Without them and in particular without #ContainedIn there is no way for Search to know that it has to update the Datasheet index when the DatasheetDocument instance changes.
Have you added mappedBy to the one to many side?
#OneToMany(cascade = CascadeType.REMOVE, mappedBy="m_datasheet")
private Set<DatasheetDocument> m_documents;
Also, how to you update DatasheetDocument? Can you show the code? Either way, you will need to make the associations bi-directional to start with.
FullTextSession fullTextSession = Search.getFullTextSession(session);
fullTextSession.openSession()
Object customer = fullTextSession.load( Datasheet.class, datasheetDocument.getDatasheet.getId() );
fullTextSession.index(customer);
fullTextSession.flushIndex();

hibernate, stackoverflow with particular entity mapping

I have the following mapping:
#Entity
public class Satellite implements Serializable, Comparable<Satellite> {
#NotNull #Id
private long id;
.....
#OrderColumn
#OneToMany(mappedBy = "satellite", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<DataModel> dataModel;
}
and a child entity:
#Entity #IdClass(value=DataModelPK.class)
public class DataModel implements Serializable, Comparable<DataModel> {
private static final long serialVersionUID = -3416403014857250990L;
#Id
private int orbit; // related to reference orbit file
private int dataPerOrbit; // in Gbit
#ManyToOne #Id
private Satellite satellite;
}
originally, DataModel was an embeddable entity, but for a better control over the primary key and the underlying structure of the db, I switched to a more traditional model.
The point is, during the loading of the entity now it generate a stack overflow!! I think there is some cyclic loading between those two entities and it got stuck!
I'm thinking to revert everything back to what it was, but I wish to understand why it gives me this error.
You have #IdClass for DataModel specified to be DataModelPK.class but your #Id annotation is on an int field.
This is a problem, it may be causing you stackoverflow but I am not certain.
Update I now see the second #Id annotation so I stand corrected, I will investigate furtuer.

Categories

Resources