JPA Delete with 2 Interlocking Entity Classes - java

I have 2 EJB 3 Entity Beans:
#Entity
public class Organisation
{
#Id
#Column(length = 64)
private String guid;
private String name;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
#JoinColumn(name = "home_unit_fk", nullable = true)
private Unit homeUnit;
}
#Entity
public class Unit
{
#Id
#Column(length = 64)
private String guid;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisation_fk", nullable = false)
private Organisation organisation;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_unit_fk", nullable = true)
private Unit parentUnit;
#OneToMany(mappedBy = "parentUnit", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
#OrderBy("shortName")
#OptimisticLock(excluded = true)
private Set<Unit> childUnits;
}
If I do a delete on the Organisation using standard Dao :
public int deleteByGuid(final String guid)
{
final Query query = entityManager.createQuery("delete from " + getPersistentClass().getName() + " where guid = :guid");
query.setParameter("guid", guid);
return query.executeUpdate();
}
But I get the following exception:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (config.unit, CONSTRAINT FK27D184F5D4393D FOREIGN KEY (organisation_fk) REFERENCES organisation (guid))
I don't get it. What am I doing wrong? Shouldn't JPA/Hibernate perform deletes on both the Unit and the Organisation within the same transaction?

A bulk delete query does not load objects into memory and it bypasses any cascade specified on associations.
I would code your delete method as:
public int deleteByGuid(final String guid){
Organization org = entityManager.find(Organization.class, guid);
entityManager.remove(org);
}
If you use a Query to do bulk updates, the operation is delegated to the database directly. If you wish to delete child objects, you have to set a DELETE CASCADE trigger at the "database" level.
By loading the object and removing it, Hibernate will trigger the cascade at the "object" level.
More info available at: http://twasink.net/blog/2005/04/differences-in-behaviour-between-hibernate-delete-queries-and-the-old-way/

Related

Hibernate not respecting nullable=true in #JoinColumn annotation

I have two entities
#Entity
#Table(name = "Documents")
public class Document extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(nullable = true, name = "asset_id")
private Asset asset;
#ManyToOne( targetEntity = Debt.class,
fetch = FetchType.LAZY, optional = false)
#JoinColumn(nullable = true, name = "debt__id")
private Debt debt;
}
and
#Entity
#Table(name = "debts")
public class Debt extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "debt", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Document> documents;
}
This mapping:
#ManyToOne( targetEntity = Debt.class,
fetch = FetchType.LAZY, optional = false)
#JoinColumn(nullable = true, name = "debt_id")
private Debt debt;
was recently added.
On running the app, the app is crashing with an error saying: Error executing DDL "alter table documents add debt__id bigint not null" via JDBC Statement
I checked the sql query hibernate sent to the database and it was: alter table documents add debt_id bigint not null
This query fails because there are records already on the documents table so a non-nullable column without default value could not be added.
So why is the nullable=true in the #JoinColumn annotation ignored by hibernate.
I couldn't find an answer anywhere. This is a spring-boot app, if it helps.
Just remove the optional = false from the #ManyToOne. That is the culprit. Also, you don't need to mention nullable=true in the #JoinColumn, that is by default true
#ManyToOne( targetEntity = Debt.class,
fetch = FetchType.LAZY)
#JoinColumn(name = "debt__id")
private Debt debt;

Spring Data JPA delete from many to many relationship problem

In my project I use Spring data jpa. I have tables for many to many relationship. My entities:
#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_PARTS", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id")
)
private Set<PartsJpa> partsJpa;
//---------------
And Parts:
#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)
private Set<SpecJpa> specJpa;
Now in Controller I try to delete one row from table parts:
#PostMapping("deletePart")
public String deletePart(#RequestParam String id, Model model) {
partsService.deleteById(Integer.parseInt(id));
return "redirect:/parts";
}
But I have exception:
ferential integrity constraint violation:
"FK9Y4MKICYBLJWPENACP4298I49: PUBLIC.PARTS FOREIGN KEY(ID_EXPORT)
REFERENCES PUBLIC.EXPORT(ID) (1)"; SQL statement: /* delete
com.aleksandr0412.demo.entitiesjpa.ExportJpa / delete from EXPORT
where id=? [23503-200]], SQL: / delete
com.aleksandr0412.demo.entitiesjpa.ExportJpa */ delete from EXPORT
where id=? 2020-05-25 19:16:31.630 WARN 13387 --- [nio-8080-exec-4]
o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 23503, SQLState:
23503
In my db for this entities I have 3 tables: Parts, Spec and Spec_parts. As I understand to solve this problem, I firstly should delete rows in table spec_parts, and after this I can delete row from table parts. How can I do this?
In your partsService implementation, I would recommend you first fetch the resource you are about to delete (i.e the PartsJpa) using the given id from the controller.
Next set its specJpa to null or emptySet, then call the delete method afterwards.
For this to work, ensure that method setSpecJpa(SpecJpa specJpa) and setPartJpa(PartJpa partJpa) are properly implemented.
I hope you find this helpful.

Hibernate FETCH.Eager fetching duplicates when using Spring Data repositories

I am facing an issue with Spring Data repository + Hibernate (in spring 2.1.4)
Note: Everything was working fine in spring 1.5.x
The problem is with #ManyToOne(fetch = FetchType.Lazy) I get correct records in 1.5 but error in spring 2.0 but with #ManyToOne(fetch = FetchType.Eager) I get duplicate records in List<Stories>.
I am using Spring data repositories epicRepository.findById(Long id) (previously Spring had epicRepository.find() but they removed it in spring 2.x)
I don't want to use #Fetch (FetchMode.SELECT) solution as it has to execute multiple select queries and will become very non-performant.
Problem:
if I use fetch = FetchType.Lazy i am getting an error could not
initialize proxy - no session (only started after upgrading to
spring 2.x and returned correct number of rows) [This error seems to be ok as I am trying to fetch the list.count later in the code, and there are no duplicates when I checked through debugger before the erroring line]
so based on some solutions here in SO I used FetchType.Eager
(I understand the performance implications of this, but anyway I
needed to do this for another work so I did this because this list is smaller and need to preserve some business logic during updates). But Now I am getting duplicate records.
Even If what I do is wrong, the count in list (mean the duplicates) should not be wrong ?
I have the following JPA entities / tables
Epic -> [id (pk), name, status_id (fk))
Story->[id (pk), name, status_id (fk), epic_id (fk))
Task -> [id (pk), name, resolution_type_id (fk), story_id (fk))
forgive me if there any typos (as i recreated code manually using different use case)
Epic.java
#Data
public class Epic {
#Id
private Long id;
private String name;
#OneToOne(fetch = FetchType.Eager, optional = false)
#JoinColumn(name = id, referenceColumnName = 'id', nullable = false, insertable = true, updatable = true)
private Status status;
#OneToMany(fetch = FetchType.Eager, cascade = ALL, mappedBy = epic)
private List<Story> stories;
}
Story.java
#Data
public class Story {
#Id
private Long id;
private String name;
#OneToOne(fetch = FetchType.Eager, optional = false)
#JoinColumn(name = id, referenceColumnName = 'id', nullable = false, insertable = true, updatable = true)
private Status status;
#OneToMany(fetch = FetchType.Eager, cascade = ALL, mappedBy = epic)
private List<Task> tasks;
#ManyToOne(fetch = FetchType.Lazy)
// This is the problem area
// Error if FetchType.Eager
// But duplicates in FetchType.Lazy
#JoinColumn(name = "id", nullable = false)
private Epic epic;
}
Task.java
#Data
public class Task {
#Id
private Long id;
private String name;
#ManyToOne(fetch = FetchType.Lazy)
#JoinColumn(name = "id")
private Story story;
#OneToOne (fetch = FetchType.Eager, optional = true)
#JoinColumn (name = "id", )
private Resolution resolution;
}
This question has been replied to many times before. Your options could be:
all EAGER collections you should migrate to Sets
manually do duplicate filtering using stream.distinct.collect on getters of collections.

Child entity not deleting on removal of parent entity

When deleting a parent entity I also want to remove the associated child entities (from the database). I have tried to make use of cascade on remove as seen below but I must be doing something incorrectly.
When calling remove on the parent entity object, I recieve the error message: "The entity is still referenced elsewhere in the database". I can confirm that the only place where the entity is referenced elsewhere in the database is in the two tables below (if I manually delete the child row from the database, the remove call on the parent works fine). I have been reading about entity objects and trying different things for the last 9 hours. What am I doing wrong?
Here is my parent table:
#Entity
#Table(name = "TURTLE_LOOKUP")
public class TurtleLookup implements Serializable
{
#Basic(optional = false)
#Column(name = "TURTLEID")
private int turtleid;
#Basic(optional = false)
#Column(name = "TURTLE")
private String turtle;
#OneToMany(mappedBy = "turtleType", cascade = CascadeType.REMOVE)
List<TurtleReview> turtleReviews;
...
}
Here is my child table:
#Entity
#Table(name = "TURTLE_REVIEW")
public class TurtleReview implements Serializable
{
#Column(name = "TURTLE_REVIEW_ID")
private int turtleReviewId;
#Column(name = "TURTLE_YEAR")
private int turtleYear;
#ManyToOne(cascade = CascadeType.REMOVE, optional = false)
#JoinColumn(name = "TURTLE_ID", referencedColumnName = "TURTLEID")
private TurtleLookup turtleType;
#Column(name = "IS_COMPLETE")
private short isComplete;
...
}
EDIT/UPDATE:
If I change CascadeType.REMOVE to CascadeType.ALL, the TurtleReview entities are successfully deleted from the database when deleting the parent TurtleLookup entity object. However, when calling the below function to create a new TurtleReview entity object, JPA tries to insert a new TurtleLookup entity in to the database, which throws the exception: "Entry already resides within the DB. Transaction rolled back". Below is the code executed when creating a new TurtleReview entity.
public void setDatasetReviewComplete(TurtleLookup turtle, Short year, boolean isComplete)
{
TurtleReview turtleReview = getTurtleReview(turtle, year);
if (turtleReview == null)
{
turtleReview = new TurtleReview();
turtleReview.setTurtleYear(year)
turtleReview.setTurtleType(new a.b.entity.TurtleLookup(turtle.getId(), turtle.getValue()));
}
turtleReview.setIsComplete(isComplete ? (short)1 : 0);
entityManager.persist(turtleReview);
}
try change cascade value to all or all-delete-orphan
#OneToMany(mappedBy = "turtleType", cascade = CascadeType.REMOVE)
List<TurtleReview> turtleReviews;
...
}
There might be an issue with your domain model, a part that is left out in the question. Do you possibly have circular cascades? If you have a circle of cascades and some of them are CascadeType.REMOVE and some are CascadeType.PERSIST, then Hibernate (not sure about other JPA implementation) will just do.... nothing when you call the remove() method. Without an error or exception message.
Try with hibernate #Cascade annotation:
#Cascade(value = CascadeType.ALL)
#OneToOne(mappedBy = "turtleReview") // mappedBy name of TurtleRewiew object field in TurtleLookup entity class
private TurtleLookup turtleType;
If your relationship is oneToOne you can't have oneToMany to the other side and you can't have List<TurtleReview>. If your relationship is oneToMany then your entities will be for example:
#Entity
#Table(name = "TURTLE_LOOKUP")
public class TurtleLookup implements Serializable
{
#Basic(optional = false)
#Column(name = "TURTLEID")
private int turtleid;
#Basic(optional = false)
#Column(name = "TURTLE")
private String turtle;
#OneToMany(mappedBy = "turtleType") // or add cascade = javax.persistence.CascadeType.ALL and remove #Cascade if you are not using hibernate
#Cascade(value = CascadeType.ALL)
List<TurtleReview> turtleReviews;
...
}
#Entity
#Table(name = "TURTLE_REVIEW")
public class TurtleReview implements Serializable
{
#Column(name = "TURTLE_REVIEW_ID")
private int turtleReviewId;
#Column(name = "TURTLE_YEAR")
private int turtleYear;
#ManyToOne
#JoinColumn(name = "TURTLE_ID", referencedColumnName = "TURTLEID")
private TurtleLookup turtleType;
#Column(name = "IS_COMPLETE")
private short isComplete;
...
}

Use Hibernate Formula annotation for reference loading

I have 3 entities - Storage, Item and Relation. Storage has several Item entities and items are bound by Relation entities. And relations can bind items from different storage. For simplification let say I want to load relation by query and want to do it fast. So now I have 3 query - loading for Storage, loading for all Items under storage and load of relation (List<Relation> relations field).
Now I want to tell hibernate how to load Collection<Relation> extRelation field. I tried #Formula, #CalculatedColumn and different combination of #ManyToMany and #JoinFormula. But generated query is wrong (or ignores my query). Also I cannot use #OneToMany because of https://hibernate.atlassian.net/browse/HHH-9897 bug.
Latest Exception is:
Caused by: org.h2.jdbc.JdbcSQLException: Table
"TEST_STORAGES_TEST_RELATIONS" not found; SQL statement:
SELECT extrelatio0_.test_storages_storage_id AS test_sto1_2_0_
,extrelatio0_.extRelation_from_item_id AS extRelat2_3_0_
,extrelatio0_.extRelation_to_item_id AS extRelat3_3_0_
,relation1_.from_item_id AS from_ite1_1_1_
,relation1_.to_item_id AS to_item_2_1_1_
,relation1_.relation_id AS relation3_1_1_
,relation1_.storage_id AS storage_4_1_1_
FROM test_storages_test_relations extrelatio0_
INNER JOIN test_relations relation1_ ON extrelatio0_.extRelation_from_item_id = relation1_.from_item_id
AND extrelatio0_.extRelation_to_item_id = relation1_.to_item_id
WHERE extrelatio0_.test_storages_storage_id = ?
My entities:
#Entity
#Table(name = "test_storages")
public class Storage {
#Id
#Column(name = "storage_id")
private BigInteger storageId;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, targetEntity = Item.class)
#JoinColumn(name = "storage_id", updatable = false)
#MapKey
private List<Item> items;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, targetEntity = Relation.class)
#JoinColumn(name = "storage_id", updatable = false)
#Fetch(org.hibernate.annotations.FetchMode.SELECT)
private List<Relation> relations;
#ManyToMany()
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula =
#JoinFormula(
value = "(select dep.from_item_id, dep.to_item_id from test_relations dep where dep.storage_id = ?)"
)
)
})
private Collection<Relation> extRelation;
}
#Entity
#Table(name = "test_items")
public class Item {
#Id
#Column(name = "item_id")
private BigInteger itemId;
private String name;
#Column(name = "storage_id")
private BigInteger storageId;
}
#Entity
#Table(name = "test_relations")
public class Relation {
#Column(name = "relation_id")
private BigInteger relationId;
#Column(name = "storage_id")
private BigInteger storageId;
#EmbeddedId
private RelationPK pk;
}
#Embeddable
public class RelationPK implements Serializable {
#Column(name = "from_item_id")
private BigInteger fromItemId;
#Column(name = "to_item_id")
private BigInteger toItemId;
}
All sources available on https://github.com/ainlolcat/test_hibernate_formula

Categories

Resources