Prevent entity deletion if related entity exists - java

I'm working with JPA 2 + Hibernate 4 and I'm implementing some CRUD operations on model entities.
Now I need to prevent a certain entity (EntityB) to be deleted when a related entity (EntityA) exists in database:
#Entity
public class EntityA {
#Id
private int id;
#OneToOne(mappedBy = "entityA", optional = false, fetch = FetchType.LAZY)
private EntityB entityB;
//...
}
#Entity
public class EntityB {
#Id
private int id;
#OneToOne
#JoinColumn(name = "id")
private EntityA entityA;
//...
}
Is there any way to achieve this using relationship options or should I check EntityA existence in my dao/repository before removing EntityB?
NOTE I need this also for #ManyToOne relationships.

If you want to prevent that in your code, than simply do not delete that entity (by checking that manually). There is no possibility to do that with annotations.
On the other side, this sounds to me rather like a need for a DB constraint. If those entities are already related, then simply add a foreign key constraint (if none is existent). If not, than think of adding one.
PS: if you already have a relationship, check the CascadeType.REMOVE setting.

I don't think you can solve this with annotations. You should manally check related-entity existence before.

Related

How to properly delete a record from a database in one query? JPA

How to properly delete a record from a database in one query. For example, when an entity uses the primary key of the parent entity using the #MapsId annotation, if the parent entry is deleted, it will swear that the parent's id is used in the child entity.
Code example :
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
}
#Entity
public class UserDetails {
#Id
private long id;
private String phone;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private User user;
}
Here, when deleting a User using the JpaRepository delete method, an error will occur that the UserDetail uses the primary key User
First, are you sure the direction of the relation makes sense? I would have expected it to be the other way around, because the user ID and name seem to be the more basic info that you need more often.
Second, what you're doing seems like an attempt to optimize performance, because you could just as well store all the data in a single entity. Are you sure the optimization pays off? (I would guess not.) See Premature Optimization.
Third, if the relation was the other way around, you could modify the annotation to #OneToOne(cascade=CascadeType.DELETE) or #OneToOne(cascade=CascadeType.ALL) to let JPA automatically delete the other entity when the first is deleted.
For that you need to delete all foreign keys with used by primary key
or by using cascade
After that use below
In JPA we can use deleteById
or by named query
DELETE FROM EMPLOYEE WHERE ID = ?1
or my native query
delete from employee where id = ?1

LazyInitializationException for Id field with hibernate + Lombok

I am facing lazy inizialization issue when I added Lombok project into my hibernate project and used its #Getter and #Setter on the entity class.
Entity classes are annotated with #Entity of Javax.persistence as I am using hibernate 5.
Issue stacktrace :-
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:146)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:259)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73)
at com.capehenry.domain.user.User_$$_jvst52e_9.getId(User_$$_jvst52e_9.java)
at com.capehenry.business.rs.course.SeatRequestResource.validateSeatRequestCancel(SeatRequestResource.java:338)
at com.capehenry.business.rs.course.SeatRequestResource.cancel(SeatRequestResource.java:220)
Everything was working fine with below code
#Entity
#Audited
#Table(name = "seat_request")
public class SeatRequest extends BaseEntity {
private CourseSchedule courseSchedule;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "courseScheduleId", nullable = false)
public CourseSchedule getCourseSchedule() {
return courseSchedule;
}
public void setCourseSchedule(CourseSchedule courseSchedule) {
this.courseSchedule = courseSchedule;
}
When I do searRequest.getCourseSchedule().getId() it works at rest layer means outside the transaction.
As soon as I change the code to below (add lombok), searRequest.getCourseSchedule().getId() at rest layer starts throwing lazyInitializationException :-
#Entity
#Audited
#Table(name = "seat_request")
#Setter
public class SeatRequest extends BaseEntity {
#ManyToOne(fetch = FetchType.LAZY, optional=false)
#JoinColumn(name = "courseScheduleId", nullable = false)
private CourseSchedule courseSchedule;
NOTE :-
1) I have to compulsory use Lombok project
2) I have to use searRequest.getCourseSchedule().getId() outside Sevrice and trasaction
Please suggest the solution, Thanks in advance!!
I have to use searRequest.getCourseSchedule().getId() outside Service and transaction
I have noticed this just now... If you are outside the service and transaction you will always have that exception. Try to use FetchType.EAGER and it should work.
When you are out of transaction your entities are detached, this means that all collections that you marked as lazy won't be loaded. So you have two options: the first is to perform all calls to collections getters inside the transaction, the second one is to mark as eager your collection, so when Hibernate loads the entity it will also load referenced collection immediately. Alternatively you could map to a DTO your Entity inside your transaction. As long as you are in the transaction the getters of lazy loaded field will always work, so a mapper to the DTO would access all informations. Once that the DTO is out of the transaction you will have access to all fields you have mapped, and than do whatever you want.
Here is how I solved the issue finally!
I thought the issue started after integration with Lombok project but the issue started when the annotations were moved to field level from method (property) level.
Bear with me for the long answer.
Here foreign is refering to the database level foreign tables.
To access any column from foreign table's outside the transaction you need to either use FetchType.Eager (which is default in hibernate for any foreign object) or need to join/subquery that table.
But if you just want to fetch the foreign key(column) with which 2 tables are joined (in our case the ID) and want to keep FetchType.LAZY then you can do it in 2 ways :-
1) Keep annotations (manyToOne, JoinColumn etc) on getter methods
2) If annotations has to be kept on field level then write one more annotation on foreign key field in parent table which is - #Access(AccessType.PROPERTY)
So in above code to solve I added this annotation on id field of course Schedule
#Entity
#Audited
#Table(name = "course_schedule")
#Getter
#Setter
public class CourseSchedule{
#Id
#GenericGenerator(name = "autoincr", strategy = "native")
#GeneratedValue(generator = "autoincr")
#Column(name = "id", unique = true, nullable = false)
#Access(AccessType.PROPERTY)
protected Long id;
..........
}
So no change was required in Seat Request.

Insert order of JPA entity with compound EmbeddedId (an EmbeddedId that contains another EmbeddedId)

I'm working on a project in WebSphere 8.5.5 (OpenJPA 2.2.3) that needs to cascade creation and merging through a large JPA annotated entity model. We are having a very specific problem when merging grand-children either by calling EntityManager.merge() on the grand-parent or by the triggered flush at the commit of a transaction. Here are the details:
Relevant portion of entity mappings:
EntityA has a oneToMany to EntityB
EntityB has a oneToMany to EntityC
EntityC has a oneToMany to EntityD
All have bidirectional mappings. Entity A and B have single column primary keys. Entity C has a composite primary key that includes a foreign key to the primary key of Entity B. Entity D has a composite key that includes the composite key of Entity C. Please see the mappings below.
#Entity
#Table(name="TableA")
public class EntityA extends BaseEntity {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_A_ID_GEN")
#SequenceGenerator(name="TABLE_A_ID_GEN", sequenceName="TABLE_A_ID", allocationSize=1)
#Column(name="TABLE_A_ID")
private Integer id;
#OneToMany(fetch=FetchType.LAZY, mappedBy="entityA", cascade=CascadeType.ALL)
private List<EntityB> entityBList;
...
}
#Entity
#Table(name="TableB")
public class EntityB extends BaseEntity {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_B_ID_GEN")
#SequenceGenerator(name="TABLE_B_ID_GEN", sequenceName="TABLE_B_ID", allocationSize=1)
#Column(name="TABLE_B_ID")
private Integer id;
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name="TABLE_A_ID")
private EntityA entityA;
#OneToMany(fetch=FetchType.LAZY, mappedBy="entityB", cascade=CascadeType.ALL)
private List<EntityC> entityCList;
...
}
#Entity
#Table(name="TableC")
public class EntityC extends BaseEntity {
#EmbeddedId
private EntityC_PK id = new EntityC_PK();
#MapsId("entityB_Id")
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name="TABLE_B_ID")
private EntityB entityB;
#OneToMany(fetch=FetchType.LAZY, mappedBy="entityC", cascade=CascadeType.ALL)
private List<EntityD> entityDList;
...
}
#Embeddable
public class EntityC_PK implements BaseComponent {
#Column(name="TABLE_B_ID", nullable = false, updatable = false)
private Integer entityB_Id;
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_C_ID_GEN")
#SequenceGenerator(name="TABLE_C_ID_GEN", sequenceName="TABLE_C_ID", allocationSize=1)
#Column(name="TABLE_C_ID")
private Integer entityC_Id;
...
}
#Entity
#Table(name="TABLE_D")
public class EntityD extends BaseEntity {
#EmbeddedId
private EntityD_PK id = new EntityD_PK();
#MapsId("entityC_Id")
#JoinColumns({
#JoinColumn(name = "TABLE_B_ID"),
#JoinColumn(name = "TABLE_C_ID")})
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private EntityC entityC;
...
}
#Embeddable
public class EntityD_PK implements BaseComponent {
#Embedded
private EntityC_PK entityC_Id;
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_D_ID_GEN")
#SequenceGenerator(name="TABLE_D_ID_GEN", sequenceName="TABLE_D_ID", allocationSize=1)
#Column(name="TABLE_D_ID")
private Integer entity_id;
...
}
What Works:
You can call an EntityManager.persist() on Entity A (with all the children attached) and the model will cascade the persist correctly.
What Doesn't Work:
If you instantiate Entity A and call EntityManager.persist(entityA) and THEN add the children, grand-children, etc. when you EntityManager.merge(entityA) (or allow the implicit merge upon committing the transaction) it will fail to execute the INSERT statements in the correct order. To make things more confusing the order of the INSERTS is not consistent across repeat executions of unit tests. It fails by attempting to insert Entity D before Entity C.
The Question:
How to we correct the JPA annotations to enforce the correct insert order (and update/delete) upon merge?
EDIT 1:
The insert/delete order is critical because the database enforces the foreign key relationships with constraints.
Let me first state (and maybe I'm stating the obvious, sorry) that you should review the JPA spec for your scenarios.......embedables sometimes have differently rules about them. Next, you state 'EntityManager.create()', but I think you meant .persist? You later talk about merge so maybe you mean .merge? Either way, I'd suggest you stick with .persist if you want to persist new entities rather than a merge. While it is not illegal, merge is typically for merging detached entities, etc.
With that out of the way, let me get at the heart of your question and give you a property which might help with your order. You didn't state in your text if your ddl contains a foreign key constraint. Since you are concerned with order, I'd assume you have such a constraint. If you do, OpenJPA knows nothing about this constraint, and as such, will not know to order things appropriately. By default, you can't depend on the order of SQL, and the randomness of the ordering is exactly what I expect. However, if you need things to be order in such a way as to support an FK constraint, then you need to allow OpenJPA to 'learn' about your constraint. To do that, you need to set this property in your persistence.xml file (or you can set it as a JVM custom property):
<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)"/>
This property allows OpenJPA to inspect your schema and in so doing it can learn about your FK constraint. With that knowledge, OpenJPA can properly order SQL.
Finally, if you don't have an FK constraint, but you want to order the SQL in a certain way, then you might need to use this:
<property name="openjpa.jdbc.UpdateManager" value="operation-order"/>
Do not, and I repeat do not use both of these properties together. It can have odd side effects. Please focus on the SchemaFactory property first, and then if it doesn't help try UpdateManager. The operation-order tells OpenJPA to order SQL based on how your persist your entities, or in other words, the order of operations. This might actually not be overly helpful to your situation since you persist A and expect everything else to be cascaded (OpenJPA would likely persist A first, but when it comes to B and C, it is a crapshoot which will go first). However, if you persisted A, then C, then B, the SQL should go in order of inserting A, C, then B with "operation-order" set.

JPA Cascading Delete: Setting child FK to NULL on a NOT NULL column

I have two tables: t_promo_program and t_promo_program_param.
They are represented by the following JPA entities:
#Entity
#Table(name = "t_promo_program")
public class PromoProgram {
#Id
#Column(name = "promo_program_id")
private Long id;
#OneToMany(cascade = {CascadeType.REMOVE})
#JoinColumn(name = "promo_program_id")
private List<PromoProgramParam> params;
}
#Entity
#Table(name = "t_promo_program_param")
public class PromoProgramParam {
#Id
#Column(name = "promo_program_param_id")
private Long id;
//#NotNull // This is a Hibernate annotation so that my test db gets created with the NOT NULL attribute, I'm not married to this annotation.
#ManyToOne
#JoinColumn(name = "PROMO_PROGRAM_ID", referencedColumnName = "promo_program_id")
private PromoProgram promoProgram;
}
When I delete a PromoProgram, Hibernate hits my database with:
update
T_PROMO_PROGRAM_PARAM
set
promo_program_id=null
where
promo_program_id=?
delete
from
t_promo_program
where
promo_program_id=?
and last_change=?
I'm at a loss for where to start looking for the source of the problem.
Oh crud, it was a missing "mappedBy" field in PromoProgram.
Double-check whether you're maintaining bidirectional association consistency. That is; make sure that all PromoProgramParam entities that link to a PromoProgram as its parent are also contained in said parent's params list. It's a good idea to make sure this happens regardless of which side "initiates" the association if you will; if setPromoProgram is called on a PromoProgramParam, have the setter automatically add itself to the PromoProgram's params list. Vice versa, when calling addPromoProgramParam on a PromoProgram, have it set itself as the param's parent.
I've encountered this problem before as well, and it was due to not maintaining bidirectional consistency. I debugged around into Hibernate and found that it was unable to cascade the delete operation to the children because they weren't in the list. However, they most certainly were present in the database, and caused FK exceptions as Hibernate tried to delete only the parent without first deleting its children (which you've likely also encountered with the #NonNull in place).
FYI, I believe the proper "EJB 3.0"-way of making the PromoProgramParam.promoProgram field (say that a 100 times) non-nullable is to set the optional=false attribute on the #ManyToOne annotation.

One-to-Many Unidirectional Parent-Child ID Cascade Save

When trying to save an ID from my parent class into a child class, I keep getting the error
"ERROR - Field 'parent_id' doesn't have a default value"
I have tried all types of mappings. I am using annotations.
Any help on this would be appreciated
Parent:
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column(name="description")
private String description;
#OneToMany
#Cascade(value= {org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE})
#JoinColumn(name="parent_id")
private List<Child> children;
Child:
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column(name="description")
private String description;
Thanks.
A late addition in case anyone ever runs into the same issue.
This entity here, when persisted using Hibernate 4.1.8, will cascade the FieldChangeentities, but will not fill the join column:
#Entity
public class Event {
//ID and other fields here
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "event_id")
private List<FieldChange<?>> fields = new ArrayList<FieldChange<?>>();
}
Neither does the insert statement set the event_id column, nor does it update the inserted entity after the fact - the event_id remains null and the relation is lost.
If, however, the #JoinColumn definition is changed like this:
#JoinColumn(name = "event_id", nullable = false)
, then the insert statement includes the event_id column like it should, and all is well.
This may only be a regression in this particular version of Hibernate, but maybe it helps someone.
In your case JPA provider to persist child object with its parent perform at least three queries on db. First two persist the objects by its own. The last one
update child object with the foreign key referencing parent. The second query fail because you have a NOT NULL constraint on the foreign key column. You have three options:
Remove NOT NULL constraint on foreign key in the child entity
Use bidirectional relationship
Change JPA provider to one which supports such cases.
You must have something wrong somewhere else because those mappings will work the way they are. They could be better, but they'll work. Specifically, all the #Column annotations are redundant and unnecessary, and as non sequitor noted, you should use the cascade property of JPA's #OneToMany instead of Hibernate's #Cascade. I've created a runnable example with the cleaned-up version of what you posted. If you have git and maven, you can run it with:
git clone git://github.com/zzantozz/testbed tmp
cd tmp
mvn -q compile exec:java \
-Dexec.mainClass=rds.hibernate.UnidirectionalManyToOneJoinColumn \
-pl hibernate-unidirectional-one-to-many-with-join-column
It creates a parent with two children, saves them, and then loads them and prints out the graph. The output is:
Creating parent with two children
Loading saved parent
Parent{description='parent', children=[Child{description='child 2'}, Child{description='child 1'}]}
Change your #OneToMany to #OneToMany(cascade=CascadeType.ALL) use JPA rather than the Hibernate extensions
My guess is that the #JoinColumn annotation needs a referencedColumnName assigned.
#JoinColumn(name = "parent_id", referencedColumnName = "id")

Categories