Been able to successfully use wnwrap session to reduce SQL count to UPDATE only from SELECT + UPDATE for Entity (annotated with #DynamicUpdate) but DynamicUpdate is not working in that case.
doInJPA(entityManager -> {
Session session = entityManager.unwrap( Session.class );
for ( Post post: posts ) {
session.update( post );
}
});
Found #DynamicUpdate only works when using entityManager.merge pattern while doing update but it leads to 2 SQL queries while detached state SELECT + UPDATE.
doInJPA(entityManager -> {
for ( Post post: posts ) {
entityManager.merge( post );
}
});
Can session unwrap pattern be made to do use of SQL UPDATE query generated by entityManager.merge , so that don't have to re-implement DynamicUpdate ?
Note -
Entity has #Id key on String dataType which is UUID.toString field and is not a generated field
tried implementing Persistable and Transient isNew and getId without success (i.e unable to reduce SELECT + UPDATE in case of entityManager.merge pattern to UPDATE only)
Been able to do insert successfully using single INSERT using entityManager.createNativeQuery.executeUpdate pattern, just the update is firing double queries with merge pattern. Session unwrap and update reduces it to 1 UPDATE but DynamicUpdate does not work
Tried Transactional at higher scope too so that GET + UPDATE are in same transaction without success
Dynamic update will only work on managed entities(which requires an entity to be SELECTed at least once) because Hibernate relies on comparing state against snapshots which are created when doing a SELECT.
If you want real dynamic updates, I can recommend you take a look at Blaze-Persistence Entity-Views which supports that: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#updatable-entity-views
Related
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.
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
This question already has answers here:
Spring Data JPA Update #Query not updating?
(5 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
Let's suppose to have this situation:
We have Spring Data configured in the standard way, there is a Respository object, an Entity object and all works well.
Now for some complex motivations I have to use EntityManager (or JdbcTemplate, whatever is at a lower level than Spring Data) directly to update the table associated to my Entity, with a native SQL query. So, I'm not using Entity object, but simply doing a database update manually on the table I use as entity (it's more correct to say the table from which I get values, see next rows).
The reason is that I had to bind my spring-data Entity to a MySQL view that makes UNION of multiple tables, not directly to the table I need to update.
What happens is:
In a functional test, I call the "manual" update method (on table from which the MySQL view is created) as previously described (through entity-manager) and if I make a simple Respository.findOne(objectId), I get the old object (not updated one). I have to call Entitymanager.refresh(object) to get the updated object.
Why?
Is there a way to "synchronize" (out of the box) objects (or force some refresh) in spring-data? Or am I asking for a miracle?
I'm not ironical, but maybe I'm not so expert, maybe (or probably) is my ignorance. If so please explain me why and (if you want) share some advanced knowledge about this amazing framework.
If I make a simple Respository.findOne(objectId) I get old object (not
updated one). I've to call Entitymanager.refresh(object) to get
updated object.
Why?
The first-level cache is active for the duration of a session. Any object entity previously retrieved in the context of a session will be retrieved from the first-level cache unless there is reason to go back to the database.
Is there a reason to go back to the database after your SQL update? Well, as the book Pro JPA 2 notes (p199) regarding bulk update statements (either via JPQL or SQL):
The first issue for developers to consider when using these [bulk update] statements
is that the persistence context is not updated to reflect the results
of the operation. Bulk operations are issued as SQL against the
database, bypassing the in-memory structures of the persistence
context.
which is what you are seeing. That is why you need to call refresh to force the entity to be reloaded from the database as the persistence context is not aware of any potential modifications.
The book also notes the following about using Native SQL statements (rather than JPQL bulk update):
■ CAUTION Native SQL update and delete operations should not be
executed on tables mapped by an entity. The JP QL operations tell the
provider what cached entity state must be invalidated in order to
remain consistent with the database. Native SQL operations bypass such
checks and can quickly lead to situations where the inmemory cache is
out of date with respect to the database.
Essentially then, should you have a 2nd level cache configured then updating any entity currently in the cache via a native SQL statement is likely to result in stale data in the cache.
In Spring Boot JpaRepository:
If our modifying query changes entities contained in the persistence context, then this context becomes outdated.
In order to fetch the entities from the database with latest record.
Use #Modifying(clearAutomatically = true)
#Modifying annotation has clearAutomatically attribute which defines whether it should clear the underlying persistence context after executing the modifying query.
Example:
#Modifying(clearAutomatically = true)
#Query("UPDATE NetworkEntity n SET n.network_status = :network_status WHERE n.network_id = :network_id")
int expireNetwork(#Param("network_id") Integer network_id, #Param("network_status") String network_status);
Based on the way you described your usage, fetching from the repo should retrieve the updated object without the need to refresh the object as long as the method which used the entity manager to merge has #transactional
here's a sample test
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ApplicationConfig.class)
#EnableJpaRepositories(basePackages = "com.foo")
public class SampleSegmentTest {
#Resource
SampleJpaRepository segmentJpaRepository;
#PersistenceContext
private EntityManager entityManager;
#Transactional
#Test
public void test() {
Segment segment = new Segment();
ReflectionTestUtils.setField(segment, "value", "foo");
ReflectionTestUtils.setField(segment, "description", "bar");
segmentJpaRepository.save(segment);
assertNotNull(segment.getId());
assertEquals("foo", segment.getValue());
assertEquals("bar",segment.getDescription());
ReflectionTestUtils.setField(segment, "value", "foo2");
entityManager.merge(segment);
Segment updatedSegment = segmentJpaRepository.findOne(segment.getId());
assertEquals("foo2", updatedSegment.getValue());
}
}
I am using MySQL + spring + hibernate.
When I execute the following code it creates a new row:
sessionFactory.getCurrentSession()
.save(configTable);
However this code below updates
sessionFactory.getCurrentSession()
.update(configTable);
I am not sure why the above code creates a new row in tables it should update in both cases to my understanding,
Any idea what could I be missing? Or what info will you need to help me track the problem...
You need to use saveOrUpdate() session method as:
sessionFactory.getCurrentSession().saveOrUpdate(configTable);
When update pass Entity primary key for update.
Make sure that your Entity have this annotation for save and update.
#org.hibernate.annotations.Entity(dynamicInsert = true, dynamicUpdate = true)
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)