Concurrency Strategies

After you enable second-level caching in Hibernate, you need to explain to Hibernate which Entity objects we want to cache and how.

To do this, Hibernate has a special annotation for Entity classes - @Cache . Example:

@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)

This annotation needs to be written for each Entity entity for which we want to use the second level cache. Example:

@Entity
@Table(name = "employee")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Employee {
    @Id
    private Integer id;
    private Set<Task> tasks;
}

Hibernate has 4 possible access strategies for a cached entity if it is accessed from different threads:

  • read-only
  • read-write
  • nonstrict-read-write
  • transactional

Read -only. A data-appropriate concurrency strategy that never changes. Hibernate will simply store these objects in its memory. Use it for reference data only.

Databases store a lot of information that never changes. For example, a table keeps a list of events that are only added but never changed or removed. If you need to work with this table through Hibernate, then the read-only caching strategy will suit you.

Read-write (read-write). Use this strategy for data that is primarily readable. However, Hibernate will track attempts to change this data, although it expects them to be very infrequent.

You need to cache mainly those objects that rarely change and are often read / requested. If you have such objects, then you need to use the read-write strategy for them.

Nonstrict-read-write . This strategy does not guarantee consistency between the cache and the database. Use this strategy if the data almost never changes and a small chance of stale data is not a critical issue.

Unlike the read-write strategy, this strategy assumes that mutable data is not locked for reading. This can result in the object being changed in one place, while in another, someone is reading the old version of it.

For example, a user has changed his comment, but other users still see his old version for some time. If this is not a problem for you, then use the nonstrict-read-write strategy.

Transactional . Use this strategy for primarily read-only data where it is important to prevent stale data in concurrent transactions on the rare occasion of an update.

Storing data in a cache

Another important detail about the second level cache that you should remember is that Hibernate does not store the objects of your classes themselves. It stores information as arrays of strings, numbers, etc.

And the object identifier acts as a pointer to this information. Conceptually, this is something like a Map, in which the id of the object is the key, and the data arrays are the value. You can imagine it like this:

1 -> { "Ivanov", 1, null , {1,2,5} }
2 -> { "Petrov", 2, null , {1,2,5} }
3 -> { "Sidorov", 3, null , {1,2,5} }

Which is very reasonable considering how much extra memory each object takes up.

In addition to the above, you should remember that the dependencies of your Entity class are also not cached by default. For example, if we consider the class above, Employee , then when fetching, the tasks collection will be retrieved from the database , and not from the second-level cache .

If you want to cache dependencies as well, then the class should look like this:

@Entity
@Table(name = "employee")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Employee {
    @Id
    private Integer id;

   @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
   private Set<Task> tasks;
}

And the last detail - reading from the second-level cache occurs only if the desired object was not found in the first-level cache.

CacheMode

Hibernate allows for very flexible caching management. You can set the cache mode for each individual session or even for each database request.

There are five such modes:

  • GET
  • IGNORE
  • NORMAL
  • PUT
  • REFRESH

The table below describes their work:

CacheMode Description
GET Data is read from the cache but not added to it.
IGNORE The session does not interact with the cache.
NORMAL Data is read from the cache and added to it.
PUT Data is never taken from the cache, but added to it.
REFRESH Data is never taken from the cache, but added to it. In this mode, the hibernate.cache.use_minimal_puts setting is additionally used.

An example of setting the cache mode for a session:

session.setCacheMode(CacheMode.GET);
Employee director = session.createQuery("from Employee where id = 4").uniqueResult();

And also an example of setting the mode for the session and the request:

session.setCacheMode(CacheMode.GET);
Query query = session.createQuery("from Employee where id = 4");
query.setCacheMode(CacheMode.IGNORE); // Ignore cache work for this request
Employee director = query.uniqueResult();
undefined
1
Task
Module 4. Working with databases, level 14, lesson 5
Locked
task1405
task1405