I am using Spring-Boot and JPA.
For saving and also updating a record there is the one method save of CrudRepository.
I use a generated long id for primary key of my record.
Now I need to update a already existing record. That will fail with an duplication exception if the primary key id does not
match the value that is already in the database. If no id is given JPA would assume an insert and that fails because the old record already exists.
So what is the best strategy for that?
I do not want to look for the id of the existing record before doing a save for updating my record.
Is there some default value for id that I an use for marking it for update instead of insert?
I don't think Hibernate has such functionality. Some databases support this with their native capabilities (insert ignore in MySQL or on conflict do nothing in Postgres), but you'd have to write SQL yourself. And some databases don't have this feature at all.
Probably, the only way to do this with Hibernate is to handle constraint violation exceptions. Note though if it happens, you may not be able to proceed with the transaction/session since Hibernate warns you that Session/EntityManager can't be further used reliably if it threw an exception.
Related
Our application is using Hibernate envers to provide auditing on updates to entities. Whenever an entity is modified (or inserted), a backup of the entity (i.e. the table’s row) is saved to an "AUD" table.
This provides us with auditing functionality (which is a requirement) and has been working well up until now. But we are now facing issues because we need to migrate to using UUIDs as data keys. Our application will be run in a distributed environment with limited or intermittent internet access, so we will be using SymmetricDS to manage data synchronisation, and UUIDs will allow us to do that without causing data conflicts.
The problem we are facing with Hibernate envers is that the RevisionNumber annotation that is used to add the REV number into the AUD table will only work with int, Integer, long and Long identifiers.
We have implemented a class called AuditRevision (that extends the Hibernate class DefaultRevisionEntity) with some extra attributes, and have implemented an AuditRevisionListener (that implements Hibernate’s EntityTrackingRevisionListener) to manage saving and updating the extra data. However, in order to allow us to save revisions using UUIDs instead of an auto incrementing id, we rewrote AuditRevision so that it doesn't extend DefaultRevisionEntity and instead defines its own UUID id.
We then saw the following error in our application:
Caused by: org.hibernate.MappingException: The field annotated with #RevisionNumber must be of type int, Integer, long or Long
at org.hibernate.envers.configuration.internal.RevisionInfoConfiguration.searchForRevisionInfoCfgInProperties(RevisionInfoConfiguration.java:224)
at org.hibernate.envers.configuration.internal.RevisionInfoConfiguration.searchForRevisionInfoCfg(RevisionInfoConfiguration.java:304)
at org.hibernate.envers.configuration.internal.RevisionInfoConfiguration.configure(RevisionInfoConfiguration.java:347)
at org.hibernate.envers.configuration.spi.AuditConfiguration.<init>(AuditConfiguration.java:119)
at org.hibernate.envers.configuration.spi.AuditConfiguration.getFor(AuditConfiguration.java:180)
at org.hibernate.envers.event.spi.EnversIntegrator.integrate(EnversIntegrator.java:76)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:312)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1859)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:852)
... 41 more
We are unsure how to continue. If we can't replace the long primary key of the AuditRevision table to use a UUID id, we could add a UUID (or another id or key) to the AuditRevision table and then use it along with the auto increment id as a composite key. The AUD tables will then need to reference the UUID, which we thought we could manage using a database trigger because we don't have control over the AUD tables.
Another option is that we could also rewrite the Hibernate envers library or fork their code and make the necessary modifications to support UUID.
Does anyone have any experience of this problem? How would you solve it?
We are using Hibernate 4.3.7.Final with a MySQL 5.7 database
first of all, I don't speak english correctly, so, sorry for the mistakes.
I having a problem, when I try to create a entity (with id NULL, then the database will assign it) and the creation fails, the entity is left with the generated ID. I need that the id remain null if the creation fails, because in my service, I check if the entity passed to "save" method, contains ID or not, to make an update or an create.
I can set the ID in null if the creation fails, but when I making more complex transactions, is hard to change manually every ID in every entity, and some times, some objects are fetched from the database and others not.
I hope that I have explained correctly... Thanks.
PD: This is the field ID in the entity...
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
PD2: I using Spring framework by the way.
EDIT: I using PostgreSQL, and try every generation posible.
From the design perspective you should rely on exceptions not on null values.
In your service you can just catch the exception and you don't have to add if statement.
Much depends on the DB you are using , the generation method and the Hibernate Dialect.
In many situation the DB will assign the ID and Hibernate will just load the field with that value.
Looking a bit into both dialect and Postgres I think that you will have to undo id by hand, since postgres sequences are not affected by rollback (see this answer) so hibernate can get the current ID even for failed inserts.
You could use JPA lifecycle annotation to collect enough information about your entity to do a clean up after a failed save.
The IDENTITY generation strategy is not commonly preferred and here is why:
Hibernate cannot generate an identifier value before an INSERT operation. That's why Hibernate has to perform the INSERT query and SELECT immediately after it. In this way, Hibernate will populate the identifier value of your persisted entity instance.
I believe that PostgreSQL doesn't support the IDENTITY id generation strategy (Hibernate Identity, Sequence and Table (Sequence) Generator). If PostgreSQL supported the IDENTITY strategy, then your entities would not have id initialized because SELECT would never happen.
I'm using Java with Spring MVC and Hibernate. I have a bunch of entities and everything works fine. If, however, I add a column to one of my database columns, my service will start crashing until I also update the relevant java entity class with the new column.
Is there a way to tell Hibernate to ignore database columns it doesn't recognize? If you want to have a field in your entity that's not in your DB table, you would use #Transient on the field. I want the inverse of that.
If this is not possible, how do Hibernate services get deployed when there's a database update that has to go along with it?
Hibernate will not "crash" after new columns were added to a table managed by Hibernate. Hibernate scheme validation goes only as far as verifying that the mapped columns can be stored in the database, but will not look for unmapped columns in the database.
What is likely causing your problem is a new NOT-null field. Adding such a field will make it impossible for Hibernate to persist anything into that table since it is oblivious to the existence of this field and will not provide it at insertion-time. Solutions to this problem are:
Providing DEFAULT in the alter table operation for clients that do not use this field
Not marking the field not-null and performing nullability checks in another layer
Using pre-insert triggers to populate the empty fields
Alternatively you can even add the new field first, deploy your new version of your application, then mark the new field as not-null.
I have a project which which has hibernate classes mapped to the underlying tables. There is table which has a composite key.I am deleting the unique row and adding the same row in my hibernate object. Both these objects are part of another object which I am persisting into db. The program throws "UNIQUE CONSTRAINT VIOLATION" error which is presume is because insert is happening before delete operation. Is there way to resolve this using some setting in hibernate or should I have the necessary logic [outside hibernate object manipulation] in place to ensure this does not happen.
You shall ensure that object deletion is flushed to database before you attempt to
create another object with the same key. But in your case it would be better to just replace
data inside of object to be remover with new one. From hibernate point of view, same PC means same object.
When I have used Hibernate in the past, 90% of the time it makes no difference what order you call the insert or delete statement, or even the commit. In most cases, it matters where the session actually closes the Transaction unless you are managing your transaction yourself.
But one great thing about hibernate is that you shouldn't be removing a row that has the same key, you should just update its object instance with the relevant data and persist the update instance. Hibernate will take care of the rest...
I'm using JPA through the Play Framework.
I'm checking to see if a User object is cached, and if so I retrieve it and merge() it such that I can update fields and save the changes later:
user = (User) Cache.get("user-auth-" + sessionAuthToken);
if (user != null) {
user = user.merge(); // I believe this is the same as EntityManager.merge()
}
However when I do this I get the following error:
PersistenceException occured :
org.hibernate.exception.ConstraintViolationException:
could not insert: [models.User]
...
Caused by: com.mysql.jdbc.exceptions.jdbc4.
MySQLIntegrityConstraintViolationException:
Duplicate entry '1235411688335416533' for key 'authToken'
It seems like its trying to insert a new user, even though this user should be, and is already in the database. Why would merge() do that?
Or perhaps I'm going about this entirely the wrong way - advice would be appreciated.
I think it may be a poblem with hashCode() and equals(). If there are not implemented correctly, a new entity my be inserted instead of updateing an existing one.
I believe your problem is the way Play manages the JPA environment (and transactions).
Once you receive a request the framework immediately creates the JPA manager and a transaction. From that moment onwards all your Model entities are automatically linked to the manager.
Play facilitates working with this model in 2 ways:
You have to explicitly indicate that you want to save changes to an object (via save())
Transaction is committed automatically unless there is an exception or you flag it for rollback (JPA.setRollbackOnly())
By running "merge" you are trying to add to the Manager an entity which is already there, which causes the unique key exception. If you just load the entity from the cache, you will be able to modify and call save() once done, and it will work.
See What is the proper way to re-attach detached objects in Hibernate?. Merge tries to write the stale state to the db in order to overwrite possible other concurrent updates. The linked question mentions session.lock(entity, LockMode.NONE); as a possible solution, I haven't tried it though.
If authToken is not a primary key, then perhaps primary key of the User instance being merged doesn't match the primary key of its counterpart in the database, therefore merge() thinks that it's a new User and tries to insert it.
So, check the primary key of the User, perhaps it have been corrupted or lost somehow.
It's an entity definition problem; specifically with regard to primary key/ unsaved values.
The entity definition needs to be correct, for Hibernate to recognize it as 'already saved'. For example, having 'null' in a nullable version field can cause Hibernate to disregard any existing ID & regard it as unsaved.
This is a Hibernate question, not just JPA. JPA is the interface -- you're having trouble with a specific implementation.