22

I am writing a little auction app, and it is very important that my bids are recorded with certainty. After all, the last couple seconds of the auction are critical moments for the buyers, and I can't risk them simultaneously bidding and having a race condition.

And of course, that's what transaction isolation is for. I can set my isolation level to serializeable, and we're all set.

But what about all the other requests? If people are viewing profiles, or sending messages, these requests don't need anywhere near that kind of transaction isolation. A read committed isolation level is perfectly acceptable for those requests.

I'm setting my transaction level as part of my hibernate property hibernate.connection.isolation, but I'd really like to be able to do something like session.setTransactionIsolation(newIsolation) per request.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
corsiKa
  • 81,495
  • 25
  • 153
  • 204
  • By very wary of Serializable as the default isolation level, as you will face query row and table locking with dead locks. In my experience the biggest issues with restrictive isolation will be around your data model, how the ORM is handling the fetches and and how the app opens and closes transactions. – stringy05 Apr 20 '15 at 05:52
  • Critical sections of the app (particularly, bids) must be done with atomicy and in a serializable fashion. No way around that - I cannot allow two people to bid on the same item at the same price, one of them has to be rejected. – corsiKa Apr 21 '15 at 15:34
  • org.hibernate.Session session = (Session)entityManager.getDelegate(); Connection connection = session.connection(); connection.setTransactionIsolation(Connection.READ_UNCOMMITTED); got from http://stackoverflow.com/questions/2924147/jpa-and-mysql-transaction-isolation-level – StanislavL Apr 22 '15 at 08:08

4 Answers4

11

If you're using Spring you can use something like this:

@Transactional(isolation = Isolation.SERIALIZABLE)

and it works for the JpaTransactionManager. If you are using JtaTransactionManager the request-scope transaction isolation is not propagated, as this is the default JTA behavior.

Because JTA doesn’t support transaction-scoped isolation levels, Spring offers the IsolationLevelDataSourceRouter to overcome this shortcoming when using application server JTA DataSources.

Because most DataSource implementations can only take a default transaction isolation level, we can have multiple such DataSources, each one serving connections for a specific transaction isolation level.

The logical transaction (e.g. @Transactional) isolation level setting is introspected by the IsolationLevelDataSourceRouter and the connection acquire request is therefore delegated to a specific DataSource implementation that can serve a JDBC Connection with the same transaction isolation level setting.

So, even in JTA environments, the transaction isolation router can offer a vendor-independent solution for overriding the default database isolation level on a per transaction basis.

Java EE doesn't support method-level transaction isolation configuration.

The SERIALIZABLE isolation level will protect you against non-repeatable reads and phantom reads, and even SERIALIZABLE doesn't protect you against lost updates across multiple-request logical transactions.

Optimistic locking6 scales better when using the detached entities (as of they were loaded when the logical transaction has started).

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Seeking a pure hibernate answer, not one directly tied to Spring. – corsiKa Apr 21 '15 at 15:36
  • It's DataSource related, not something Hibernate can control. You can do it what Spring does on your behalf, if you're willing to do the routing yourself. – Vlad Mihalcea Apr 21 '15 at 15:53
  • Why then is it called the default isolation level if you can't ever change the default on the fly? That's not a "default" setting, that's a permanent setting. There has to be some way to change it (and, without setting up a connection for each transaction level, that's a hack...) – corsiKa Apr 21 '15 at 19:55
  • Most often you use connection pooling and the isolation level is already set by the DataSource. To change the isolation level you'd have to do it right after starting a new transaction, because you can't change it in the middle of an ongoing transaction. If you use programmatic transaction management, you would have to set manually control it around the transaction boundaries. As of speaking, I am not aware of any such Hibernate feature. If you find a solution, please let me know. – Vlad Mihalcea Apr 21 '15 at 20:35
  • I awarded the bounty to jjcosare because it answered the question. However, I'll be issuing two more (for you and for Carlitos) because you guys solved my problem. I much enjoyed your blog posts (especially the step-by-step sql entries, very easy to follow). Thank you very much. – corsiKa Apr 26 '15 at 17:55
7
Session session = getSession(dataSource, sessionFactory, Connection.TRANSACTION_SERIALIZABLE);

public Session getSession(DataSource dataSource, SessionFactory sessionFactory, int isolationLevel){

  // Get connection from current dataSource and set new isolation
  Connection connectionWithNewIsolation = dataSource.getConnection();
  connectionWithNewIsolation.setTransactionIsolation(isolationLevel);

  // Get session from current sessionFactory with the new isolation
  Session session = sessionFactory.openSession(connectionWithNewIsolation);

  // Hibernate 4.3
  //SessionFactory.openStatelessSession(Connection connection)
  // Hibernate 3.6
  //SessionFactory.openSession(Connection connection)
  //SessionFactory.openStatelessSession(Connection connection)

  return session;
}
jjcosare
  • 1,463
  • 9
  • 9
  • This `getConnection` - does that create a new connection? So I need to clean up that connection when I'm done? – corsiKa Apr 24 '15 at 14:51
  • Yes it will established a new connection. session.close() will clean up the connection for you. – jjcosare Apr 26 '15 at 05:24
  • This definitely answers exactly what I asked for. But it turns out what I asked for isn't what I should be doing. Still, this answers the question, which is exactly what I asked for, right? =] – corsiKa Apr 26 '15 at 17:56
  • Could you do that after the session is already created? I mean session.doWork and then connection.setTransactionIsolation level, or will that be too late already ? – mjs Mar 12 '21 at 13:30
2

For this case, i will use Optimistic lock in your bids objects... the race condition will still occurs, but it will be detected when the transaction tries to commit the changes at your domain objects (throwing an exception if the version readed was updated by another thread).

So any change on any bid object, will be almost serializable (i say "almost" because in order to be serializable, the failed transactions will need to be catched and retried somehow).

Carlitos Way
  • 3,279
  • 20
  • 30
  • So let everyone go wild because the number of collisions is going to be very few, and when a collision does occur, just retry the request? Or in my particular case, tell the user "sorry, someone bid while you were thinking - please refresh" – corsiKa Apr 24 '15 at 22:31
  • Exactly... however, i don't if the collisions are gonna be only a few... may be not, if there many users bidding at the same time for a wanted object... May be you should run some test using jmeter to see how this strategy works for you... – Carlitos Way Apr 24 '15 at 22:44
  • Those are implementations details... you will have to analyze what strategy is more friendly user... the message strategy is easy to implement, but may cause frustration on users that try to bid many times without success (IMHO) .... – Carlitos Way Apr 24 '15 at 22:47
  • Most bids are done for the minimum amount (although some tricky people will throw one or two extra increments to throw off other bidders) and there is literally no way around two people bidding the same amount of money at (roughly, within a few seconds) the same time and having one of them get rejected. But that said, I love that instead of serializing the transaction, I use versioning instead. I will be testing this and the other strategy this weekend, but I must say I like this one the best. – corsiKa Apr 25 '15 at 04:18
  • @CarlitosWay I'll be issuing another bounty for you because your answer helped me understand Vlad's answer better which is what actually solved my problem. Thank you very much for your time and effort! – corsiKa Apr 26 '15 at 17:57
0

if setting isolation level per transaction fails, you can always serialize specific operation manually in your code (synchronized, semaphores etc). however keep it mind it's not scalable (single jvm, single operation, easy to be accidentally by-passed from other parts of code)

piotrek
  • 13,982
  • 13
  • 79
  • 165