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