Transactional Atom in Spring

What does transaction atomicity mean in SQL / Spring and what does it mean?

I think of the following case. Correct me if I am wrong:

This code is incorrect:

@Transactional public void voteUp(long fooId) { Foo foo = fooMapper.select(fooId); // SELECT * FROM foo WHERE fooId == #{fooId} foo.setVotes(foo.getVotes() + 1); fooMapper.update(foo); // UPDATE foo SET votes = #{votes} (...) WHERE fooId == #{fooId} } 

Despite the fact that a transactional transaction does not mean that the value of β€œvotes” will always increase by one, if voteUp is invoked simultaneously on many machines / in many threads? If that were the case, would it mean that only one transaction can be executed at a time, which will lead to a decrease in efficiency (especially if the voteUp code does more things in the transaction)?

The only right way to do this is (?):

 /* not necessarily */ @Transactional public void voteUp(long fooId) { fooMapper.voteUp(fooId); // UPDATE foo SET votes = votes + 1 WHERE fooId == #{fooId} } 

In the examples, I used myBatis to connect to the database, but I think the question remains the same if I used the hibernate or plain SQL statements.

+5
source share
4 answers

The isolation level determines how reliable the viewing of data in a transaction will be. The most reliable isolation level is serializable (which affects database performance), but the usual default value is read-commit :

In this isolation level, the concurrency-based DBMS management implementation retains write locks (obtained from the selected data) until the end of the transaction, but read locks are released as soon as the SELECT operation is performed (therefore, a non-recurring read phenomenon can occur at this isolation level, as described below ) As at the previous level, range locks are not controlled.

Putting it on simpler words, the read is recorded - this is the isolation level, which ensures that the reading of the data is recorded at the time of reading. This simply limits the reader's perception of any intermediate, unfixed, "dirty" reading. He does not make any promises that if the transaction reshoots the read, it will find the same data; data can be changed after reading.

In the first example, between a selection and an update, some other process can change the counter value: the selection occurs, then another process changes the counter value, then the update acts on the changed line.

If you change the isolation level to read with the ability to read it again, make sure that the increment in the first example works correctly. Of course, the second example is correct, as it stands and is the best solution.

+5
source

According to the documentation , when you comment on a method with @Transactional, Spring creates a proxy with the same interfaces as your annotated class. And when you call the methods of your object, all calls go through the proxy object. A proxy object wraps the transactional methods of your class in a try catch construct. Source code of the object:

 @Transactional public void voteUp(long fooId) { Foo foo = fooMapper.select(fooId); // SELECT * FROM foo WHERE fooId == #{fooId} foo.setVotes(foo.getVotes() + 1); fooMapper.update(foo); // UPDATE foo SET votes = #{votes} (...) WHERE fooId == #{fooId} } 

The proxy object will look something like this:

 //It all approximately just to show you a way how Spring does it. public void voteUp(long fooId) { EntityTransaction tx = em.getTransaction(); tx.begin(); try{ originalObject.voteUp(fooId); tx.commit(); }catch(Exception e){ tx.rallback(); throw e; } } 

So, even if voteUp is invoked simultaneously on many machines / in many threads, the value of "votes" always increases by one. Because a transaction in one thread locks a table to write data from other threads.

You are right: if the voteUp method takes a long time, this will lead to a decrease in efficiency. This means that methods annotated by @Transactional should not be time consuming.

And you're right, you can update database records without a choice if your ORM library allows this path.

+1
source

@Transactional in this case is used to manage SQL transactions, it does not add any thread safety. Spring's Transaction Manager does nothing but ask the database to start a new transaction, so you need to consult your RDBMS documentation and read about its transaction semantics.

So, in the first example there will be a race condition, even if SELECT and UPDATE are part of the same transaction. There are two possible solutions to your problem:

1- Lock lines. Acquiring a lock on the row you are about to change will not allow any other SQL transaction to change its value.

2- Optimistic lock: Optimistic lock does not actually use locks. What you do is that you are using a value that, as you know, is sure to change whenever this line is updated. For example, you could re-write your update statement:

 UPDATE foo SET votes = #{votes} (...) WHERE fooId == #{fooId} AND votes = #{oldNoOfVotes} 

If no rows are updated, this means that another process has already changed the value of this row, and you can either try again or throw an exception.

+1
source

This is not just atomicity. A standard database transaction should have the following features:

  • Atomic
  • According
  • Isolated
  • Lasting

These are the "ACID" requirements. What you noted in the β€œwrong” is actually still atomic, but not isolated. to make it isolated (so that simultaneous updates still give you the correct result), you can delegate concurrency processing to the database ( set vote = vote+1 ) or use your structure function to properly process isolation.

https://en.wikipedia.org/wiki/Database_transaction

0
source

Source: https://habr.com/ru/post/1240001/


All Articles