I have a problem with modifying data while setting database replication
Before DB replication, I get data that I want to modify using repository.findById() and then I modified the data.
But I realized that repository.findById() is set #Transactional(readOnly = true) in SimpleJpaRepository.java so that I can't modify the data even if I use #Transactional in my Service or repository.save()
Is there any other way to force findById() to connect by a write connection except by making custom method in the repository for findById?
+++)
I solved my problem! I wanted to use dirty checking for modifying datas and I realized that my setting about EntityManagerFactory was something wrong and I fixed it with a doc in spring.io (https://docs.spring.io/spring-data/jpa/docs/current-SNAPSHOT/reference/html/#reference) I tried many times with many other developers posting but they didn't work for me, but it did. Thank you for giving me answers ðŸ˜
Refer this,
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions
Section 5.7.1. Transactional query methods to be more specific
It says that #Modifying annotation can be used to override transaction configuration
Typically you will use the readOnly flag set to true as most of the query methods will be reading ones. In contrast to that deleteInactiveUsers() makes use of the #Modifying annotation and overrides the transaction configuration. Thus the method will be executed with readOnly flag set to false.
You don't need to change that flag!
Find the data
Edit data
Call JPA repository.save(newData) method with #Modifying to save the edited data in the DB
I.E.
#Transactional
#Modifying
#Query(value = "UPDATE user SET points = points + ?1
WHERE id = ?2", nativeQuery = true)
int increasePoints(int points, Long id);
Related
I am new in Spring Data JPA and will use #Modifying(clearAutomatically = true, flushAutomatically = true) annotation for an update method that updated a name field of Product entity. I have 2 questions that made me confused:
1. Do we need to use #Transactional with #Modifying annotation?
2. What does #Modifying exactly do? I read several pages e.g. Spring Data JPA #Modifying Annotation, and now too confused.
Second question first: the Modifying annotation lets you execute DML (inserts, updates, deletes) and DDL using JPA Query annotations where you put the DML or DDL where the query would normally go.
To answer the first question, i would expect to use this in a service layer annotated with #Transactional instead of putting the annotation on the Repository, because these operations seem likely to occur as part of a bigger operation with other business logic. But if that is not the case then the annotation could go on the Repository.
I am facing a weird problem with Spring Boot(2.3.7) + PostgreSQL v12 (row level security) + Hibernate (5.x).
Here are the steps that I am executing
A procedure accepts an input variable and creates temporary table. The variable is then inserted in temporary table.
Spring Advice which executes for all #Service annotation and invokes a procedure with a variable (call it custom_id).
#Transactional attribute is specified on all #Service classes.
PostgreSQL row level security has been enabled on the tables being queried and updated.
Row level security applies filter based on the variable stored (custom_id value) in temporary table.
All update, select, insert operations are executed using custom implementation of JpaRepository (interface based)
This works fine as long as there are only select operation performed on the database. But starts to fail with code having a combination of select and updates. The code simply fails with a message as it is not able to locate the temporary table.
I enabled trace for Spring transaction and found that there are few statements like
No need to create transaction for XXX
While code that performs update operation has statements like
Getting transaction for XXX
After searching for a while, I realised that SimpleJpaRepository has #Transaction with readonly flag set to true. This results in SELECT operation getting executing in transaction less mode.
Procedure
create or replace procedure proc_context(dummy_id uuid) AS $context_var$
declare
begin
create temp table if not exists context_metadata
(
dummy_id uuid
)
on commit drop;
insert into context_metadata values(dummy_id);
end;
$context_var$ LANGUAGE plpgsql;
ERROR
Following error is logged in console
ERROR: relation "context_metadata" does not exist
What I tried
Tried implementing custom transaction manager and explicitly invoking the procedure to set the temporary variable value (Didn't work). Refer below
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
super.prepareSynchronization(status, definition);
if (status.isNewTransaction() || status.isReadOnly() || status.isNewSynchronization()) {
UUID someID = ....;
Query query = entityManager.createNativeQuery("CALL proc_context(?);");
query.setParameter(1, someID);
query.executeUpdate();
}
}
Tried setting #Transactional notation with readonly set to false on all repositories.
What I am looking for?
Unfortunately due to this behaviour, the row-level security implementation is not working in my code. Is there any way to disable read-only transactions using a global property OR provide me with any hint to overcome this problem?
Finally, I could figure out after 2 days of battle. The problem was multi-faceted.
I noticed hibernate.transaction.flush_before_completion property set to true in application.properties file. I had to remove that property.
Developer had written a very messy code to update the entity attributes (Was performing select, then creating new instance, populating attributes and then calling save method). All this ruckus to update one single attribute.
Tested the code and everything worked fine.
I'm experiencing what seems to be a record lock between sequential (not concurrent) database operations, which I can't explain.
Situation
Method saveRegistrationToken is called from a REST controller. I test the calls to the method via Postman (HTTP client); the method is not called anywhere else, this is the only operation executed.
The method execution is supposed to behave as follows:
Take a String (registration token) and a user ID (also a string) as input
It should first update a USERS table, setting the value of the REGISTRATION_TOKEN column to null for every row where the column has the same value as the input registration token
It should then update the USERS table for the row with the specified user ID, setting the value of the REGISTRATION_TOKEN column to the input registration token.
Problem
Every first execution of the method will behave as expected: sets the value of the DB column REGISTRATION_TOKEN (table USER) to null wherever it is the specified value, and then sets the registration token to the input value for the row with the input user ID. As such, the value of the registration token for the row in question is the input value at the end of the execution of the method.
Every second execution will correctly do the first step ("void" the registration token wherever it exists) but does not update the value for the row with the specified user ID. As such, the value of the registration token for the row in question is null at the end of the execution of the method.
DefaultUserService.java
#Override
public void saveRegistrationToken(String userId, String registrationToken) {
usersRepository.voidRegistrationToken(registrationToken);
User u = usersRepository.findById(userId).get();
u.setRegistrationToken(registrationToken);
usersRepository.save(u);
}
UsersRepository.java
#Repository
public interface UsersRepository extends JpaRepository<User, String> {
#Modifying
#Transactional
#Query(value = "UPDATE USERS " +
"SET registration_token = null " +
"WHERE registration_token = :registration_token",
nativeQuery = true)
void voidRegistrationToken(#Param("registration_token") String registrationToken);
}
User.java
#Entity(name = "users")
#AllArgsConstructor //lombok
#Data
#NoArgsConstructor
#ToString
#EqualsAndHashCode
public class User {
#Id
private String id;
private String registrationToken;
private String email;
}
What I've tried
I initially thought it would be a flush problem: that once the registration token had been set to null everywhere, the transaction would not be flushed until after the registration token had been set again for the user ID, leading to conflicting behaviour between both DB operations. I disproved that explicitly calling usersRepository.flush(); after the first operation, and observing the same behaviour.
I tried different propagation and isolation levels on the repository operation: #Transactional(propagation = Propagation.SUPPORTS, isolation = Isolation.READ_UNCOMMITTED), which didn't help.
I tried explicitly setting the flush mode on the repository operation: #QueryHints(value = { #QueryHint(name = org.hibernate.annotations.QueryHints.FLUSH_MODE, value = "ALWAYS") }) , which didn't change anything.
It now seems to me that the first operation "locks" the updated record, which prevents the second operation from updating it, but I don't understand how.
Explicitly specifying auto-commit true: spring.datasource.auto-commit=true
Dependencies: compile("org.springframework.boot:spring-boot-starter-data-jpa") effectively version 2.1.1.RELEASE
Any ideas, explanations, links to docs would be very much appreciated - I've tried everything I can think of.
Many thanks, Chris
UPDATE:
Another reason I think it's some kind of flush problem.
I updated this method as follows:
#Override
public void saveRegistrationToken(String userId, String registrationToken) {
usersRepository.voidRegistrationToken(registrationToken);
String check = usersRepository.findById(userId).get().getRegistrationToken();
/* breakpoint on the following line */
User u = usersRepository.findById(userId).get();
u.setRegistrationToken(registrationToken);
usersRepository.save(u);
}
When stopping at the breakpoint where indicated:
Every first ("normal") execution, the value of the check variable is null
Every second execution, its value is the same as the input registration token
Although I always prefer to mark the service method as #Transactional as a whole, reviewing your code, I think you defined the appropriate transaction demarcation in your methods, by explicitly define the #Transactional annotation in voidRegistrationToken, and by using the methods provided by JpaRepository, implicitly annotated in such a way.
In any case, as you indicated, as a result of performing the different operations over the User who will be assigned the registration token, you are obtaining inconsistent values.
It is a clear indication that the information of the affected User entity maintained by the EntityManager in the persistence context is being polluted somewhere across the different methods invocation.
I honestly cannot give you the exact reason about this behavior.
It may have to do with the moment in which the changes are flushed to the database, to entirely discretion of the EntityManager, but you already tried to flush the different changes manually and your transactions seems appropriate and, as a consequence, it will probably not be the cause of the problem.
Maybe it have to do with a second level cache as #Guillaume suggested, maybe with the way the #Modifying operation is implemented by Spring Data.
One think you can try is to instruct your #Modifying annotation to clear the persistence context once the operation is completed:
#Modifying(clearAutomatically = true)
This will provide you a clean state to perform the registration token update.
Please, see the relevant docs.
Please, be aware that the possible implications of the use of this solution.
The use of flush and clear in EntityManager is an anti-pattern, something that should be avoided if you can by using the appropriate transaction demarcations and component architecture.
The call of clear leads to all objects being decoupled from the EntityManager. Be aware that, depending of the use case, even modified object data will not be saved to the database - this is the main difference with flush that always will preserve the modifications performed to the entities managed in the persistence context before detach them.
In any case, due to the way your transactions are defined, probably in your use case it will work properly.
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 have a simple update query just to check if update query works:
this.openDBTransaction();
Query updateQuery = session.createQuery(
"UPDATE User AS usr SET usr.failedLogins=666 WHERE usr.id='USER_3000'"
);
int result = updateQuery.executeUpdate();
this.closeDBTransaction();
but somehow DB is not update with desired value. result came as 1 so something took place but for sure not update query.
Any clue what is going on?
You should use #Transactional annotation so that the compiler knows that the transaction is manipulating the database, thus permits to perform Data Manipulation queries or it will simply execute it as a Data Definition Language query.
Look at the code snippet below, for example,
#Transactional
public Employee editEmployee(Employee employee) { //employee is the data you got through post
return entityManager.merge(e1);
}
Also, the best practice is to always implement Data Access Object Interface and its implementation and define your queries in the implementation.
I hope this helps.