Seam/Hibernate/JPA -- Duplicate primary key exception? - java

I've got an object model that is persisted using Seam and JPA (Hibernate). It looks something like this:
#Entity(name = "MyObject")
public class MyObject {
...
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_myobj")
#SequenceGenerator(name = "seq_myobj", sequenceName = "seq_myobj")
private Long id = null;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
#NotNull
private MySubObject subObjA=null;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
#NotNull
private MySubObject subObjB=null;
...
}
#Entity(name = "MySubObject")
public class MySubObject {
...
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_mysubobj")
#SequenceGenerator(name = "seq_mysubobj", sequenceName = "seq_mysubobj")
private Long id = null;
}
I've defined my #ManyToOne annotations correctly and everything. However, if I try and persist an instance of MyObject where both subObjA and subObjB are set, I get an exception saying I've got a duplicate primary key one of the sub obj's. What would cause this behavior? Both objects have their identifier types set to SEQUENCE, and I have no problem if I set one or the other. It's only when I set both that I get the exception.
I'm running Seam 2.2 and my backend database is PostgreSQL. Any thoughts on what could be causing this strange behavior? I thought both objects would be persisted as part of the same transaction and that the correct primary keys would be assigned automatically. Like I said, if I only set one of the objects there is no issue. It only happens when I set them both. Any help you can give would be GREATLY appreciated.
EDIT I've noticed some strange behavior in testing out various things, however. If I create MyObject programmatically and set all of its properties (including subObj) it persists with no problem. However, if I enter the properties using a form, I get the error. Could it have something to do with transactions?

If you override equals/hashCode in MySubObject class be sure that these methods only check the surrogate key id (in such a case you should avoid them completely).
If equals/hashCode methods work with some business key properties, make sure that these keys are unique before persisting.

Ran through a battery of tests and different scenarios, and have found occasions when it works. So, it looks like there is a bug in my action class when I got to submit and persist to the database.

Related

NullPointerException happens with FetchType.LAZY withi hibernate and spring boot

Update
I'd like to note that #sainr's answer Converting Hibernate proxy to real entity object does solve the problem. But the issue behind the scene is my SiteEntity having a final modifier of it's setControllerEntity and getControllerEntity, which I didn't raise in my question. And I apologize.
Remove the final modifier. Then Hibernate can initialize the proxy objects just fine.
The explanation can be found in another answer on Stack Overflow.
I have three entities as following
#Entity
#Table(name = "controller")
public class ControllerEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, updatable = false)
private long id;
}
#Entity
#Table(name = "site")
public class SiteEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "controller_id", nullable = false)
private ControllerEntity controllerEntity;
}
#Entity
#Table(name = "device")
public class DeviceEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "site_id", nullable = true)
private SiteEntity siteEntity;
}
After I found the device entity, I try to get the controllerEntity directly from it.
final DeviceEntity deviceEntity1 = deviceRepository.findOne(1L);
System.out.println(deviceEntity1.getSiteEntity().getControllerEntity().getId());
But it results a java.lang.NullPointerException which is caused by the null controllerEntity in the siteEntity.
Also, even if I tried to use siteRepositoy to fetch the siteEntity again. the controllerEntity of it is still null.
After I removed the fetch = FetchType.LAZY from both the DeviceEntity and SiteEntity, NPE doesn't happen anymore.
But it seems odd and doesn't make sense. Can I use FetchType.LAZY while expecting hibernate fetch the correct value?
Thanks.
To give you an access to the field declared with FetchType.LAZY, Hibernate constructs a proxy with CGLIB. Consequently, when you're calling a getter for such field (in your case, getSiteEntity() or getControllerEntity()), you're not accessing the field value directly -- instead, the call is passed to the proxy object of Hibernate. In turn, Hibernate tries to load the actual value from the data store and in order to do this, it would require an active Hibernate session to access the DB. Most likely, in your case, the Hibernate session is already closed and such lazy load fails, giving you an effectively null value of the field.
There are basically two ways to solve this:
Use FetchType.EAGER that would load all field values along with the holding object DeviceEntity
Transform a proxy object into a real object (check Converting Hibernate proxy to real entity object) and access it in a regular way
Think about it, whether you really need a lazy load in your case. If you are not storing plenty of heavy objects in child fields to load them on demand, probably switching to FetchType.EAGER will be the easiest way.
Hope that helps.
Hibernate work with primitive types sometimes not very well. Try to replace
private long id
to
private Long id
For primary keys in Hibernate it is better to use wrapper classes instead of primitive types.

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.

Best practice to prepare entity for view in Spring mvc

I have a JPA entity that contains collections of another entities instances.
I need to remove some of the instances from the collection and change other stuff, just for View and I don't want to change my database content.
What is the best way to do it?
Make a clone of my object and work with it.
Remove lazy load (or get all that I need from this main bean). Then close hibernate session, and work with the detached object.
Anything else?
UPDATE
My bean
#Entity
#Table(name = "client")
public class Client extends AbstractPersistentEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CLIENTS_SEQ")
#SequenceGenerator(name = "CLIENTS_SEQ", sequenceName = "clients_seq")
private Integer id;
#NotEmpty
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "clientId")
private Collection<ContactPhones> contactPhonesCollection;
}
And I want to remove some of ContactPhones for view. But it can be much complicated, may be in ContactPhones will be another collection and I want to remove it. Something like that.
If you don't want to actually remove any row from the database, in my opinion the best choice is to detach the entity from the session and work with it as with any other Java object.

Categories

Resources