Spring Hibernate transaction - read only vs read write - java

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.

Related

Can I set readOnly false when I use findById with JPA

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);

Spring JPA always caches data [duplicate]

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());
}
}

Oracle db lock when using PROPAGATION_REQUIRED_NEW with two separate spring bean

I have 2 spring bean (Bean A and B) which access the db. Both bean are in PROPAGATION_REQUIRED_NEW and the isolation level is READ COMMITED on a ORACLE db.
Bean call A select (via Hibernate) on the same table twice with different criteria.
Then the bean B is called to for each of the row oth the table to do an update on them.
Weirdly everything work fine if Bean A does only one call (select) to the db.
I've check with the query from dba.stackexchange and it's only when the second "select" in Bean A that have a db lock.
NOTE: Why not using PROPAGATION_REQUIRED or PROPAGATION_NESTED for bean B ?
We don't use PROPAGATION_REQUIRED Because we don’t won’t to roll back everything if one of the row fail and has for PROPAGATION NESTED the version of our jdbc driver we are using doesn’t support it.
My current workaround is to have a bean that handle the select seperatly and the main bean doesn't do any transaction just call the two other bean. I just find it really strange that doing "select * from X" cause a lock on the table when we provide locking NONE and we are in READ_COMMITED. Am I missing something here?
The first query:
select
temptable0_.ID as ID1_43_,
temptable0_.IDSEND as IDS2_43_,
temptable0_.DATERECEI as DAT3_43_,
temptable0_.DATEPROC as DAT4_43_,
temptable0_.STATUT as STA5_43_,
temptable0_.ERROR_TYPE as ERR6_43_,
temptable0_.CODE as COD7_43_,
temptable0_.DESCRIPTION as DES8_43_,
temptable0_.NOTIF as NOT9_43_,
temptable0_.ACTION as ACT10_43_,
temptable0_.IDCLIENT as IDC11_43_,
temptable0_.PARAM as PAR12_43_
from
TABLE_TMP temptable0_
where
temptable0_.STATUT in (
'NEW'
)
The second query (same transaction (BEAN A))
select
temptable0_.ID as ID1_43_,
temptable0_.IDSEND as IDS2_43_,
temptable0_.DATERECEI as DAT3_43_,
temptable0_.DATEPROC as DAT4_43_,
temptable0_.STATUT as STA5_43_,
temptable0_.ERROR_TYPE as ERR6_43_,
temptable0_.CODE as COD7_43_,
temptable0_.DESCRIPTION as DES8_43_,
temptable0_.NOTIF as NOT9_43_,
temptable0_.ACTION as ACT10_43_,
temptable0_.IDCLIENT as IDC11_43_,
temptable0_.PARAM as PAR12_43_
from
TABLE_TMP temptable0_
where
temptable0_.STATUT in (
'ERROR'
)
And the Update statement (done by BEAN B)
update
TABLE_TMP
set
IDSEND=?,
DATERECEI=?,
DATEPROC=?,
STATUT=?,
ERROR_TYPE=?,
CODE=?,
DESCRIPTION=?,
NOTIF=?,
ACTION=?,
IDCLIENT=?,
PARAM=?
where
ID=?
Where IDSEND is incremented, DATEPROC is set and STATUT and ERROR_TYPE are set if there a error or a success.
Oracle doesn't support READ UNCOMMITED. You are using the default mode with is READ COMMITTED.
If you try to select a records with a pending transaction change from other sessionon it, you'll have to wait until the update is commited.

Hibernate/Spring HibernateTemplate.findByCriteria(Deatched Criteria dc) executes a sql update on view

I am trying to search a view based on given criteria. This view has a few fields for multiple different entities in my application that a user may want to search for.
When I enter the name of an entity I want to search for, I add a restriction for the name field to the detached criteria before calling .findByCriteria(). This causes .findByCriteria() to retrieve a list of results with the name I am looking for.
Also, when I look through my log, I can see hibernate calling a select statment.
I have now added another entity to my view, with a few searchable fields. When I try to search for a field related to this new entity, I get an exception in my log.
When I look through my log with the exception, I can see hibernate calling a select statment with an update statement right after the select (I am not trying to update a record, just retrieve it in a list).
So why is hibernate calling an update when I am calling .findByCriteria() for my new entity?
org.hibernate.exception.SQLGrammarException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:90)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
SQL that is executed:
Hibernate:
select
*
from
( select
this_.SEARCH_ID as SEARCH1_35_0_,
this_.ST_NM as ST24_35_0_
from
SEARCH_RESULT this_
where
this_.LOAN_TYPE=? )
where
rownum <= ?
DEBUG 2012-03-21 11:37:19,332 142195 (http-8181-3:org.springframework.orm.hibernate3.HibernateTemplate):
[org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:389)]
Eagerly flushing Hibernate session
DEBUG 2012-03-21 11:37:19,384 142247 (http-8181-3:org.hibernate.SQL):
[org.hibernate.jdbc.util.SQLStatementLogger.logStatement(SQLStatementLogger.java:111)]
update
SEARCH_RESULT
set
ADDR_LINE1=?,
ASSGND_REGION=?,
BASE_DEAL_ID=?,
ST_NM=?
where
SEARCH_ID=?
There is probably an update happening because Hibernate is set up to do an autoflush before executing the queries, so if the persistence context thinks it has dirty data, it will try to update it. Without seeing the code I can't be sure, but I'd guess that even though search_result is a view, your corresponding Java object is annotated on the getters and the object has matching setters. Hibernate doesn't make a distinction between tables and views, and if you call a setter, Hibernate will assume that it has data changes to update.
You can tweak how you build your Java objects for views by adding the #Immutable annotation (or hibernate.#Entity(mutable = false) depending on which version you're using. This should be enough to indicate to Hibernate to not flush changes. You can also annotate the fields directly and get rid of your setters so that consumers of the SearchResult object know that it's read only.

What is the best way to set a MySQL user variable in a Java, Spring MVC, Hibernate with Annotations web application?

I need to be able to set a MySQL user variable that is used in a trigger in a Spring MVC Hibernate web ap. This user variable is used in MySQL triggers on the tables that are being manipulated by Hibernate. I need this user variable to be correctly set during all of Hibernate's write accesses to the database.
Unfortunately HQL does not have a technique for setting MySQL user variables and the method I have found for directly executing MySQL does not seem to work with the transaction. Since the user variable's life span is that of the database connection I need the MySQL to execute using the same connection that they HQL will be executed with. However my transactions seem to run the HQL after the native MySQL has been executed and thus the expected user variable is not present for the MySQL trigger.
To execute native MySQL queries I have been using:
HibernateEntityManager em=(HibernateEntityManager) getEntityManager();
Connection connection=em.getSession().connection();
Statement s = connection.createStatement();
s.executeUpdate("SET #current_user_id="+userId);
When the Spring MVC commits my transaction it runs the HQL queries and then throws an exception because the #current_user_id is causing the MySQL trigger to break. I verified this by testing without the triggers present.
I found this SO question that is very similar to mine: How to use Mysql variables with Hibernate?
So I followed the suggestion and used a stored procedure and the executeNativeQuery method on the entity manager to call it.
Here is my stored procedure code:
DROP PROCEDURE IF EXISTS set_current_user
CREATE PROCEDURE set_current_user(IN user_id BIGINT)
BEGIN
SET #current_user_id = user_id
END
And I call it with:
Query q = em.createNativeQuery("CALL set_current_user("+userId+")");
q.executeUpdate();

Categories