Lets say a client updates an entity ( Ex: student entity ).
So we get the student Id and other modified fields (not all fields) from the client.
I read that we should pass the particular entity object to DAO in order to update.
But then , how will I get to form that entity object.Because I don't have all fields data to create a proper entity object.
Should I make two DB calls ?
The first call is to construct a proper entity object and then make the update by passing this updated entity object to the DAO.
The only way to avoid two DB calls is to use an update statement to update only th fields you have. E.g.
UPDATE Student SET someField1 = :field1, someField2 = :field2 WHERE ID = :id
Remember that update queries bypass optimistic locking checks.
If you use optimistic locking you should append the version to the where clause and also increment it.
UPDATE Student SET someField1 = :field1, version = version + 1 WHERE id = :id AND version = :version
After an executeUpdate you should check the affected rows:
1 : everything is ok
0 : the entity could either not be found by it's id. Maybe it was deleted in the meanwhile or the version did not match. In both cases you should raise an OptimisticLockException.
>1 : you should raise an exception to rollback the transaction.
Related
I have a field in my SQL table which needs to be updated by one and return a unique ID. But looks like it is not being updated on the latest data, especially when I give a lot of requests.
#Transactional
public interface CompanyRepository extends CrudRepository<Company, Integer> {
#Modifying(clearAutomatically = true, flushAutomatically = true)
#Query(value = "update Company c set c.accessId = :accessId WHERE c.id = :companyId AND c.accessId = :oldAccessId", nativeQuery = true)
int updateAccessId(#Param("companyId") Integer companyId, #Param("accessId") Integer accessId, #Param("oldAccessId") Integer oldAccessId);
}
Even with both clearAutomatically and flushAutomatically set to true, it is not working on the latest data.
I could see two update query being successful both with oldAccessId as the same.
Should the table design be changed?
PS : I have tried without nativeQuery = true as well.
What you have here is a classical race condition.
Two threads read the same entity, with identical accessId, increment it by one and then writing the result using the method you show in your question. Resulting in effectively only one update.
There are various ways how to fix this.
Use JPA and optimistic locking.
Assuming you have an attribute with #Version annotated you can do the
following in a single transactional method:
Load the entity.
increment the accessId.
persist the entity.
If another transaction tries to do the same on the same entity one of the two will get an exception. In that case retry until the update goes through.
Use the database.
Make reading and updating atomic in the database. Instead of passing the new value as parameter use a query like this one:
update Company c
set c.accessId = c.accessId + 1
WHERE c.id = :companyId
Make it a version attribute.
As mentioned above JPA already has #Version attributes which get updated on every change. Depending on the exact requirements you might be able to make accessId simply that attribute and get it updated automatically.
Check if any rows got updated.
Based on your comment your intention was to basically reimplement what JPA does with version attributes. If you do this you are missing a crucial part: checking that the update actually updated anything, by comparing the return value against 1.
in hibernate if I persist new entity it should become managed , moreover it should return the managed entity.
if the entity is detached , the merge will return managed entity but the one that I passed will still detached.
I've tried that in spring boot with hibernate and everything is working except the following case :
User transientUser=new User();
transientUser.setId(9L);
User managedTransientUser=userRepository.save(transientUser);
if I set the Id manually ( even with removing the auto generation) the entity that I pass ( transientUser) will still be unmanaged . If I use generation Identity then the returned and passed entity are the same ( the id is null and the DB will auto increment ) is that expected ?
That's simply because you're not calling persist(). You're calling userRepository.save().
This method tests if the entity is new (by checking if it already has an ID). If it is, it calls persist(). Otherwise it calls merge(). Since your entity already has an ID, it calls merge().
I am new in hibernate. I am using SesssionFactory to get the session of the transaction and one way which I found after searching is used for setting few fields using set parameter i.e
Query query = getCurrentSession().createSQLQuery(
"UPDATE table_name set field1=:f1 where ID=:id");
query.setParameter("f1", f1);
query.setParameter("id", id);
but I want to update the whole row. I have already set the values in the entity class but is there a way to pass the values of the whole entity class to the database based on the id the id is the primary key for the table which I want to update.
you already have all data present in the hibernate entity object? Then just call the session directly:
getCurrentSession().save(myEntity);
to create a new object, or
getCurrentSession().update(myEntity);
to update an existing row.
If not sure, you can use:
getCurrentSession().saveOrUpdate(myEntity);
Take a look at Session#update (or saveOrUpdate). This will allow you to persist a complete, mapped, object to the database.
To be as OO as you can, you can get entity by session.get(entityClass, id);
And then after modifying object by setters/getters, you can save it back to the DB using update method :session.update(entity);
I am using #Version annotation to provide version control in hibernate. My question is regarding the proper mapping of data from DTO to Entity.
What I feel is the right way is as follows but I want to know if there is a better way or this is how everybody does it.
call comes to my service
i load the entity to be updated (assume AddressEntity with version = 1)
i map the AddressDTO values to AE, including sub-collections if any
after all mapped, i detach the entity AE (only to be detached after Lazy sub collections mapped too)
now i map the version from DTO to AE (as hibernate does not allow to update version in managed entity)
now i call merge to update this detached AE entity
1) Is this the right way semantics and logic wise ?
2) (bit out of context) is there an overhead for hibernate to merge an object already in context and managed ie can i use merge for all updates safely irrespective or managed/unmanaged or Only merge+flush for unmanaged and flush for managed after updating some properties ?
Let me try to answer your question stepwise:
Suppose you have loaded an AddressEntity (having id=123 and version=1). Set the property values from AddressEntity to AddreeDto including the id and version values. Send the AddressDto to UI.
Changes made to AddresDto. Call has come to your service. Create an instance of AddressEntity and set the values from AddressDto including the id and version values. This new AddressEntity has now turned into a detached instance, as it has a persistent identity, but its state is not guaranteed to be synchronized with database state.
Hibernate lets you reuse this Addressentity instance in a new transaction by reassociating it with a new persistence manager.This detached instance can be reassociated with a new Session by calling update(). You don't need to load the entity again.The update() method forces an update to the persistent state of the object in the database.
Set the addressEntity properties:
addressEntity.setId(dto.getId());
addressEntity.setVersion(dto.getVersion());
Attach addressEntity to a new session:
Transaction tx = sessionTwo.beginTransaction();
sessionTwo.update(addressEntity);
tx.commit();
sessionTwo.close();
The session.update will execute an SQL similar to this:
update ADDRESS_ENTITY set ... , VERSION=2
where ID=123 and VERSION=1
If another application transaction would have updated the same ADDRESS_ENTITY since it was loaded, the VERSION column would not contain the value 1, and the row would not be updated, and you will receive a stale object state exception. You can catch the exception and inform the User about the stale data.
after all mapped, i detach the entity AE (only to be detached after Lazy sub collections mapped too)
Assuming you are performing this in a single transaction. Any persistent object that you have retrieved from DB is associated with the current session and transaction context. If it is modified in the same transaction, its state will be automatically synchronized with the DB. This mechanism is called automatic dirty checking. It means Hibernate will track and save the changes made to an object inside a session.
Transaction tx = session.beginTransaction();
int addressEntityID = 1234;
AddressEntity addressEntity = (AddressEntity) session.get(AddressEntity.class, new Long(addressEntityID));
// set the values from AddressDTO to AddressEntity
tx.commit();
session.close();
The object is retrieved from DB, it is modified and the modifications are propagated to DB on transaction commit.You don't need to detach and reattach an entity to perform an update.
now i map the version from DTO to AE (as hibernate does not allow to update version in managed entity)
The managed versioning is used to implement optimistic locking and the versioning of the entities is managed by Hibernate. The version number is just a counter value, it does not have any useful information that you should keep in your DTO.You don’t need to set the value of the version yourself. Hibernate will initialize the value when you first save an AddressEntity, and increment or reset it whenever the object is modified.
If another application transaction(T2) updates the persistent instance the same item since it was read by the current application transaction(T1), the T2 transaction will change the version value for this entity. Now when T1 tries to make an update, Hibernate will throw a stale object state exception, as the version of the entity has been changed. You can catch the exception and inform the User about the stale data. In particular, versioning prevents the lost update problem. You don't need to map the version from DTO to AE or from AE to DTO, as it does not have any meaningful information which can be used in contexts other than to implement optimistic locking.
I have a couple of objects that are mapped to tables in a database using Hibernate, BatchTransaction and Transaction. BatchTransaction's table (batch_transactions) has a foreign key reference to transactions, named transaction_id.
In the past I have used a batch runner that used internal calls to run the batch transactions and complete the reference from BatchTransaction to Transaction once the transaction is complete. After a Transaction has been inserted, I just call batchTransaction.setTransaction(txn), so I have a #ManyToOne mapping from BatchTransaction to Transaction.
I am changing the batch runner so that it executes its transactions through a Web service. The ID of the newly inserted Transaction will be returned by the service and I'll want to update transaction_id in BatchTransaction directly (rather than using the setter for the Transaction field on BatchTransaction, which would require me to load the newly inserted item unnecessarily).
It seems like the most logical way to do it is to use SQL rather than Hibernate, but I was wondering if there's a more elegant approach. Any ideas?
Here's the basic mapping.
BatchQuery.java
#Entity
#Table(name = "batch_queries")
public class BatchQuery
{
#ManyToOne
#JoinColumn(name = "query_id")
public Query getQuery()
{
return mQuery;
}
}
Query.java
#Entity
#Table(name = "queries")
public class Query
{
}
The idea is to update the query_id column in batch_queries without setting the "query" property on a BatchQuery object.
Using a direct SQL update, or an HQL update, is certainly feasible.
Not seeing the full problem, it looks to me like you might be making a modification to your domain that's worth documenting in your domain. You may be moving to having a BatchTransaction that has as a member just the TransactionId and not the full transaction.
If in other activities, the BatchTransaction will still be needing to hydrate that Transaction, I'd consider adding a separate mapping for the TransactionId, and having that be the managing mapping (make the Transaction association update and insert false).
If BatchTransaction will no longer be concerned with the full Transaction, just remove that association after adding a the TransactionId field.
As you have writeen, we can use SQL to achieve solution for above problem. But i will suggest not to update the primary keys via SQL.
Now, as you are changing the key, which means you are creating alltogether a new object, for this, you can first delete the existing object, with the previous key, and then try to insert a new object with the updated key(in your case transaction_id)