UPDATE: Solution right after question.
Question:
Usually, synchronization is serializing parallel requests within a JVM, e.g.
private static final Object LOCK = new Object();
public void doSomething() {
...
synchronized(LOCK) {
...
}
...
}
When looking at web applications, some synchronization on "JVM global" scope is maybe becoming a performance bottleneck and synchronization only within the scope of the user's HttpSession would make more sense.
Is the following code a possibility? I doubt that synchronizing on the session object is a good idea but it would be interesting to hear your thoughts.
HttpSession session = getHttpServletRequest().getSession();
synchronized (session) {
...
}
Key Question:
Is it guaranteed that the session object is the same instance for all threads processing requests from the same user?
Summarized answer / solution:
It seems that the session object itself is not always the same as it depends on the implementation of the servlet container (Tomcat, Glassfish, ...) and the getSession() method might return just a wrapper instance.
So it is recommended to use a custom variable stored in the session to be used as locking object.
Here is my code proposal, feedback is welcome:
somewhere in a Helper Class, e.g. MyHelper:
private static final Object LOCK = new Object();
public static Object getSessionLock(HttpServletRequest request, String lockName) {
if (lockName == null) lockName = "SESSION_LOCK";
Object result = request.getSession().getAttribute(lockName);
if (result == null) {
// only if there is no session-lock object in the session we apply the global lock
synchronized (LOCK) {
// as it can be that another thread has updated the session-lock object in the meantime, we have to read it again from the session and create it only if it is not there yet!
result = request.getSession().getAttribute(lockName);
if (result == null) {
result = new Object();
request.getSession().setAttribute(lockName, result);
}
}
}
return result;
}
and then you can use it:
Object sessionLock = MyHelper.getSessionLock(getRequest(), null);
synchronized (sessionLock) {
...
}
Any comments on this solution?
I found this nice explanation in spring-mvc JavaDoc for WebUtils.getSessionMutex():
In many cases, the HttpSession reference itself is a safe mutex as well, since it will always be the same object reference for the same active logical session. However, this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex.
This method is used as a lock when synchronizeOnSession flag is set:
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
return handleRequestInternal(request, response);
}
If you look at the implementation of getSessionMutex(), it actually uses some custom session attribute if present (under org.springframework.web.util.WebUtils.MUTEX key) or HttpSession instance if not:
Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE);
if (mutex == null) {
mutex = session;
}
return mutex;
Back to plain servlet spec - to be 100% sure use custom session attribute rather than HttpSession object itself.
See also
http://www.theserverside.com/discussions/thread.tss?thread_id=42912
In general, don't rely on HttpServletRequest.getSession() returning same object. It's easy for servlet filters to create a wrapper around session for whatever reason. Your code will only see this wrapper, and it will be different object on each request. Put some shared lock into the session itself. (Too bad there is no putIfAbsent though).
As people already said, sessions can be wrapped by the servlet containers and this generates a problem: the session hashCode() is different between requests, i.e., they are not the same instance and thus can't be synchronized! Many containers allow persist a session. In this cases, in certain time, when session was expired, it is persisted on disk. Even when session is retrieved by deserialization, it is not same object as earlier, because it don't shares same memory address like when was at memory before the serialization process. When session is loaded from disk, it is put into memory for further access, until "maxInactiveInterval" is reached (expires). Summing up: the session could be not the same between many web requests! It will be the same while is in memory. Even if you put an attribute into the session to share lock, it will not work, because it will be serialized as well in the persistence phase.
Synchronization occurs when a lock is placed on an object reference, so that threads that reference the same object will treat any synchronization on that shared object as a toll gate.
So, what your question raises an interesting point: Does the HttpSession object in two separate web calls from the same session end up as the same object reference in the web container, or are they two objects that just happen to have similar data in them? I found this interesting discussion on stateful web apps which discusses HttpSession somewhat. Also, there is this discussion at CodeRanch about thread safety in HttpSession.
From those discussions, it seems like the HttpSession is indeed the same object. One easy test would be to write a simple servlet, look at the HttpServletRequest.getSession(), and see if it references the same session object on multiple calls. If it does, then I think your theory is sound and you could use it to sync between user calls.
Here is my own solution:
It seems that the session object itself is not always the same as it depends on the implementation of the servlet container (Tomcat, Glassfish, ...) and the getSession() method might return just a wrapper instance.
So it is recommended to use a custom variable stored in the session to be used as locking object.
Here is my code proposal, feedback is welcome:
somewhere in a Helper Class, e.g. MyHelper:
private static final Object LOCK = new Object();
public static Object getSessionLock(HttpServletRequest request, String lockName) {
if (lockName == null) lockName = "SESSION_LOCK";
Object result = request.getSession().getAttribute(lockName);
if (result == null) {
// only if there is no session-lock object in the session we apply the global lock
synchronized (LOCK) {
// as it can be that another thread has updated the session-lock object in the meantime, we have to read it again from the session and create it only if it is not there yet!
result = request.getSession().getAttribute(lockName);
if (result == null) {
result = new Object();
request.getSession().setAttribute(lockName, result);
}
}
}
return result;
}
and then you can use it:
Object sessionLock = MyHelper.getSessionLock(getRequest(), null);
synchronized (sessionLock) {
...
}
Any comments on this solution?
Another solution suggested in "Murach's Java Servlets and JSP (3rd Edition)" book:
Cart cart;
final Object lock = request.getSession().getId().intern();
synchronized (lock) {
cart = (Cart) session.getAttribute("cart");
}
Personally, I implement session-locking with the help of an HttpSessionListener*:
package com.example;
#WebListener
public final class SessionMutex implements HttpSessionListener {
/**
* HttpSession attribute name for the session mutex object. The target for
* this attribute in an HttpSession should never be altered after creation!
*/
private static final String SESSION_MUTEX = "com.example.SessionMutex.SESSION_MUTEX";
public static Object getMutex(HttpSession session) {
// NOTE: We cannot create the mutex object if it is absent from
// the session in this method without locking on a global
// constant, as two concurrent calls to this method may then
// return two different objects!
//
// To avoid having to lock on a global even just once, the mutex
// object is instead created when the session is created in the
// sessionCreated method, below.
Object mutex = session.getAttribute(SESSION_MUTEX);
// A paranoia check here to ensure we never return a non-null
// value. Theoretically, SESSION_MUTEX should always be set,
// but some evil external code might unset it:
if (mutex == null) {
// sync on a constant to protect against concurrent calls to
// this method
synchronized (SESSION_MUTEX) {
// mutex might have since been set in another thread
// whilst this one was waiting for sync on SESSION_MUTEX
// so double-check it is still null:
mutex = session.getAttribute(SESSION_MUTEX);
if (mutex == null) {
mutex = new Object();
session.setAttribute(SESSION_MUTEX, mutex);
}
}
}
return mutex;
}
#Override
public void sessionCreated(HttpSessionEvent hse) {
hse.getSession().setAttribute(SESSION_MUTEX, new Object());
}
#Override
public void sessionDestroyed(HttpSessionEvent hse) {
// no-op
}
}
When I need a session mutex, I can then use:
synchronized (SessionMutex.getMutex(request.getSession())) {
// ...
}
__
*FWIW, I really like the solution proposed in the question itself, as it provides for named session locks so that requests for independent resources don't need to share the same session lock. But if a single session lock is what you want, then this answer might be right up your street.
The answers are correct. If you want to avoid the same user executes 2 different (or the same) requests at the same time, you can synchronize on the HttpSession. The best to do this is to use a Filter.
Notes:
if your resources (images, scripts, and any non-dynamic file) also comes through the servlet, you could create a bottleneck. Then be sure, the synchonization is only done on dynamic pages.
Try to avoid the getSession directly, you should better test if the session already exists because a session is not automatically created for guests (as nothing has to be stored in the session). Then, if you call getSession(), the session will be created and memory will be lost. Then use getSession(false) and try to deal with the null result if no session already exists (in this case, don't synchronize).
The spring framework solution as mentioned by Tomasz Nurkiewicz is accidentally correct in clustered environments only because the Servlet spec requires session consistency across multiple JVMs. Otherwise, it does not do a magic on its own for the scenarios where multiple requests are spread across different machines. See the discussion in this thread that sheds some light on the subject.
Using
private static final Object LOCK = new Object();
you are using the same lock for all sessions and it was the core reason for deadlock I did face.
So every session in your implementation has the same race condition, which is bad.
It needs change.
Other suggested answer:
Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE);
if (mutex == null) {
mutex = session;
}
return mutex;
seems much better.
Related
I have a long-running operation in a Spring Boot web application.
This is how it works:
When the user clicks a button, a POST request is made and the operation starts.
Because the operation will take a long time, it is started asynchronously and a response is sent immediately.
Using JavaScript, I periodically send GET requests to find out if the operation has finished.
Here are the request handlers:
import java.util.concurrent.Future;
#RequestMapping(value = "/start", method = RequestMethod.POST)
#ResponseBody
String start(HttpSession session) {
Future<String> result = resultService.result();
session.setAttribute("result", result);
return "started";
}
#RequestMapping(value = "/status")
#ResponseBody
String status(HttpSession session) throws Exception {
#SuppressWarnings("unchecked")
Future<String> result = (Future<String>) session.getAttribute("result");
if (result != null && result.isDone()) {
return result.get();
} else {
return "working";
}
}
And this is the long-running operation (in a separate bean):
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
#Async
#Override
public Future<String> result() {
String result = computeResult(); // takes long
return new AsyncResult<String>(result);
}
The complete example is on GitLab.
Also, here's a GIF showing how it works.
Now, this works, but the problem is that SonarQube raised an issue:
Make "Future" and its parameters serializable or don't store it in the session.
It explained that
the session [...] may be written to disk anyway, as the server manages its memory use in a process called "passivation". Further, some servers automatically write their active sessions out to file at shutdown & deserialize any such sessions at startup.
See MITRE, CWE-579 - J2EE Bad Practices: Non-serializable Object Stored in Session
Since I can't make Future serializable, what would be a better way to keep track of the long-running operation between requests?
Now, this works, but the problem is that SonarQube raised an issue:
To fix the above issue, you can write a wrapper class implementing Serializable that contains the result of the Future object along with the Future object as transient. And you can place this wrapper object in the session instead of directly putting the Future object.
Ex:
public class ResultWrapper implements Serializable {
private String result = "working"; //String, since the resultService.result() is returning Future<String>
private transient Future future; //transient as it is not serializable.
public String getResult() {
if (future != null && future.isDone()) {
result = future.get();
//Once the future is done, call the session.setAttribute(...) so that value of result field is replicated across the JVM nodes.
}
return result;
}
}
Note that this just solves the issue you have raised regarding the SonarQube. But it doesn't really provide failover or handles activation/passivation even if the session replication is active.
If there are two nodes M1 & M2 on which the webapp is running with session replication in place, the async job computeResult(); will obviously be running only on one of the machines (the one which received the initial request) and if that machine goes down all the requests will be forwarded to the other active machine and the result will always return "working" forever.
Another issue which applies even the webapp is running on a single node is that, if the session gets passivated the future will not be passivated as it is transient and so you will loose reference to that object and reactivated wrapper will have future obj as null. Finally the result is same as above case.
I would like to know how to make sure that some method in a service is accessed only once at a time per session.
I'll illustrate by a small example:
Assume we have a user in a state A (user.state = A). This user sends a HTTP GET request to our java spring controller to get a page, say /hello. Based on his status, he will be sent to either A or B. Before that, we will change his status to B (see code below).
Now, assume again that the call dao.doSomething(); takes a lot of time. If the user sends another GET (by refreshing his browser for instance), he will call the exact same method dao.doSomething(), resulting in 2 calls.
How can you avoid that?
What happens if you sends 2 HTTP GETs at the same time?
How can you have something consistent in your controller/service/model/database?
Note 1: here we don't issue the 2 HTTP GETs from different browser. We just make them at the same time on the same browser (I'm aware of the max concurrent session solution, but this does not solve my problem.).
Note 2: the solution should not block concurrent accesses of the controller for different users.
I've read a bit about transaction on service, but I'm not sure if this is the solution. I've also read a bit on concurrency, but I still don't understand how to use it here.
I would greatly appreciate your help! Thanks!
code example:
#Controller
public class UserController {
#RequestMapping(value='/hello')
public String viewHelloPage() {
// we get the user from a session attribute
if (user.getState() = A) {
user.setStatus(B);
return "pageA";
}
return "pageB";
}
#Service
public class UserService {
Dao dao;
#Override
public void setStatus(User user) {
dao.doSomething();
user.setStatus(B);
}
}
Although I wouldn't recommend it (as it basically blocks all other calls from the same user to). On most HandlerAdapter implementations you can set the property synchronizeOnSession by default this is false allowing for concurrent requests to come from the same client. When you set this property to true requests will be queued for that client.
How to set it depends on your configuration of the HandlerAdapter.
how to make sure that some method in a service is accessed only once
at a time per session.
Try to Lock on session object in your controller before calling service method
If dao.doSomething() is doing work that you only want to happen once, you should use an idempotent method like PUT or DELETE. There's no law forcing you to use the correct method, but worst-case it's a self-documenting way to tell the world about how your API should be used. If that isn't enough for you, most browsers will try to help you out based on the type of request. For instance, the browser will often use caching to avoid multiple GETs.
It seems like what you really want to know is how to enforce idempotency. This is very application-specific. One general approach is to generate and store a pseudo-unique id on the server side for the client to attach to their request. This way, any request with the same id after the first can be safely ignored. Obviously old ids should be evicted intelligently.
As I said, the solution is often application-specific. In your case above, it looks like you're trying to switch between 2 states, and your implementation is a server-side toggle. You can utilize the client to ensure that multiple requests will not be a problem.
#RequestMapping(value="/hello", method=RequestMethod.PUT)
public String test(#RequestParam("state") String state) {
dao.setState(user, state)
switch (state) {
case "A":
return "B";
case "B":
return "A";
default:
return "error";
}
}
If you don't mind to configure and use AOP, then the following might help you
#Aspect
#Component
public class NonConcurrentAspect implements HttpSessionListener{
private Map<HttpSession, Map<Method, Object>> mutexes = new ConcurrentHashMap<HttpSession, Map<Method, Object>>();
#Around(value = "#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object handle(ProceedingJoinPoint pjp) throws Throwable {
MethodInvocationProceedingJoinPoint methodPjp = (MethodInvocationProceedingJoinPoint) pjp;
Method method = ((MethodSignature) methodPjp.getSignature()).getMethod();
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
HttpSession session = request.getSession(false);
Object mutex = getMutex(session, method);
synchronized (mutex) {
return pjp.proceed();
}
}
private Object getMutex(HttpSession session, Method method) {
Map<Method, Object> sessionMutexes = mutexes.get(session);
Object mutex = new Object();
Object existingMutex = sessionMutexes.putIfAbsent(method, mutex);
return existingMutex == null ? mutex : existingMutex;
}
#Override
public void sessionCreated(HttpSessionEvent se) {
mutexes.put(se.getSession(), new ConcurrentHashMap<Method, Object>());
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
mutexes.remove(se.getSession());
}
}
It synchronizes on a per-session per-method mutex. One restriction is that the methods so advised should not call each other (which is hardly a case, unless you violate MVC design pattern severely), otherwise you may face deadlocks.
This would handle all the methods tagged with #RequestMapping, but if you want just few methods be guarded against concurrent execution,
then, as one of the possible solutions, you could introduce your own annotation, e.g.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface NonConcurrent {
}
tag the specific methods with this annotation, and replace #RequestMapping in #Around annotation in the above aspect class with your own.
In highly contended environment you may think of more advanced solution than intrinsic locks.
I would, however, advise against using HandlerAdapter's synchronizeOnSession option, not only because it synchronizes all the invocations on the same mutex, but, which is less obvious, the synchronization on publicly available mutex is potentially dangerous.
RollerSession has the following code:
public static RollerSession getRollerSession(HttpServletRequest request) {
RollerSession rollerSession = null;
HttpSession session = request.getSession(false);
if (session != null) {
rollerSession = (RollerSession)session.getAttribute(ROLLER_SESSION);
if (rollerSession == null) {
// HttpSession with no RollerSession?
// Must be a session that was de-serialized from a previous run.
rollerSession = new RollerSession();
session.setAttribute(ROLLER_SESSION, rollerSession);
}
....
I am new to concurrency problems. Here there seems to be an atomicity violation with two different threads potentially updating setAttribute concurrently. Is that right? Can session be shared by two threads given that it is being obtained from request?
Yes, you are right, more over there is a visibility problem too! According to IBM's post and Java Ranch the get/set operation are NOT thread-safe. So if you don't want any race conditions in your app, you should synchronize, but be careful where you put the synchronized.
EXPLANATION
Multiple servlets executing request threads may have active access to the same session object at the same time. The container must ensure that manipulation of internal data structures representing the session attributes is performed in a threadsafe manner. The Developer has the responsibility for threadsafe access to the attribute objects themselves. This will protect the attribute collection inside the HttpSession object from concurrent access, eliminating the opportunity for an application to cause that collection to become corrupted.
This is safe:
// guaranteed by the spec to be safe
request.getSession().setAttribute("foo", 1);
This is NOT safe:
HttpSession session = request.getSession();
Integer n = (Integer) session.getAttribute("foo");
// not thread safe
// another thread might be have got stale value between get and set
session.setAttribute("foo", (n == null) ? 1 : n + 1);
-- McDowell's Answer
I'd like to know the correct / best way to handle concurrency with an Axis2 webservice.
Eg, given this code:
public class MyServiceDelegate
{
#Resource
UserWebService service; // Injected by spring
public CustomerDTO getCustomer()
{
String sessionString = getSessionStringFromCookies();
service.setJSESSIONID(sessionString);
CustomerDTO customer = service.getCustomerFromSessionID();
}
}
Note that in the above that UserWebService is a 3rd party API. The service requires that when making calls, we pass a cookie with the JSESSIONID of an authenticated session.
Am I correct in assuming that this statement is not threadsafe? IE., given two threads, is it possible for the following to occur?
ThreadA : service.setJSESSIONID("threadA")
ThreadB : service.setJSESSIONID("threadB")
ThreadA : service.getCustomerFromSessionID // service.sesionID == "threadB"
If so, what's the most appropriate way to handle this situation? Should I use a resource pool for service? Or should I declare service as synchronized?
public CustomerDTO getCustomer()
{
synchronized( service ) {
service.setJSESSIONID(sessionString);
CustomerDTO customer = service.getCustomerFromSessionID();
}
}
Or, is there another, more appropriate way to handle this problem?
Would each thread have its own Delegate object and hence its own UserWebService service?
In the simple case, if delegates are created on the stack the threads would be independent.
If the cost of creation is high, have a pool of the delegate objects. Taking one from teh pool is comparativley cheap. You need to be very careful with housekeeping, but effectively this is what is done with database connections. Some environments have utility classes for managing such pooling - tends to be preferable to rolling your own.
Is UserWebService one of your classes? If so, I think I'd change the method signature to:
public CustomerDTO getCustomer()
{
CustomerDTO customer = service.getCustomerFromSessionID(sessionString);
}
And not have your UserWebService maintain state, that way it will be inherently thread-safe
As you said, the function is not thread safe. Java has a simple way to make monitors, which is an object that only allows one thread to access a function at a time. More info on monitors
To make it thread safe you can put synchronized either, as you did, around the expression, or before the function name:
public synchronized CustomerDTO getCustomer(){
service.setJSESSIONID(sessionString);
CustomerDTO customer = service.getCustomerFromSessionID();
}
The difference between the two is which object you turn into a monitor.
If I have a util class with static methods that will call Hibernate functions to accomplish basic data access. I am wondering if making the method synchronized is the right approach to ensure thread-safety.
I want this to prevent access of info to the same DB instance. However, I'm now sure if the following code are preventing getObjectById being called for all Classes when it is called by a particular class.
public class Utils {
public static synchronized Object getObjectById (Class objclass, Long id) {
// call hibernate class
Session session = new Configuration().configure().buildSessionFactory().openSession();
Object obj = session.load(objclass, id);
session.close();
return obj;
}
// other static methods
}
To address the question more generally...
Keep in mind that using synchronized on methods is really just shorthand (assume class is SomeClass):
synchronized static void foo() {
...
}
is the same as
static void foo() {
synchronized(SomeClass.class) {
...
}
}
and
synchronized void foo() {
...
}
is the same as
void foo() {
synchronized(this) {
...
}
}
You can use any object as the lock. If you want to lock subsets of static methods, you can
class SomeClass {
private static final Object LOCK_1 = new Object() {};
private static final Object LOCK_2 = new Object() {};
static void foo() {
synchronized(LOCK_1) {...}
}
static void fee() {
synchronized(LOCK_1) {...}
}
static void fie() {
synchronized(LOCK_2) {...}
}
static void fo() {
synchronized(LOCK_2) {...}
}
}
(for non-static methods, you would want to make the locks be non-static fields)
By using synchronized on a static method lock you will synchronize the class methods and attributes ( as opposed to instance methods and attributes )
So your assumption is correct.
I am wondering if making the method synchronized is the right approach to ensure thread-safety.
Not really. You should let your RDBMS do that work instead. They are good at this kind of stuff.
The only thing you will get by synchronizing the access to the database is to make your application terribly slow. Further more, in the code you posted you're building a Session Factory each time, that way, your application will spend more time accessing the DB than performing the actual job.
Imagine the following scenario:
Client A and B attempt to insert different information into record X of table T.
With your approach the only thing you're getting is to make sure one is called after the other, when this would happen anyway in the DB, because the RDBMS will prevent them from inserting half information from A and half from B at the same time. The result will be the same but only 5 times ( or more ) slower.
Probably it could be better to take a look at the "Transactions and Concurrency" chapter in the Hibernate documentation. Most of the times the problems you're trying to solve, have been solved already and a much better way.
Static methods use the class as the object for locking, which is Utils.class for your example. So yes, it is OK.
static synchronized means holding lock on the the class's Class object
where as
synchronized means holding lock on the class' instance. That means, if you are accessing a non-static synchronized method in a thread (of execution) you still can access a static synchronized method using another thread.
So, accessing two same kind of methods(either two static or two non-static methods) at any point of time by more than a thread is not possible.
Why do you want to enforce that only a single thread can access the DB at any one time?
It is the job of the database driver to implement any necessary locking, assuming a Connection is only used by one thread at a time!
Most likely, your database is perfectly capable of handling multiple, parallel access
If it is something to do with the data in your database, why not utilize database isolation locking to achieve?
To answer your question, yes it does: your synchronized method cannot be executed by more than one thread at a time.
How the synchronized Java keyword works
When you add the synchronized keyword to a static method, the method can only be called by a single thread at a time.
In your case, every method call will:
create a new SessionFactory
create a new Session
fetch the entity
return the entity back to the caller
However, these were your requirements:
I want this to prevent access to info to the same DB instance.
preventing getObjectById being called for all classes when it is called by a particular class
So, even if the getObjectById method is thread-safe, the implementation is wrong.
SessionFactory best practices
The SessionFactory is thread-safe, and it's a very expensive object to create as it needs to parse the entity classes and build the internal entity metamodel representation.
So, you shouldn't create the SessionFactory on every getObjectById method call.
Instead, you should create a singleton instance for it.
private static final SessionFactory sessionFactory = new Configuration()
.configure()
.buildSessionFactory();
The Session should always be closed
You didn't close the Session in a finally block, and this can leak database resources if an exception is thrown when loading the entity.
According to the Session.load method JavaDoc might throw a HibernateException if the entity cannot be found in the database.
You should not use this method to determine if an instance exists (use get() instead). Use this only to retrieve an instance that you assume exists, where non-existence would be an actual error.
That's why you need to use a finally block to close the Session, like this:
public static synchronized Object getObjectById (Class objclass, Long id) {
Session session = null;
try {
session = sessionFactory.openSession();
return session.load(objclass, id);
} finally {
if(session != null) {
session.close();
}
}
}
Preventing multi-thread access
In your case, you wanted to make sure only one thread gets access to that particular entity.
But the synchronized keyword only prevents two threads from calling the getObjectById concurrently. If the two threads call this method one after the other, you will still have two threads using this entity.
So, if you want to lock a given database object so no other thread can modify it, then you need to use database locks.
The synchronized keyword only works in a single JVM. If you have multiple web nodes, this will not prevent multi-thread access across multiple JVMs.
What you need to do is use LockModeType.PESSIMISTIC_READ or LockModeType.PESSIMISTIC_WRITE while applying the changes to the DB, like this:
Session session = null;
EntityTransaction tx = null;
try {
session = sessionFactory.openSession();
tx = session.getTransaction();
tx.begin();
Post post = session.find(
Post.class,
id,
LockModeType.LockModeType.PESSIMISTIC_READ
);
post.setTitle("High-Performance Java Perisstence");
tx.commit();
} catch(Exception e) {
LOGGER.error("Post entity could not be changed", e);
if(tx != null) {
tx.rollback();
}
} finally {
if(session != null) {
session.close();
}
}
So, this is what I did:
I created a new EntityTransaction and started a new database transaction
I loaded the Post entity while holding a lock on the associated database record
I changed the Post entity and committed the transaction
In the case of an Exception being thrown, I rolled back the transaction