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