Eclipselink inserting entity with 0 ID - java

I just discovered a rather strange problem with EclipseLink (both 2.3.0 and 2.4.1) and just wanted to ask if anyone can confirm that this is a bug and not just that I am missing something obvious ...
Basically, I have two entities involved, let us simply call them A and B. A had an eager (if that matters), non-cascading many-to-one unidirectional reference to B modelled in the database with a join column. The DB table A contains the columns ID (PK), B_ID (FK to B.ID), and more, the table B contains the columns ID and a few more.
Now, I had a list of As, which should be updated with references to new B instances. In pseudo-code something like:
for(A a : list) {
// using some criteria, check if perhaps a matching B
// entity is already found in the database
B b = perhapsGetExistingBFromDatabase();
if(b == null) {
// no existing B was found, create a new
b = new B();
// set values in B
b.setFoo(4711),
b.setBar(42);
// save B
em.merge(b);
}
// set reference from A to B ...
a.setB(b);
// ... and update A
em.merge(a);
}
Since the reference was non-cascading, it was necessary to merge both b and a. Everything worked as expected.
Then, someone (I) changed the cascade type of the relationship from none to merge/persist, since that was required somewhere else in the code. I expected the old code to work, merging b is not really required, shouldn't IMHO however hurt? A brief test confirmed that it still worked, the new B entity was inserted and A updated accordingly.
BUT, it only works if there is only one A entity in the list. Running through the loop a second time causes EclipseLink to auto-flush the session since perhapsGetExistingBFromDatabase does a "SELECT .. FROM B", there is a merged B entity cached and it wants the database table to be up to date. Using FINEST logging level and breakpoints in the code, I can verify that EclipseLink determines that it is required to generate an id for the new B entity, it invokes the sequence generator and also sets the id in the correct field of the entity. Still, EclipseLink invokes SQL statements similar to these:
INSERT INTO B (ID, ...) VALUES(0, ...);
UPDATE A SET B_ID = 0 WHERE ID = ...;
The generated id is lost somewhere and EclipseLink tries to create a new entity with id=0. The data is indeed invalid and later, EclipseLink also throws a PersistenceException: Null or zero primary key encountered in unit of work clone.
Bug or my mistake?
Edit: James asked for the mapping of B.ID:
#Id
#GeneratedValue(generator = "sq_idgen", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "sq_idgen", sequenceName = "...", allocationSize = 100)
#Column(name = "id")
protected long id;
Note, that removing the unneccesary em.merge(b); solves my problem. It is just not obvious to me why the invocation of merge causes EclipseLink to fail completely, trying to insert a B instance without a populated id.

That is odd, how is the Id of B mapped?
Seems like the merged might somehow be getting two separate instances of B (as there is nothing to identify them as being the same as they have no Id). Not that merge() is normally not required, it is only required for detached objects, such as when using serialization (try using persist instead).
To avoid the flush you can set the flushMode in the EntityManager or persistence unit to COMMIT instead of AUTO.

Related

Unique Constraint Violation on Hibernate #ManyToMany Relationship

I have 3 entities in my database, let's call them A, B, and C.
A and B share a many-to-many relationship with one another. A has a SortedSet of Bs, but B does not reference A (no collection or w/e configured). So we have the following.
// Inside class A
#ManyToMany
#JoinTable(name = "a_b",
joinColumns = {#JoinColumn(name = "a_id")},
inverseJoinColumns = {#JoinColumn(name = "b_id")})
#LazyCollection(LazyCollectionOption.FALSE)
#SortNatural
private SortedSet<B> bSet = new TreeSet<B>();
B and C have a one-to-many relationship with one another (1 B to many Cs). C has a B in it's entity, but B does not have a reference to it's many C entities. So we have the following
// Inside class C
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "b_id", nullable = false, updatable = true)
protected B b;
We have a sync process that runs a nightly job to update the A entities and their associations to B entities (which doesn't change very often). We end up having something similar to the following (really, it's a lot more complicated with DAOs, services, etc).
// Get the A value to be updated
A aToUpdate = entityManager.find(A.class, idForA);
// Out of scope of the question, but we need to figure out B via a string field on C
C cValue = myDao.getByProperty("fieldName", fieldValue);
// Determine the B values to set on aToUpdate
B bToSetOnA = cValue.getB();
TreeSet<B> bSet = new TreeSet<>();
bSet.add(bToSetOnA);
// Update aToUpdate
aToUpdate.setBSet(bSet);
aToUpdate = entityManager.merge(aToUpdate);
entityManager.flush();
When this happens, the following error occurs.
ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (Sync Thread)
Duplicate entry 'myAId-almostMyBId' for key 'uk_a_b'
One interesting thing is that the almostMyBId is 1 character short of the actual B ID. But only the full ID appears in the a_b table.
When I look through the code base, there is a uk_a_b constraint on an index on the a_b table. This is from liquibase.
<createIndex indexName="uk_a_b" tableName="a_b" unique="true">
<column name="a_id"/>
<column name="b_id"/>
</createIndex>
If I remove the aToUpdate.setBSet(bSet); line, the error disappears.
I added logging and confirmed that the IDs of the new bSet are the same as the old ones on aToUpdate.
Somehow, Hibernate seems to be trying to re-add the association, even though we're doing a merge and the associations haven't really changed.
I've tried changing a few CascadeType and FetchType things here and there, but the error doesn't seem to go away. Anyone have an idea of what's going on?
This is very specific to my situation, but figured I'd post the answer anyways in case anyone read my question and has been working on it.
Another developer on my team was tasked with speeding up a report based on the a and a_b tables. To avoid needing to do a JOIN and a WHERE clause, the developer copied the data over from a_b into a new table (using the WHERE clause) and added triggers so that whenever things were updated, it would insert into the new table. This new table had a constraint named the same as the a_b table (namely, uk_a_b). The duplicate case was not properly handled, so an error was being thrown. Due to the name similarities, it seemed to be the a_b table causing the issue when it was really the new table. Fun times.

Hibernate #MapsId why getting this errors?

So, I have Class A and Class B.
They share their primary key, using the following configuration:
In Class A I reference Class B as a child
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
public B getB()
{
return b;
}
In Class B, in order to get ID from parent Class A, I use the following annotations:
#Id
#GeneratedValue(generator = "customForeignGenerator")
#org.hibernate.annotations.GenericGenerator(name = "customForeignGenerator", strategy = "foreign", parameters = #org.hibernate.annotations.Parameter(name = "property", value = "a"))
#Column(name = "a_id")
public Long getId()
{
return id;
}
#MapsId("id")
#OneToOne(mappedBy = "b")
#PrimaryKeyJoinColumn
public A getA()
{
return a;
}
The problem is that uppon saving A with
session.saveOrUpdate(aInstance);
DB returns the following error:
Duplicate entry '123456' for key 'PRIMARY'
This tells us 2 things, first is that #MapsId is working correctly, giving A's Id to B as it should, the second is that hibernate decided it was a 'save' and not an 'update', and this only happens on saveOrUpdate when Id is null right? (wierd?)
The usual solution would be to get the old B from DB and merge, if existed, but that arrises a whole lot of problems like also getting the old A from DB to session or making the dreaded "a different object with the same identifier value was already associated with the session" hibernate errors for the assossiated objects. Also not very performance friendly, doing unecessery DB hits.
Is there an error in my anotations? Am I doing it wrong? What is the normal configuration for this?
EDIT:
It kind of defeats the purpose of using #MapsId, setting the IDs manually, but since no solution was found I did set the IDs manually like this:
if(aInstance.getId() != null)
aInstance.getB().setId(aInstance.getId());
session.saveOrUpdate(aInstance);
Just until moments ago this was returning the following error:
org.hibernate.StaleStateException:
Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1
But for some reason it stopped throwing the error and now it works. In all cases, the previous code is still all valid since aInstance might not have Id, and in that case, the MapId works perfectly inserting a new A and B in BD. The problem was only on Update.
Was\Is it an hibernate bug? probably. I'll let you guys know when StaleStateException turn up again.
For now this is a temporary solution, until someone comes up with the actual solution.
I finally found the answer to all the problems.
To understand the root of the problem we must remind how saveOrUpdate(object) works.
1) If object has ID set, saveOrUpdate will Update else it will Save.
2) If hibernate decides it is a Save but object is already on DB, you want to update, the Duplicate entry '123456' for key 'PRIMARY' exception will occur.
3) If hibernate decides it is an Update but object is not in DB, you want to save, the StaleStateException Exception occurs.
The problem relies on the fact that if aInstance exists in DB and already has an ID, #MapsId will give that ID to B ignoring the rules above, making Hibernate think B also exists in DB when it may not. It only works properly when both A and B dont exist in DB or when they both exist.
Therefor the workaround solution is to make sure you Set the ID only and only if each object exists in DB, and set ID to null when it does not:
B dbB = (B) unmarshaller.getDetachedSession().createCriteria(B.class).add(Restrictions.idEq(aInstance.getId())).uniqueResult();
if (dbB != null) //exists in DB
{
aInstance.getB().setId(aInstance.getId()); //Tell hibernate it is an Update
//Do the same for any other child classes to B with the same strategy if there are any in here
}
else
{
aInstance.getB().setId(null); //Tell hibernate it is a Save
}
unmarshaller.getDetachedSession().clear();
(using detached session, so that main session stays clear of unwanted objects, avoiding the "object with the same identifier in session" exception)
If you dont need the DB object, and only want to know if it exists or not in the DB, you can use a Count, making it much lighter:
String query = "select count(*) from " + B.class.getName() + " where id = " + aInstance.getId();
Long count = DataAccessUtils.uniqueResult(hibernateTemplate.find(query));
if (count != null && count > 0)
{
aInstance.getB().setId(aInstance.getId()); // update
}
else
{
aInstance.getB().setId(null); // save
}
Now you can saveOrUpdate(aInstance);
But like i said, #MapsId strategy is not very Hibernate friendly.
Some key realization that helped me understanding #MapsId better:
The #MapsId annotation changes the ID type of the entity from Assign to Generate. (I wish I could override this behavior and set the IDs manually.)
Generate assumes that an entity with a non null ID already exists in DB. Therefore, setting the ID manually leads to StaleObjectException because hibernate issues an EntityUpdateAction instead of Create, but there is nothing to update inside the DB.
On persist the null ID will be automatically set by using the ID of the other side of the #OneToOne relationship.
If the other side is missing and the ID is null an Exception is raised.

Struts2 and Hibernate insert operation error [duplicate]

org.hibernate.HibernateException: identifier of an instance
of org.cometd.hibernate.User altered from 12 to 3
in fact, my user table is really must dynamically change its value, my Java app is multithreaded.
Any ideas how to fix it?
Are you changing the primary key value of a User object somewhere? You shouldn't do that. Check that your mapping for the primary key is correct.
What does your mapping XML file or mapping annotations look like?
You must detach your entity from session before modifying its ID fields
In my case, the PK Field in hbm.xml was of type "integer" but in bean code it was long.
In my case getters and setter names were different from Variable name.
private Long stockId;
public Long getStockID() {
return stockId;
}
public void setStockID(Long stockID) {
this.stockId = stockID;
}
where it should be
public Long getStockId() {
return stockId;
}
public void setStockId(Long stockID) {
this.stockId = stockID;
}
In my case, I solved it changing the #Id field type from long to Long.
In my particular case, this was caused by a method in my service implementation that needed the spring #Transactional(readOnly = true) annotation. Once I added that, the issue was resolved. Unusual though, it was just a select statement.
Make sure you aren't trying to use the same User object more than once while changing the ID. In other words, if you were doing something in a batch type operation:
User user = new User(); // Using the same one over and over, won't work
List<Customer> customers = fetchCustomersFromSomeService();
for(Customer customer : customers) {
// User user = new User(); <-- This would work, you get a new one each time
user.setId(customer.getId());
user.setName(customer.getName());
saveUserToDB(user);
}
In my case, a template had a typo so instead of checking for equivalency (==) it was using an assignment equals (=).
So I changed the template logic from:
if (user1.id = user2.id) ...
to
if (user1.id == user2.id) ...
and now everything is fine. So, check your views as well!
It is a problem in your update method. Just instance new User before you save changes and you will be fine. If you use mapping between DTO and Entity class, than do this before mapping.
I had this error also. I had User Object, trying to change his Location, Location was FK in User table. I solved this problem with
#Transactional
public void update(User input) throws Exception {
User userDB = userRepository.findById(input.getUserId()).orElse(null);
userDB.setLocation(new Location());
userMapper.updateEntityFromDto(input, userDB);
User user= userRepository.save(userDB);
}
Also ran into this error message, but the root cause was of a different flavor from those referenced in the other answers here.
Generic answer:
Make sure that once hibernate loads an entity, no code changes the primary key value in that object in any way. When hibernate flushes all changes back to the database, it throws this exception because the primary key changed. If you don't do it explicitly, look for places where this may happen unintentionally, perhaps on related entities that only have LAZY loading configured.
In my case, I am using a mapping framework (MapStruct) to update an entity. In the process, also other referenced entities were being updates as mapping frameworks tend to do that by default. I was later replacing the original entity with new one (in DB terms, changed the value of the foreign key to reference a different row in the related table), the primary key of the previously-referenced entity was already updated, and hibernate attempted to persist this update on flush.
I was facing this issue, too.
The target table is a relation table, wiring two IDs from different tables. I have a UNIQUE constraint on the value combination, replacing the PK.
When updating one of the values of a tuple, this error occured.
This is how the table looks like (MySQL):
CREATE TABLE my_relation_table (
mrt_left_id BIGINT NOT NULL,
mrt_right_id BIGINT NOT NULL,
UNIQUE KEY uix_my_relation_table (mrt_left_id, mrt_right_id),
FOREIGN KEY (mrt_left_id)
REFERENCES left_table(lef_id),
FOREIGN KEY (mrt_right_id)
REFERENCES right_table(rig_id)
);
The Entity class for the RelationWithUnique entity looks basically like this:
#Entity
#IdClass(RelationWithUnique.class)
#Table(name = "my_relation_table")
public class RelationWithUnique implements Serializable {
...
#Id
#ManyToOne
#JoinColumn(name = "mrt_left_id", referencedColumnName = "left_table.lef_id")
private LeftTableEntity leftId;
#Id
#ManyToOne
#JoinColumn(name = "mrt_right_id", referencedColumnName = "right_table.rig_id")
private RightTableEntity rightId;
...
I fixed it by
// usually, we need to detach the object as we are updating the PK
// (rightId being part of the UNIQUE constraint) => PK
// but this would produce a duplicate entry,
// therefore, we simply delete the old tuple and add the new one
final RelationWithUnique newRelation = new RelationWithUnique();
newRelation.setLeftId(oldRelation.getLeftId());
newRelation.setRightId(rightId); // here, the value is updated actually
entityManager.remove(oldRelation);
entityManager.persist(newRelation);
Thanks a lot for the hint of the PK, I just missed it.
Problem can be also in different types of object's PK ("User" in your case) and type you ask hibernate to get session.get(type, id);.
In my case error was identifier of an instance of <skipped> was altered from 16 to 32.
Object's PK type was Integer, hibernate was asked for Long type.
In my case it was because the property was long on object but int in the mapping xml, this exception should be clearer
If you are using Spring MVC or Spring Boot try to avoid:
#ModelAttribute("user") in one controoler, and in other controller
model.addAttribute("user", userRepository.findOne(someId);
This situation can produce such error.
This is an old question, but I'm going to add the fix for my particular issue (Spring Boot, JPA using Hibernate, SQL Server 2014) since it doesn't exactly match the other answers included here:
I had a foreign key, e.g. my_id = '12345', but the value in the referenced column was my_id = '12345 '. It had an extra space at the end which hibernate didn't like. I removed the space, fixed the part of my code that was allowing this extra space, and everything works fine.
Faced the same Issue.
I had an assosciation between 2 beans. In bean A I had defined the variable type as Integer and in bean B I had defined the same variable as Long.
I changed both of them to Integer. This solved my issue.
I solve this by instancing a new instance of depending Object. For an example
instanceA.setInstanceB(new InstanceB());
instanceA.setInstanceB(YOUR NEW VALUE);
In my case I had a primary key in the database that had an accent, but in other table its foreign key didn't have. For some reason, MySQL allowed this.
It looks like you have changed identifier of an instance
of org.cometd.hibernate.User object menaged by JPA entity context.
In this case create the new User entity object with appropriate id. And set it instead of the original User object.
Did you using multiple Transaction managers from the same service class.
Like, if your project has two or more transaction configurations.
If true,
then at first separate them.
I got the issue when i tried fetching an existing DB entity, modified few fields and executed
session.save(entity)
instead of
session.merge(entity)
Since it is existing in the DB, when we should merge() instead of save()
you may be modified primary key of fetched entity and then trying to save with a same transaction to create new record from existing.

Hibernate: Criteria query taking a long time with three-level relationship

I'm having trouble using Criteria to find all the objects that belong to a certain entity. My model is like the following (just showing the relevant code):
#Entity...
Class A {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "ide_b", referencedColumnName = "ide_b", nullable = false)
private B b;
}
#Entity...
class B {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "ide_c", referencedColumnName = "ide_c", nullable = false)
private C c;
}
#Entity...
class C {
...
}
My Criteria query is as simple as this (actually, there would be some filters, but they're not being used):
Criteria criteria = getSession().createCriteria(A.class);
criteria.list(); // MY SYSTEM STAYS HERE FOREVER WHEN RUNNING AGAINST A REAL DATABASE
Would anybody have a clue on this issue? The system just stays forever on the line "criteria.list()" and never returns.
I've already tested the SQL that it generates directly on the database and it works just fine.
I've already tested this query both with code involving only class A having a reference to B and class A having a reference to C (directly). They both work. This third level in the association seems to be causing problems... Note: my Hibernate version is an old one, like 3.0.0
You need to remove fetch type join. Eager is fine for your data layout, but do your really need that much data at once?
With default fetch, hibernate will query table A only. Then for each B's foreign key, queries B only once per key. Same done for C.
i.e. once b_id=1 is fetched from B, it wont be fetched again even it used with million rows of A. Hibernate's 2 nd level cache handles it.
With join type fetch, for each row from A, you'll get 1 single row containing columns of all 3 tables.
If your relation was OneToMany, then you'll get A x B x C rows returned. But since ManyToOne, there is no such issue.
Your problem is even this query returns 1 big row for each items at A, there is too much B and C replication. So DB response is huge so processing is hard as well for both DB and your app.
Well, actually I think the problem is something else. I've changed both relationships (A -> B and B -> C) to lazy (and removed the #Fetch). Then I queried for a specific A object. So far so good. I was able to get the object and I was able to call a.getB() successfully.
Nonetheless, when I call b.getC() Hibernate does not return the C object to me (I mean, Hibernate gets stuck at this line).
The query Hibernate creates to fetch the C object when I call b.getC() is the following:
select
myCtable0_.id as id1_85_0_,
myCtable0_.name as nam2_85_0_
from
MyTableC myCtable0_
where
myCtable0_.id in (?, ?)
The C table has an id field (the primary key) and a name (varchar).
Another option is to fetch all data separately. Then hibernate cache will have each entity and then, I think, criteria query will be faster.

JPA: How do I avoid loading an object simply so I can store its ID in the database?

This question is so simple, you can probably just read the code
This is a very simple performance question. In the code example below, I wish to set the Owner on my Cat object. I have the ownerId, but the cats method for requires an Owner object, not a Long. Eg: setOwner(Owner owner)
#Autowired OwnerRepository ownerRepository;
#Autowired CatRepository catRepository;
Long ownerId = 21;
Cat cat = new Cat("Jake");
cat.setOwner(ownerRepository.findById(ownerId)); // What a waste of time
catRepository.save(cat)
I'm using the ownerId to load an Owner object, so I can call the setter on the Cat which is simply going to pull out the id, and save the Cat record with an owner_id. So essentially I'm loading an owner for nothing.
What is the correct pattern for this?
First of all, you should pay attention to your method to load an Owner entity.
If you're using an Hibernate Session :
// will return the persistent instance and never returns an uninitialized instance
session.get(Owner.class, id);
// might return a proxied instance that is initialized on-demand
session.load(Owner.class, id);
If you're using EntityManager :
// will return the persistent instance and never returns an uninitialized instance
em.find(Owner.class, id);
// might return a proxied instance that is initialized on-demand
em.getReference(Owner.class, id);
So, you should lazy load the Owner entity to avoid some hits to the cache nor the database.
By the way, I would suggest to inverse your relation between Owner and Cat.
For example :
Owner owner = ownerRepository.load(Owner.class, id);
owner.addCat(myCat);
Victor's answer is correct (+1 from me), but requires going through the EntityManager or Hibernate session. Assuming the repositories you have autowired are JPA repositories from Spring Data and you would prefer to go through them, use the JpaRepository#getOne method. It calls EntityManager#getReference, so it does the same thing, returning a proxy to the entity.
I do not think the relationship necessarily needs to be reversed here, which mapping to use depends on the situation. In many cases many-to-one is preferred.
Probably not what you were looking for, but nothing in your question implies that you have to solve this with JPA. Some things are just much much simpler with plain old SQL:
INSERT INTO cat (name, owner_id) VALUES ('Jake', 21)
If you are using Hibernate you can do this:
Long ownerId = 21;
Cat cat = new Cat("Jake");
Owner owner = new Owner();
owner.setId(ownerId);
cat.setOwner(owner);
catRepository.save(cat)
It's not standard JPA, but, if you are not willing to migrate to other JPA provider, it's the best from a performance perspective.
Update
As Nathan pointed out, you need to make sure the Owner is not already associated (in which case you can get a NonUniqueObjectException since the Persistence Context can have at most one entity associated in the 1st level cache).
Using EntityManager.contains(entity) doesn't help in this case, since Hibernate stores the entities in an IdentiyHashMap, where the key is the Object reference itself.
So you should use this method when, for example, you have a use case where you must insert these entities for the first time, or when you need to update them and the Owner hadn't been loaded in the current running Persistence Context (either directly or through JPQL or a Criteria API).
Otherwise, use EntityManager.getReferemce(Class entityClass, Object primaryKey).
One more way (can come handy sometimes in legacy code or db schema):
#Entity
public class Cat {
#Column(name = "OWNER_ID")
private Long ownerId;
#ManyToOne
#JoinColumn(name = "OWNER_ID", insertable = false, updatable = false)
private Owner owner;
}

Categories

Resources