Referential integrity with One to One using hibernate - java

I'm having two tables -
Foo { foo_id, name }
Foo_properties { fp_id, foo_id, phoneNumber}
Now I want to map this in my object model using hibernate..
I need a foo_id in Foo_properties because i want to maintain referential integrity and want to add ON DELETE CASCADE constraint.
so I mapped the relation in the following way -
#Entity
public class Foo{
#Id
private long foo_id;
private String name;
#OneToOne(mappedBy = "foo")
private FooProperties fooProperties;
}
#Entity
public class FooProperties{
#Id
private long fp_id;
private String phoneNumber;
#OneToOne
#JoinColumn(name = "foo_id", nullable = false)
private Foo foo;
}
Now since the owning side is FooProperties class, I'm facing following issues -
If I set the new instance of FooProperties to Foo the existing FooProperties still remains in DB and hibernate doesn't delete that instance, e.g.
Foo foo = entityManager.find(Foo.class, fooId);
foo.setFooProperties(new FooProperties("xxx-xxx-xxx"));
entityManager.merge(foo);
This results into the new row in FooProperties table along with the existing one. Now I don't understand how I can change my mapping to so I can have above code (or variant of it) working for all scenarios, that means I need Foo as a owning side and foo_id in FooProperties. Is there any way to define the mapping like this?
NOTE: I already asked question based on this but I think I wasn't clear in previous question so asked this another one.

You were already told to use orphanRemoval = true or CascadeType.DELETE_ORPHAN. However, due to casuistics in interpretation of JPA Specification it wouldn't work as expected for one-to-one relationships (HHH-5559).
You can achieve a proper behaviour of orphanRemoval with the following trick:
#Entity
public class Foo{
#OneToMany(mappedBy = "foo", orphanRemoval = true)
private List<FooProperties> fooProperties;
public FooProperties getFooProperties() {
if (fooProperties == null || fooProperties.isEmpty()) return null;
else return fooProperties.get(0);
}
public void setFooProperties(FooProperties newFooProperties) {
if (fooProperties == null) fooProperties = new ArrayList<FooProperties>();
else fooProperties.clear();
if (newFooProperties != null)
fooProperties.add(newFooProperties);
}
...
}
#Entity
public class FooProperties{
#ManyToOne
#JoinColumn(name = "foo_id", nullable = false)
private Foo foo;
...
}
Or even this, if you don't need FooPropeties.foo:
#Entity
public class Foo{
#OneToMany(orphanRemoval = true)
#JoinColumn(name = "foo_id", nullable = false)
private List<FooProperties> fooProperties;
// getter/setter as above
...
}

Bar is the owner of the association (as indicated by the mappedBy on the inverse side) and thus the cascade has to be set there.
Edit:
To invert that, this might help.

There are 2 options for you to choose from, since you don't want to change your mapping :
Do it via your service layer logic. I think you have a similar question already.
Use the Hibernate annotation #Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN) on the Foo side of the relationship. However this is explicitly Hibenate and JPA 2 doesn't include support for the same.

I think instead of calling merge on the entity, if you directly call update on session object then hibernate will first delete the existing row and then it will add the new one. I implemented the same, but, in my case I used xml for mapping the entity. I hope this will help you.

Related

JPA: 'CascadeType.REMOVE' or 'orphanRemoval = true', which use in a n:n relation that generate new table/class with EmbeddeId class?

I am developing an REST API to a pizzeria store. And here i'm trying to delete a Flavor and all data related to it. Further explained below:
Classes:
Flavor have at least one Filling, each one taking a position on it.
i.e: Souce (at pos. 1), mozzarela (at pos. 2) tomato (at pos. 3)
Flavors must have a price to each Size
With that in mind, we can conclude that exist two many-to-many relationships:
Flavor to many Filling
Flavor to many Size
Class diagram of actual implementation
The requirement is to: delete a Flavor, and automatically delete all the FillingPositionFlavor and FlavorPriceSize.
But,I'm confused on use of CascadeType.REMOVE and orphanRemoval = true:
When I use Cascade and OrphanRemoval on Flavor.sizePrices, get a HibernateException when trying to edit a Flavor, exclusion works fine:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.pkg.Flavor.sizePrices
When I use Cascade on Flavor.sizePrices, get a PSQLException when excluding a Flavor, editing works fine:
ERROR: update or delete on table "tb_flavor" violates foreign key constraint "fk9orw0yhtc0e06ka84dbcd2c82" on table "tb_flavor_size_price"
I'm doing unit testing of services in Spring Boot to test all the CRUD operations.
Below is the actual code, I hid properties like id and others to facilitate the read.
#Entity
#Table(name = "tb_flavor")
class Flavor {
#OneToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE},orphanRemoval = true)
private Set<FlavorPositionFilling> flavors = new HashSet<FlavorPositionFilling>();
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE},orphanRemoval = true)
private Set<FlavorPriceSize> priceSizes;
// other properties and methods
}
#Entity
#Table(name = "tb_flavor_price_size")
class FlavorPriceSize {
#EmbeddedId
private FlavorPriceSizeEmbeddeId id;
private float price;
// other properties and methods
}
#Embeddable
class FlavorPriceSizeEmbeddeId implements Serializable {
#ManyToOne(cascade = { CascadeType.ALL })
#JoinColumn(name = "ID_FLAVOR_FK", referencedColumnName = "id_flavor")
private Flavor flavor;
#ManyToOne(cascade = { CascadeType.ALL })
#JoinColumn(name = "ID_SIZE_FK", referencedColumnName = "id_size")
private Size size;
}
#Entity
#Table(name = "tb_flabor_position_filling")
class FlaborPositionFilling {
#EmbeddedId
private FlaborPositionFillingEmbeddedId id;
private Integer position;
}
#Embeddable
class FlaborPositionFillingEmbeddedId implements Serializable {
#ManyToOne(cascade = CascadeType.REMOVE)
#JoinColumn(name="ID_FLAVOR_FK", referencedColumnName="id_flavor")
private Flavor sabor;
#ManyToOne()
#JoinColumn(name="ID_FILLING_FK", referencedColumnName="id_filling")
private Filling filling;
}
I've read a lot about both, but still not understand the right use of each and their effect on operations. Can anyone explain it to me? Show videos, images, code...
Let's assume that you have a parent -> child relationship.
If you set CacadeType.REMOVE on the relationship every EntityManager.remove call on the parent will also remove the children.
orphanRemoval = true is used to delete orphan children.
So if remove a child from the parent reference or collection and save the parent the child will be deleted because its no longer attached to the parent.

JPA/validation #ManyToOne relations should not create new rows

I have an JPA entity with contains a ManyToOne reference to another table, a simplified version of that entity is shown below:
#Entity
#Table(name = "ENTITIES")
public class Entity implements Serializable {
#Id #NotNull
private String id;
#JoinColumn(name = "REFERENCE", referencedColumnName = "ID")
#ManyToOne(optional = false)
private ReferencedEntity referencedEntity;
}
#Entity
#Table(name = "REFERENCES")
public class ReferencedEntity implements Serializable {
#Id #NotNull #Column(name = "ID")
private String id;
#Size(max = 50) #Column(name = "DSC")
private String description;
}
Finding entities works fine. Peristing entities also works fine, a bit too good in my particular setup, I need some extra validation.
Problem
My requirement is that the rows in table REFERENCES are static and should not be modified or new rows added.
Currently when I create a new Entity instance with a non-existing (yet) ReferencedEntity and persist that instance, a new row is added to REFERENCES.
Right now I've implemented this check in my own validate() method before calling the persist(), but I'd rather do it more elegantly.
Using an enum instead of a real entity is not an option, I want to add rows myself without a rebuild/redeployment several times in the future.
My question
What is the best way to implement a check like this?
Is there some BV annotation/constraint that helps me restrict this? Maybe a third party library?
It sounds like you need to first do a DB query to check if the value exists and then insert the record. This must be done in a transaction in order to ensure that the result of the query is still true at the time of insertion. I had a similar problem half a year back which might provide you with some leads on how to set up locking. Please see this SO question.
You should add this => insertable=false, updatable=false
And remove => optional=false , and maybe try nullable=true

Hibernate OneToOne BiDirectional Optional Relationship: Works when inserted without optional object, Breaks when updated with new optional object

I have the following OneToOne relational setup between the two object, ChecklistItem and ButtonAction (shown in code snippets below). It's kind of a unique setup, I suppose. It's bi-directional, yet optional from the ChecklistItem side of things, and ButtonAction is the owner, storing the foreign key of the ChecklistItem as its primary key.
ChecklistItem Class:
#Entity
#Table(name = "CHECKLIST_ITEM", schema = "CHKL_APP")
public class ChecklistItem implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CHECKLIST_ITEM_ID_SEQ")
#SequenceGenerator(name = "CHECKLIST_ITEM_ID_SEQ", sequenceName = "CHKL_APP.CHECKLIST_ITEM_ID_SEQ")
private Long id;
#OneToOne(optional = true, mappedBy = "checklistItem", cascade = CascadeType.ALL)
private ButtonAction button;
//...
}
ButtonAction Class:
#Entity
#Table(name = "ACTION_BUTTON", schema = "CHKL_APP")
public class ButtonAction implements Serializable {
#Id
#Column(name = "checklist_item_id", unique = true, nullable = false, insertable = true, updatable = false)
#GenericGenerator(name = "generate-from-checklist-item", strategy = "foreign", parameters = #Parameter(name = "property", value = "checklistItem"))
#GeneratedValue(generator = "generate-from-checklist-item")
private Long checklistItemId;
#OneToOne
#PrimaryKeyJoinColumn
#JsonIgnore
private ChecklistItem checklistItem;
//...
}
I'm using SpringBoot so I've just got a ChecklistItemRepository interface that extends SpringBoot's CrudRepository:
public interface ChecklistItemRepository extends CrudRepository<ChecklistItem, Long> {}
In my ChecklistItem service, I've configured the save method to work like so:
#Service
#Transactional
public class ChecklistItemServiceImpl implements ChecklistItemService {
#Override
public ChecklistItem saveChecklistItem(ChecklistItem checklistItem) {
processButtonAction(checklistItem);
return checklistItemRepository.save(checklistItem);
}
private void processButtonAction(ChecklistItem checklistItem,String username) {
ButtonAction button = checklistItem.getButton();
if(button != null) {
button.setChecklistItem(checklistItem);
if(checklistItem.getId() != null){
button.setChecklistItemId(checklistItem.getId());
}
}
}
//...
}
So whenever the ChecklistItem gets saved (via POST or PUT), it's updating that ButtonAction (when the user has selected to include one) with a reference to the ChecklistItem, and its ID (if not null) before the save is invoked.
Here's my problem... When a user PUTs a ChecklistItem with a NEW ButtonAction (User initially POSTed a ChecklistItem without a ButtonAction), I get the following error:
org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property [com.me.chklapp.checklistitem.action.ButtonAction.checklistItem];
nested exception is org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.me.chklapp.checklistitem.action.ButtonAction.checklistItem]
Every 'answer' I've found online is saying that the relationship needs to be set, but I'm already doing that in my service. I've verified that it's doing just so by debugging and checking that each object has a non-null reference to the other. Also, I couldn't find anyone else having the same problem I'm having, where in some cases, it saves, and in others it breaks; it was all-or-nothing in those cases.
The only fishy thing I was able to see when I turned on more detailed hibernate logging was that right before the error was thrown, it does a select on the buttonaction table where the cheklistitem id matches. I'm guessing Hibernate does this to determine whether it needs to do an insert or an update on the buttonaction table. But maybe it's then using that empty row instead of the ButtonAction object that's on my ChecklistItem?
Thanks for your help!
To be honest, I'm still not sure why my original problem presented or why this solution works, so if anyone can shed some light on those things, PLEASE comment; I wish to understand better.
Kudos to #CarlitosWay and #Matthew for working with me to try and find a solution. CarlitosWay was on to something when he commented that GeneratedValue and GenericGenerator weren't needed. I couldn't just remove them; I needed some way to tell Hibernate where to get ButtonAction's ID from, but it got me started down the track to find alternative configurations. I discovered one that looked promising: MapsId. I looked at some examples and fanangled around with things to see what would work. If nothing else, this seemed to confirm that I've got somewhat of a unique setup, as my solution does not resemble the usual examples of what MapsId is used for.
I'm posting my resulting code below, but again, I'm still not entirely sure how Hibernate's working with all of this, so I may have some superfluous annotations here. Please let me know how I can clean this up if possible.
ButtonAction Class:
#Entity
#Table(name = "ACTION_BUTTON", schema = "CHKL_APP")
public class ButtonAction implements Serializable {
#Id
#Column(name = "checklist_item_id", unique = true, nullable = false, insertable = true, updatable = false)
private Long checklistItemId;
#OneToOne
#MapsId
#PrimaryKeyJoinColumn
#JsonIgnore
private ChecklistItem checklistItem;
//...
}
Basically I exchanged the GenericGenerator and GeneratedValue annotations on the checklistItemId for the MapsId annotation on the checklistItem. In most other examples I'd seen, the MapsId annotation was on the other class (which would be ChecklistItem in this case), but I'm thinking since ButtonAction is the owner of the association and where the ID is coming from, it needs to be on the native object. ChecklistItem, ChecklistItemRepository, and ChecklistItemServiceImpl are all unchanged from my original code in my question.
Theoretically, my original code is the Hibernate way to do this JPA equivalent. But since they behave differently, I must be misunderstanding something so if you know the reason, please respond!
Consider creating a new ButtonAction whenever an owner ChecklistItem is first created. Then update the ButtonAction as needed.
I'm not exactly sure, but I think there is some Hibernate issue when trying to derive IDs at a later time.
Creating both ButtonAction and ChecklistItem at the same time should work.

Why doesn't JPA update the foreign key on a OneToOne relation?

I'm using PlayFramework 2.3 and i have the following classes:
MyEntity.java:
#Entity(name = "myentity")
public class MyEntity extends Model {
#Id
#GeneratedValue
public long id;
#OneToOne(cascade = CascadeType.ALL, optional = true)
#JoinColumn(name = "actual_version_id", nullable = true)
public Version actualVersion;
#OneToOne(optional = true)
#JoinColumn(name = "next_version_id", nullable = true)
public Version nextVersion;
...
}
Version.java
#Entity(name = "version")
public class Version extends Model {
#Id
#GeneratedValue
public long id;
#OneToOne
#JoinColumn(name = "entity_id", nullable = false)
public MyEntity entity;
...
}
When i want to make a new version for the entity, i copy it by detach, set the id to 0, and persist like this:
public Version clone(){
JPA.em().detach(this);
this.id = 0;
JPA.em().persist(this);
return this;
}
If i use the following code it works properly (first code)
entity.nextVersion = entity.actualVersion.clone();
JPA.em().flush();
entity.actualVersion = entity.nextVersion;
entity.nextVersion = null;
JPA.em().flush();
I didn't really like this code, because i could use it like this (second code)
entity.actualVersion = entity.actualVersion.clone();
JPA.em().flush();
But if I do this the foreign key doesn't update in the 'entity' table and i don't know why. Can anybody tell me what's the difference between the two implementtation of cloning? It seems some JPA black magic for me, but i couldn't find the answer.
EDIT:
This is a refactored code to make it easier to undestand. I'm not overriding any function from the Object class, or any other (my clone() function is called newRound in the original code with 2 parameters for example)
I don't really want to make any model modification like adding CascadeType.ALL to the annotation, or anything like that, because this is a program in production now, and i don't know what bugs would that make.
I just want to know why the first code updates the foreign key in the entity (actual_version_id) and the second doesn't. I think it has to be something with that CascadeType.ALL annotation parameter at the actualVersion variable.
Be very careful with clone() in a JPA setting; the JPA environment usually adds tracking properties to the bytecode of a class, and might get confused. Instead, override the default clone() to create an actual object and copy over all properties manually, one-by-one.

Hibernate and JPA: how to make a foreign key constraint on a String

I am using Hibernate and JPA. If I have two simple entities:
#Entity
#Table(name = "container")
public class Container {
#Id
#Column(name="guid")
private String guid;
}
#Entity
#Table(name="item")
public class Item {
#Id
#Column(name="guid")
private String guid;
#Column(name="container_guid")
private String containerGuid;
}
and I want to insure that inserting an Item fails if the referenced Container does not exist. I would prefer not to have a Container object populated inside the item object (ManyToOne), how would I do this if it is possible to do?
You can declare arbitrary constraint using columnDefinition attribute:
#Column(name="container_guid",
columnDefinition = "VARCHAR(255) REFERENCES container(guid)")
private String containerGuid;
Note, however, that Hibernate doesn't know anything about this constraint, so that, for example, it may not perform inserts in proper order with respect of it and so on.
Therefore it would be better to create a #ManyToOne relationship. If you are afraid of extra SQL query for Container needed to set this property, you can use Session.load()/EntityManager.getReference() to get a proxy without issuing actulal query.
Try using below relationship mapping
RelationShip Mapping
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#ManyToOne()
#ManyToMany()
<>
#JoinColumn(name="<>")

Categories

Resources