Spring Boot:Open Session In View caused cache problems

Introduction

The open session in view anti-pattern is enabled by default in Spring Boot(both 1.x and 2.x),where a Hibernate session is created and bound to the request(thread) at the very beginning of the request processing chain.The merit is that we can use the session to query or update database during the whole life cycle of the request(thread),including in the view layer(I’m not sure it is a good practice to do db query in the view layer).

When OSIV is enabled,care should be taken in regarding the cache related issues. Hibernate uses the first level cache per session, which can’t be disabled.This means that all database query results in the while life cycle of the request will be cached.In system design,the question of whether this behavior is desired should be asked.

Recently I encountered a situation related to the cache issues caused by OSIV.

The problem

It is a multi-tenant system and a single database is used, with each table having a tennant_id column.
Upon receiving request needing to increment  the value of some column in a specified row, two things should be done.

  1. Check and ensure that the operating user and the data belong to the same tenant.
  2. Begin transaction; Lock the row; row.some_column++; Commit.

We did the two things above in the following way.

  1. We separate the authority check concern of step 1 above as an aspect.
    –> Here the row to be updated is retrieved from database.
  2. We implement the main business logic of step 2 above in the public controller method, with the @Transaction annotation.
    –> This time we lock the row by another select for update query before incrementing the column.

In the two steps of processing, the same session is used, so in step 2 above, we just see the row retrieved in step1, because it is in the session cache(Hibernate first level cache),instead of the latest value of in step2 .
A possible sequence of two users(say Alice and Bob)updating a row(row#1) at the some time is shown below.
We assume that row#1.some_column is 0 before the update.

  1. [Alice]In the authority check aspect, row#1 is retrieved,with some_column as 0.
  2. [Bob]In the authority check aspect, row#1 is retrieved,with some_column as 0.
  3. [Alice]In business logic, row#1 is retrieved again, with a writing lock. This time, we got row#1 with some_column as 0 from the  session cache!
  4. [Alice]In business logic, increment row#1.some_column and commit the transaction, row#1.some_column becomes 1.
  5. [Bob]In business logic, row#1 is retrieved again by the select for update query, but this time, although the some_column value in the query result is 1, we still got row#1 with some_column as 0,because it is in the session cache!
  6. [Bob]In business logic, increment row#1.some_column and commit the transaction, row#1.some_column becomes 1.

So after the update operations of two users, we got wrong value of row#1.some_column!(actual:1, expected:2)

Solution

In my case above, Open Session In View is not we desired and we disable it by adding the follwing line in application.properties file.

spring.jpa.open-in-view=false

Notice:Open Session In View is enabled by default in both Spring Boot 1.x and 2.x.

References

The looooong discussion of whether to enable OSIV by default.