JPA's bulk update works when changing the object? - java

Normally, if I change an object mapped with #Entity, it will be persisted at the end of transactional methods, even if I don't call any save methods.
I'm doing a bulk update for performance reasons using the EntityManager#CriteriaUpdate from JPA, but I need to trigger some events in the setters of the objects, so I set them, but don't call the save method.
What I want to know is if the bulk update is useful if I change the object, or each object will be persisted, even though the bulk update is executed?
PgtoDAO:
public void bulkUpdateStatus(List<Long> pgtos, Long newStatusId) {
CriteriaBuilder cb = this.manager.getCriteriaBuilder();
CriteriaUpdate<Pgto> update = cb.createCriteriaUpdate(Pgto.class);
Root e = update.from(Pgto.class);
update.set("status", newStatusId);
update.where(e.get("id").in(pgtos));
this.manager.createQuery(update).executeUpdate();
}
PgtoService:
#Transactional(readOnly = false)
public int changePgtosStatus(List<Pgto> pgtos, StatusEnum newStatus){
...
List<Long> pgtoIds = new ArrayList<Pgto>();
for(Pgto pgto : pgtos){
// Hibernate will persist each object here, individually?
pgto.setStatus(newStatus.id());
pgtoIds.add(pgto.getId());
}
pgtoDao.bulkUpdateStatus(pgtoIds, newStatus.id());
// I tried setting a different status here to the objects, but it did not persisted
}
Perhaps I should end the connection after the bulk update?

Criteria query and changed entities are treated separately. Criteria query is just executed, and managed (loaded via entity manager) changed entities are synchronized with database on transaction commit.
If you like to prevent this, you will have to detach those entities from entity manager. Then changes will be not propagated to database anymore

Related

Cannot save after deleting entity in Hibernate

I want to delete all records with some lineId to save another records with the same lineId(as refresh) but after deleting I can't save anything. There isn't any error, but I don't have my record in database.
When I don't have ma deleting code everything saves correctly.
public void deleteAndSaveEntities(List<Entity> entities, Long lineId){
deleteEntities(lineId);
saveEntities(entities);
}
private void deleteEntities(Long lineId) {
List<Entity> entitiesToDelete = entityRepository.findAllByLineId(lineId);
entityRepository.deleteAll(entitiesToDelete);
}
private void saveEntities(List<Entity> entities) {
entityRepository.saveAll(entities);
}
Actually you want to update the entries that has the lineId. Try it as:
First fetch by find..().
Make related changes on that entries
Then save them.
As thomas mentioned, hibernate reorders the queries within the transaction for performance reasons and executes the delete after the update.
I would commit the transaction between these two operations.
Add a #Transactional over deleteEntities and saveEntities.
But be aware that #Transactional does not work when invoked with in the same object.
You must inject the Service into itself and then call the methods on the self reference

JPA flushing to Database before #PreUpdate is called

I am trying to capture the entity data in the database before the save is executed, for the purpose of creating a shadow copy.
I have implemented the following EntityListener in my Spring application:
public class CmsListener {
public CmsListener() {
}
#PreUpdate
private void createShadow(CmsModel entity) {
EntityManager em = BeanUtility.getBean(EntityManager.class);
CmsModel p = em.find(entity.getClass(), entity.getId());
System.out.println(entity);
}
}
The entity does indeed contain the entity object that is to be saved, and then I inject the EntityManager using another tool, which works fine - but for some reason, the entity has already been saved to the database. The output of CmsModel p = em.find(...) results in identical data which is in entity.
Why is JPA/hibernate persisting the changes before #PreUpdate is called? How can I prevent that?
I would assume this is because em.find doesn't actually query the database but fetches the object from cache, so it actually fetches the same object entity refers to (with changes already applied).
You could check your database log for the query that fetches the data for entity.id to verify this is indeed the case or you could add a breakpoint in createShadow() and have a look at the database entry for entity at the time the function is called to see for yourself if the changes are already applied to the database at that time.
To actually solve your problem and get your shadow copy you could fetch the object directly from database via native query.
Here is an untested example of what this could look like:
public CmsModel fetchCmsModelDirectly(){
Query q = em.createNativeQuery("SELECT cm.id,cm.value_a,cm.value_b FROM CmsModel cm", CmsModel.class);
try{
return q.getSingleResult();
}catch(NoResultException e){
return null;
}
}
Do you check if the entity is really updated to database? My suspect is that the change is only updated to the persistence context (cache). And when the entity is query back at the listener, the one from the cache is returned. So they are identical.
This is the default behavior of most of the ORM (JPA in this case) to speed up the data lookup. The ORM framework will take care of the synchronizing between the persistence context and the database. Usually when the transaction is committed.

update and return data in spring data JPA

For concurrency purpose, I have got a requirement to update the state of a column of the database to USED while selecting from AVAILABLE pool.
I was thinking to try #Modifying, and #Query(query to update the state based on the where clause)
It is all fine, but this is an update query and so it doesn't return the updated data.
So, is it possible in spring data, to update and return a row, so that whoever read the row first can use it exclusively.
My update query is something like UPDATE MyObject o SET o.state = 'USED' WHERE o.id = (select min(id) from MyObject a where a.state='AVAILABLE'), so basically the lowest available id will be marked used. There is a option of locking, but these requires exceptional handling and if exception occur for another thread, then try again, which is not approved in my scenario
You need to explicitly declare a transaction to avoid other transactions being able to read the values involved until it's commited. The level with best performance allowing it is READ_COMMITED, which doesn't allow dirty reads from other transactions (suits your case). So the code will look like this:
Repo:
#Repository
public interface MyObjectRepository extends JpaRepository<MyObject, Long> {
#Modifying
#Query("UPDATE MyObject o SET o.state = 'USED' WHERE o.id = :id")
void lockObject(#Param("id") long id);
#Query("select min(id) from MyObject a where a.state='AVAILABLE'")
Integer minId();
}
Service:
#Transactional(isolation=Isolation.READ_COMMITTED)
public MyObject findFirstAvailable(){
Integer minId;
if ((minId = repo.minId()) != null){
repo.lockObject(minId);
return repo.findOne(minId);
}
return null;
}
I suggest to use multiple transactions plus Optimistic Locking.
Make sure your entity has an attribute annotated with #Version.
In the first transaction load the entity, mark it as USED, close the transaction.
This will flush and commit the changes and make sure nobody else touched the entity in the mean time.
In the second transaction you can no do whatever you want to do with the entity.
For these small transactions I find it clumsy to move them to separate methods so I can use #Transactional. I therefore use the TransactionTemplate instead.

DTO entity mapping with hibernate #Version control

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.

How do you update a foreign key value directly via Hibernate?

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)

Categories

Resources