I'm hunting down a memory leak in a JSF application. I've read various articles that discuss potential memory leaks when using threads with transactions and sessions. Does the following code seem like it could produce a leak? This code is called thousands of times (for page requests).
public class HibernateHelper
{
private static final ThreadLocal threadSession = new ThreadLocal();
private static final ThreadLocal threadTransaction = new ThreadLocal();
public static Session startSessionWithDBName(SessionFactorySingleton.DBname dbname)
{
Session s = getSessionWithDBName(dbname);
beginTransaction(dbname);
return s;
}
#SuppressWarnings("unchecked")
private static Session getSessionWithDBName(SessionFactorySingleton.DBname dbname)
{
Session s = (Session) threadSession.get();
if (s == null)
{
s = SessionFactorySingleton.getSessionFactory(dbname).openSession();
threadSession.set(s);
}
else
{
String sf = ((SessionFactoryImpl)s.getSessionFactory()).getProperties().getProperty("hibernate.session_factory_name");
if((dbname !=null && sf !=null) && !sf.equals(dbname.toString()))
{
closeSessionBySessionFactoryName(sf);
s = SessionFactorySingleton.getSessionFactory(dbname).openSession();
threadSession.set(s);
}
}
return s;
}
#SuppressWarnings("unchecked")
public static boolean isSessionOpen()
{
Session s = (Session) threadSession.get();
if (s != null)
return s.isOpen();
return false;
}
public static boolean wasTransactionCommited()
{
Transaction t = (Transaction) threadTransaction.get();
if (t != null)
return t.wasCommitted();
return false;
}
#SuppressWarnings("unchecked")
public static void closeSessionBySessionFactoryName(String sessionfactoryname) {
Session s = (Session) threadSession.get();
if (s != null && s.isOpen())
{
String sfname = ((SessionFactoryImpl)s.getSessionFactory()).getProperties().getProperty("hibernate.session_factory_name");
if(sfname.equals(sessionfactoryname))
{
try
{
commitTransaction(sfname);
} catch (Exception e) {
e.printStackTrace();
}
s.close();
threadSession.set(null);
}
}
}
/**
* Start a new database transaction.
*/
private static void beginTransaction(SessionFactorySingleton.DBname dbname) {
Session s = (Session) threadSession.get();
if(s!=null)
{
log.info("Starting new transaction for this thread.");
Transaction tx = s.beginTransaction();
threadTransaction.set(tx);
}
}
/**
* Commit the database transaction.
*/
public static void commitTransaction(String sessionfactoryname) throws Exception {
Transaction tx = (Transaction) threadTransaction.get();
try {
if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) {
tx.commit();
log.info("Committed transaction for thread "+ Thread.currentThread().getName());
}
threadTransaction.set(null);
} catch (Exception e)
{
log.error("Exception when committing transaction: ", e);
rollbackTransactionKeepingSessionOpen();
throw e;
}
}
/**
* Rollback the database transaction.
*/
public static void rollbackTransactionKeepingSessionOpen() {
Transaction tx = (Transaction) threadTransaction.get();
threadTransaction.set(null);
if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) {
tx.rollback();
}
}
}
Yes it does.
Each threadLocal instance is bound to a lifespan of a thread that uses it.
Tomcat's thread pool can easily have hundreds of threads; each thread is reused and never destroyed.
Some of them will be stuck in an I/O operation, querying DB or whatnot, at the same time holding a reference to an open Hibernate session (which in turn may contain a lot of attached entities).
So during some periods the application may have many big object graphs that cannot be reclaimed by GC.
I think you should manage Hibernate sessions and transactions in a different way.
Related
I want to insert a list of Urls in a database under the condition that it doesn't exist in or an other table.
My current attempt is very slow every request can take 100ms - 500ms.
for(URL url : urls) {
if (this.urlToEditService.findByUrl(newUrl).size() == 0 &&
this.rawPageService.findByUrl(newUrl).size() == 0) {
UrlToEdit urlToEdit = new UrlToEdit(newUrl);
urlToEditService.save(urlToEdit);
}
}
This is my save method.
public boolean save(UrlToEdit urlToEdit) {
Transaction transaction = null;
try (Session session = factory.openSession()) {
transaction = session.beginTransaction();
session.save(urlToEdit);
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
return false;
}
return true;
}
Is there a way to insert all with one transaction? I could Imagine that the open and close part is the boodleneck.
EDIT1: I changed the code a bit so I would save in one transaction based on this answer:
List<URLToEdit> list = new ArrayList();
for(URL url : urls) {
if (this.urlToEditService.findByUrl(newUrl).size() == 0 &&
this.rawPageService.findByUrl(newUrl).size() == 0) {
list.add( new UrlToEdit(newUrl));
}
}
public boolean save(List<URLToEdit> urlToEditList) {
Transaction transaction = null;
int counter = 0;
try (Session session = factory.openSession()) {
transaction = session.beginTransaction();
for (UrlToEdit urlToEdit : urlToEditList) {
session.save(urlToEdit);
counter++;
if(counter > 20) {
counter = 0;
session.flush();
session.clear();
}
}
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
return false;
}
return true;
}
But the speed doesn't increase. So the slow part is still the exists check.
With this session handler:
public class SessionHandler {
private static SessionFactory DBContext;
static {
try {
DBContext = HibnerateConfiguration.config().buildSessionFactory();
}
catch(Throwable t) {
throw new ExceptionInInitializerError(t);
}
}
/*
* Returns a session anyway. If currently no session exist, open a new one;
* If there is a current session, use the existing one.
*/
#Override
public Session getSession() {
try {
return DBContext.getCurrentSession();
}
catch (HibernateException he) {
logger.error("session already exist.");
return DBContext.getCurrentSession();
}
}
public void close() {
DBContext.close();
}
}
and the following create and get methods:
public Serializable create(T type_entity) {
Session session = getSessionHandler().getSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Serializable result = session.save(type_entity);
tx.commit();
return result;
} catch (HibernateException ex) {
ex.printStackTrace();
if (tx!=null) tx.rollback();
throw ex;
} finally {
getSessionHandler().close();
}
}
#SuppressWarnings("unchecked")
public T get(Serializable id) throws InvalidRequestException {
Session session = getSessionHandler().getSession();
Transaction tx = session.beginTransaction();
tx.commit();
try {
Object obj = session.get(_classtype, id);
if (obj == null) {
throw new InvalidRequestException(String.format("requested object with id %s does not exist.", id));
} else {
return (T)obj;
}
} catch(HibernateException ex) {
ex.printStackTrace();
if (tx!=null) tx.rollback();
throw ex;
} finally {
getSessionHandler().close();
}
}
When I create an object that returns me id = 4, and if immediately I make a request on browser that eventually ask for the new object of id 4, I have to wait for a few seconds (last time I tried is > 3 seconds).
When the id is returned from the create, the data should already exist. However the get returns null. I highly suspect the get is using the old cache which then is updated every a few seconds, but I have no idea how to fix it.
Let me know if any info is required and I am happy to provide them.
In my application i have a situation where i need to do a recursive call to a method to achieve requirement.
But when i call the method I am getting
_Exception in thread "main" org.hibernate.SessionException: Session was already closed
at org.hibernate.internal.SessionImpl.close(SessionImpl.java:410)
at com.cerner.core.dao.oracleImpl.test.TestRecurssionSessionClose.fact(TestRecurssionSessionClose.java:40)
at com.cerner.core.dao.oracleImpl.test.TestRecurssionSessionClose.main(TestRecurssionSessionClose.java:49)
_
I have test code for this
public class TestRecurssionSessionClose {
private SessionFactory factory;
private Session session;
private Transaction transaction;
public TestRecurssionSessionClose() {
HibernateUtil.configureSessionFactory();
factory = HibernateUtil.getFactory();
}
public int fact(int n) {
System.out.println(factory.isClosed());
session = factory.openSession();
transaction = session.beginTransaction();
try {
if (n == 1) {
return 1;
} else {
System.out.println(n);
return (n * fact(n - 1));
}
} catch (
HibernateException ex) {
if (transaction != null)
transaction.rollback();
return 0;
} finally {
if (session != null) {
session.close();
}
}
}
public static void main(String[] args) {
TestRecurssionSessionClose testRecurssionSessionClose = new TestRecurssionSessionClose();
System.out.println(testRecurssionSessionClose.fact(3));
}
}
I am new to hibernate. please tell me what I am missing here?
By default some hibernate versiones have hibernate.transaction.auto_close_session to true. This makes that the session is closed automatically without requirement to close.
Disable the auto close session or better use automatic session context management
See: Session configuration
Sorry for my english. I learn JavaEE and i dont know proper or not i use session in hibernate. How to use them? I use pattern DAO and hibernate. Tell my how property use session
this is HibernateUtil class
private static final SessionFactory sessionFactory;
static {
try{
sessionFactory = new Configuration().configure("/app/web/landingpage/HibernateConnect/hibernate.cfg.xml").buildSessionFactory();
}catch(Throwable ex) {
System.out.println("Error " + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
public static void close(Session session) {
if (session != null) {
try {
session.close();
} catch (HibernateException ignored) {
System.out.print("Couldn't close Session" + ignored);
}
}
}
And that class makes all operation db CategoryDaoImpl
public class CategoryDaoImpl implements CategoryDao{
private Session session = null;
//get all category
public Collection getAllCategory() {
List categoris = new ArrayList<Category>();
try{
session = HibernateUtil.getSessionFactory().openSession();
categoris = session.createCriteria(Category.class).list();
}catch(Exception e) {
System.out.println("getAllCategory "+ e);
}finally{
if(session != null && session.isOpen())
session.close();
}
return categoris;
}
//get category id
public Category getCategory(int id) {
Category cat = null;
try {
session = HibernateUtil.getSessionFactory().openSession();
cat = (Category) session.load(Category.class, id);
}catch(Exception e) {
System.out.println("getAllCategory "+ e);
}finally{
if(session != null && session.isOpen())
session.close();
}
return cat;
}
//and below few methods that use it the some way session
}
and this servlet take results indexuser
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
try{
Collection allcategory = Factory.getInstance().getCatDAO().getAllCategory();
request.setAttribute("allcategory", allcategory);
request.getRequestDispatcher("/index.jsp").forward(request, response);
} catch(Exception e) {
System.out.println(e);
} finally{
if(session!=null && session.isOpen())
session.close();
}
The main contract here is the creation of Session instances. Usually an application has a single SessionFactory instance and threads servicing client requests obtain Session instances from this factory.
The internal state of a SessionFactory is immutable. Once it is created this internal state is set. This internal state includes all of the metadata about Object/Relational Mapping.
Basically session is used to get physical connection with database. So while you performing any DB Operations it will first open Session using sessionFactory and then Session do physical connection with database and then perform your operation and after performing operation you can close it.
Session is light weight.
I have one method :
// methods
private void transform(TransformList transformList) {
TableA tA = transformList.getTableA();
TableB tB = transformList.getTableB();
TableC tC = transformList.getTableC();
daoObj.saveA(tA);
daoObj.saveB(tB);
daoObj.saveC(tC);
}
Now,
//saveA mehtod
public void saveA(TableA tA) {
Session session = Context.getHibernateSession();
try {
if(session != null) {
Transaction tx = session.beginTransaction();
tx.begin();
session.merge(tA);
tx.commit();
}
}catch (Exception e) {
tx.rollback();
throw new SomeException(e.getMessage());
}
finally{
if (session != null) {
session.clear();
}
}
}
Similarly,
//saveB mehtod
public void saveB(TableB tB) {
Session session = Context.getHibernateSession();
try {
if(session != null) {
Transaction tx = session.beginTransaction();
tx.begin();
session.merge(tB);
tx.commit();
}
}catch (Exception e) {
tx.rollback();
throw new SomeException(e.getMessage());
}
finally{
if (session != null) {
session.clear();
}
}
}
And,
//saveC mehtod
public void saveC(TableC tC) {
Session session = Context.getHibernateSession();
try {
if(session != null) {
Transaction tx = session.beginTransaction();
tx.begin();
session.merge(tC);
tx.commit();
}
}catch (Exception e) {
tx.rollback();
throw new SomeException(e.getMessage());
}
finally{
if (session != null) {
session.clear();
}
}
}
Now while calling 'transform' method, if saveA and saveB method are success and then some
exception occurs in saveC method, is it possible to rollback TableA and TableB records
(which are committed already) along with TableC records?
No. You'll have move your transaction handling at least one level higher: create a transaction, and call saveA(), saveB() and saveC() inside that transaction. Don't handle transactions inside the save methods.
Of course, this is only one possibility. There are frameworks, like Spring, that greatly help you manage your transactions.