Does the hibernate session need to be closed? - java

I'm adding some batching utility to my Spring Boot app and I'm using the session from my entity manager to do so. The batching seems to work fine but I'm wondering if I need to close my session and if there's a potential memory leak I'm missing.
As I understand it, closing a session disallows any further action on the session to occur. When I was closing the session before I encountered an error of IllegalStateException: Session/Entity Manager is closed. So in my case since I'm using the persistence context, I believe I don't want to clear the session.
#Repository
public class BatchDAO {
#PersistenceContext
private EntityManager em;
#Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
private Integer batchSize;
#Transactional
public <S> List<S> batchUpdate(Iterable<S> items) {
Session session = em.unwrap(Session.class);
Iterator<S> iterator = items.iterator();
int i = 0;
while (iterator.hasNext()) {
if (i > 0 && i % batchSize == 0) {
session.flush();
session.clear();
}
S item = iterator.next();
session.update(item);
i++;
}
session.flush();
session.clear();
// session.close(); throws exception
return Lists.newArrayList(items);
}
#Transactional
public <S> List<S> batchInsert(Iterable<S> items) {
Session session = em.unwrap(Session.class);
int i = 0;
Iterator<S> iterator = items.iterator();
while (iterator.hasNext()) {
if (i > 0 && i % batchSize == 0) {
session.flush();
session.clear();
}
S item = iterator.next();
session.persist(item);
}
session.flush();
session.clear();
// session.close(); throws exception
return Lists.newArrayList(items);
}
}

Related

How to fix LazyInitializationException in Java?

I work on a small project and I have 2 tables, User and Application. A user can have multiple applications and an application might be used by multiple user, so it is a many-to many relation between them. Each table has some fields(id, name, password, technologies etc) and I also declared 2 arraylists both in User and Application class with the #ManyToMany adnotation. Problem is that in my Business Layer i wrote a method which should add an application to a user and when I try to do user.getListOfApplications().add(app) it gives me that exception...
public class ManagerHibernate
{
private SessionFactory sessionFactory;
public void setup()
{
sessionFactory = new Configuration().configure().buildSessionFactory();
}
public void exit()
{
sessionFactory.close();
}
public void create(Object obj)
{
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(obj);
session.getTransaction().commit();
session.close();
}
public Object read(Class<?> c, int idObj)
{
Session session = sessionFactory.openSession();
session.beginTransaction();
Object obj = session.get(c, idObj);
System.out.println(obj);
session.getTransaction().commit();
session.close();
return obj;
}
public void update(Object obj)
{
Session session = sessionFactory.openSession();
session.beginTransaction();
session.update(obj);
session.getTransaction().commit();
session.close();
}
public void delete(Object obj)
{
Session session = sessionFactory.openSession();
session.beginTransaction();
session.delete(obj);
session.getTransaction().commit();
session.close();
}
public <T> List<T> loadAllData(Class<T> type)
{
Session session = sessionFactory.openSession();
session.beginTransaction();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(type);
criteria.from(type);
List<T> data = session.createQuery(criteria).getResultList();
session.getTransaction().commit();
session.close();
return data;
}
}
public Boolean addNewApplicationToUser(String userUserName, String applicationName)
{
int okUser = 0;
int okApp = 0;
listOfApplications = managerHibernate.loadAllData(Application.class);
listOfUsers = managerHibernate.loadAllData(User.class);
User user = null;
Application app = null;
for(Application index: listOfApplications)
{
if(index.getApplicationName().equals(applicationName))
{
okApp = 1;
app = index;
}
}
for(User index: listOfUsers)
{
if(index.getUserUserName().equals(userUserName))
{
okUser = 1;
user = index;
}
}
if(okUser == 0 || okApp == 0)
return false;
else
{
user.getListOfApplications().add(app);
//app.getUserList().add(user);
return true;
}
}
The method addNewApplicationToUser is written in another class called ControllerHibernate. Only the else branch is important, the rest is to check if the parameters do actually exist in the database
The issue starts when you are loading data with the following method managerHibernate.loadAllData
public <T> List<T> loadAllData(Class<T> type)
{
// New session was opened here
Session session = sessionFactory.openSession();
session.beginTransaction();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(type);
criteria.from(type);
List<T> data = session.createQuery(criteria).getResultList();
session.getTransaction().commit();
session.close();
//session is close here
return data;
}
So when you are loading data the hibernate framework will only load the user object. Since you have opted to use lazy loading in your model class the application values will be loaded only when your try to access the list. Since you have already closed your session the framework can no longer get the application list resulting in lazy loading exception.
listOfApplications = managerHibernate.loadAllData(Application.class);
//loading user data and close the session associated with it
listOfUsers = managerHibernate.loadAllData(User.class);
User user = null;
Application app = null;
for(Application index: listOfApplications)
{
if(index.getApplicationName().equals(applicationName))
{
okApp = 1;
app = index;
}
}
for(User index: listOfUsers)
{
if(index.getUserUserName().equals(userUserName))
{
okUser = 1;
user = index;
}
}
if(okUser == 0 || okApp == 0)
return false;
else
{
// when you run this line the hibernate framework will try to retrieve the application data.Since you have the closed session lazy load exception occurs
user.getListOfApplications().add(app);
return true;
}
Ways to overcome this issue
1) Try to keep your session open so that the application data is can be fetched by your framework
2) Change lazy loading to eager loading in your model pojo class (Since you are using many to many relationship not advisable to use this way)
since there is no transaction for fetching the lazy listofApplication in user you need to fetch it first. in order to do so you can change loadAllData as follow :
public interface CriteriaSpec
{
public void joinFetch(CriteriaBuilder builder, CriteriaQuery criteria, Root root);
}
public <T> List<T> loadAllData(Class<T> type, Optional<CriteriaSpec> spec)
{
Session session = sessionFactory.openSession();
session.beginTransaction();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(type);
Root root = criteria.from(type);
if(spec.isPresent())
spec.joinFetch(builder, criteria, root);
List<T> data = session.createQuery(criteria).getResultList();
session.getTransaction().commit();
session.close();
return data;
}
then use it :
managerHibernate.loadAllData(Application.class, Optional.empty());
listOfUsers = managerHibernate.loadAllData(User.class, (rootEntity, query,
criteriaBuilder) -> {
rootEntity.fetch("listOfApplications", JoinType.Left_OUTER_JOIN);
});

Why Hibernate session object's delete method is not working?

Why session object's delete method is not working in GenericDAOImpl.java, neither its giving any exception nor its showing any output. All other methods working fine expect public void delete(T object), Please help me, Sorry if i asked this question in wrong way.
public class GenericDAOImpl<T> implements IGenericDAO<T> {
private SessionFactory sessionFactory;
public GenericDAOImpl(Class<T> cl, SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
if (sessionFactory == null)
throw new RuntimeException("Session factory is null!!!");
}
#Override
public T get(Class<T> cl, Long id) {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
#SuppressWarnings("unchecked")
T element = (T) session.get(cl, id);
session.getTransaction().commit();
return element;
}
#Override
public T get(Class<T> cl, Serializable obj) {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
#SuppressWarnings("unchecked")
T element = (T) session.get(cl, obj);
session.getTransaction().commit();
return element;
}
#Override
public T save(T object) {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
session.save(object);
session.getTransaction().commit();
return object;
}
#Override
public void update(T object) {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
session.update(object);
session.getTransaction().commit();
}
#Override
public void delete(T object) {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
session.delete(object);
session.getTransaction().commit();
}
#SuppressWarnings("unchecked")
#Override
public T findUniqueByQuery(String hsql, Map<String, Object> params) {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Query query = session.createQuery(hsql);
if (params != null) {
for (String i : params.keySet()) {
query.setParameter(i, params.get(i));
}
}
return (T) query.uniqueResult();
}
#SuppressWarnings("unchecked")
#Override
public List<T> query(String hsql, Map<String, Object> params) {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Query query = session.createQuery(hsql);
if (params != null) {
for (String i : params.keySet()) {
query.setParameter(i, params.get(i));
}
}
List<T> result = null;
if ((hsql.toUpperCase().indexOf("DELETE") == -1)
&& (hsql.toUpperCase().indexOf("UPDATE") == -1)
&& (hsql.toUpperCase().indexOf("INSERT") == -1)) {
result = query.list();
} else {
}
session.getTransaction().commit();
return result;
}
}
As investigated in comments, you are facing
org.hibernate.TransactionException: nested transactions not supported exception
This is happening because you began transaction and never committed or rollbacked upon an exception.
I can see one of it's case in your code. See your code below
#SuppressWarnings("unchecked")
#Override
public T findUniqueByQuery(String hsql, Map<String, Object> params) {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Query query = session.createQuery(hsql);
if (params != null) {
for (String i : params.keySet()) {
query.setParameter(i, params.get(i));
}
}
return (T) query.uniqueResult();
}
See, you began and never committed a transaction. Like wise check all other places in your project.
I have the same problem. Although I was not using transaction at all. I was using namedQuery like this :
Query query = session.getNamedQuery(EmployeeNQ.DELETE_EMPLOYEES);
int rows = query.executeUpdate();
session.close();
It was returning 2 rows but the database still had all the records. Then wrap up the above code with this :
Transaction transaction = session.beginTransaction();
Query query = session.getNamedQuery(EmployeeNQ.DELETE_EMPLOYEES);
int rows = query.executeUpdate();
transaction.commit();
session.close();
Then it started working fine. I was using SQL server. But I think if we use h2 above code (without transaction) will also work fine.
org.hibernate.TransactionException: nested transactions not supported exception
Most probably you're not closing your session after an update or insert, and then you're doing the delete.

Update multiple rows using hibernate Criteria

I am trying to run an update query which would look like this in sql:
update studentMaster set sess_status = 'G' where ACADEM_YEAR = COURSE_YEAR;
I am trying to re-create the query using Criteria like this:
public void updateSessionStatus() {
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
Criteria crit = sess.createCriteria(CollegeStudentsMaster.class);
crit.add(Restrictions.eqProperty("academicYear", "courseYears"));
CollegeStudentsMaster e = (CollegeStudentsMaster) crit.uniqueResult();
e.setSessionStatus("G");
sess.saveOrUpdate(e);
tx.commit();
} catch (HibernateException asd) {
if (tx != null) {
tx.rollback();
}
log.debug(asd.getMessage());
} finally {
sess.close();
}
}
This is not working because the rows which meet this Criteria are many, my unique result is the problem here I guess.
How can I convert this into an update for all the rows that meet the Criteria. I do not want to use HQL query, I am rather doing it with Criteria.
public void updateSessionStatus() {
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
Criteria crit = sess.createCriteria(CollegeStudentsMaster.class);
crit.add(Restrictions.eqProperty("academicYear", "courseYears"));
// Here is updated code
ScrollableResults items = crit.scroll();
int count=0;
while ( items.next() ) {
CollegeStudentsMaster e = (CollegeStudentsMaster)items.get(0);
e.setSessionStatus("G");
sess.saveOrUpdate(e);
if ( ++count % 100 == 0 ) {
sess.flush();
sess.clear();
}
}
tx.commit();
} catch (HibernateException asd) {
if (tx != null) {
tx.rollback();
}
log.debug(asd.getMessage());
} finally {
sess.close();
}
}
It is always suggested that execute bulk operations very close to database and we do not need keep updated object in session unless they are required, Hence try to avoid load objects in session while executing bulk operations.

list() method from hibernate

While developing the backend for a web app, we used hibernate to handle the database. So, while testing, we had a test failing.
package com.app.db.hibernate.test;
import java.util.HashMap;
import com.app.db.hibernate.UserManager;
import com.app.db.utils.TestUtils;
import org.junit.*;
import static org.junit.Assert.*;
public class HibernateUserTest {
public final UserManager um = new UserManager();
public Integer id;
public HashMap<String, Object> userParams;
#Before
public void setup(){
this.userParams = TestUtils.loadParams();
this.id = this.um.agregar(this.userParams);
}
#Test
public void deleteUserTest(){
//Se elimina el usuario de la db:
um.deleteUser(userParams);
//Se comprueba que no esta:
Integer id_ = um.search(userParams);
assertNull(id_);
}
#After
public void cleanUp(){
um.reset();
}
}
So here we add a user to the database, call the deleteUser() method, and check if it works (search(userParams) should return null). The thing is that the test fails since, even though um.deleteUser(userParams) deletes the dummy user from the database, um.search(userParams) still "finds" it (by returning the id of the user just deleted, I checked that by debugging the code), I do not know from where, thus failing. Here's the code for delete() and search():
public void deleteUser(HashMap<String, Object> params){
Session sesion = sessionFactory.openSession();
try{
sesion.beginTransaction();
User u = (User)sesion.get(User.class, search(params));
sesion.delete(u);
sesion.getTransaction().commit();
}catch(HibernateException he){
he.printStackTrace();
}finally{sesion.close();}
}
public Integer search(HashMap<String, Object> params) {
Session sesion = sessionFactory.openSession();
Integer id = null;
try{
List lista = sesion.createCriteria(User.class).add(
Restrictions.eq("name",(String)params.get("name"))).list();
if(lista.size() == 1){id = ((User)lista.get(0)).getId();}
}catch(HibernateException he){
he.printStackTrace();
}finally{sesion.close();}
return id;
}
reset() just cleans the users table:
public void reset() {
Session session = sessionFactory.openSession();
session.beginTransaction();
Query q = session.createQuery("delete from User");
q.executeUpdate();
session.getTransaction().commit();
}
So, my question is: Is there something i'm missing here to make the test pass? Thanks in advance.
well, the trick was to add some lines, to get the transaction from the session, and commiting, to go from this:
public Integer search(HashMap<String, Object> params) {
Session sesion = sessionFactory.openSession();
Integer id = null;
try{
List lista = sesion.createCriteria(User.class).add(
Restrictions.eq("name",(String)params.get("name"))).list();
if(lista.size() == 1){id = ((User)lista.get(0)).getId();}
}catch(HibernateException he){
he.printStackTrace();
}finally{sesion.close();}
return id;
}
to this:
public Integer search(HashMap<String, Object> params) {
Session sesion = sessionFactory.openSession();
Integer id = null;
//get the transaction object:
Transaction tx = sesion.beginTransaction();
try{
//a new restriction added to the search:
Criteria criteria = sesion.createCriteria(User.class);
criteria.add(Restrictions.eq("name", (String)params.get("name")));
criteria.add(Restrictions.eq("email", (String)params.get("email")));
List lista = criteria.list();
//commit:
tx.commit();
//only one should be found, this is yet to be implemented:
if(lista.size() == 1){id = ((User)lista.get(0)).getId();}
}catch(HibernateException he){
//in case of an exception thrown while committing, roll back transaction:
tx.rollback();
he.printStackTrace();
}finally{sesion.close();}
return id;
}
That made the test pass :)

Hibernate PersistenceContext session Flush

i want to know when does hibernate fulshes the context session when i call session= session.getCurrentSession()
The thing is i have 2 methods in my dao calling getCurrentSession(), when i process the update making the call to getCurrentSession() the entitys are empty:
SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];...)
How can i make this entitys persist from the select method to the update method?
Here are my methods:
public List<SystemConfiguration> getListConfigurations() {
List<SystemConfiguration> lista = new ArrayList<SystemConfiguration>();
Session session = null;
Query query = null;
String sql = "from SystemConfiguration where description = :desc";
try {
/* BEFORE
session = SessionFactoryUtil.getInstance().getCurrentSession();
#SuppressWarnings("unused")
Transaction ta = session.beginTransaction(); */
//FOLLOWING LINE SOLVED THE PROBLEM
session = SessionFactoryUtil.getInstance().openSession();
query = session.createQuery(sql);
query.setString("desc", "configuracion");
lista = query.list();
return lista;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void updateConfigurations(List<SystemConfiguration> configs) throws Exception{
Session sess = null;
Transaction tx = null;
try {
//BEFORE
//sess = SessionFactoryUtil.getInstance().getCurrentSession();
//FOLLOWING LINE SOLVED THE PROBLEM
sess = SessionFactoryUtil.getInstance().openSession(new SystemConfigurationInterceptor());
tx = sess.beginTransaction();
for (SystemConfiguration sys : configs) {
sess.update(sys);
}
tx.commit();
} // try
catch (Exception e) {
e.printStackTrace();
if (tx != null && tx.isActive()) {
tx.rollback();
} // if
throw e;
}
}
And this is my interceptor:
public class SystemConfigurationInterceptor extends EmptyInterceptor {
private int updates;
private int creates;
private int loads;
public void onDelete(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
// do nothing
}
// This method is called when Entity object gets updated.
public boolean onFlushDirty(Object entity,
Serializable id,
Object[] currentState,
Object[] previousState,
String[] propertyNames,
Type[] types) {
if ( entity instanceof SystemConfiguration ) {
updates++;
for ( int i=0; i < propertyNames.length; i++ ) {
if ( "updated_at".equals( propertyNames[i] ) ) {
currentState[i] = new Timestamp(Calendar.getInstance().getTime().getTime());
return true;
}
}
}
return false;
}
public boolean onLoad(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
if ( entity instanceof SystemConfiguration ) {
loads++;
}
return false;
}
// This method is called when Entity object gets created.
public boolean onSave(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
if ( entity instanceof SystemConfiguration ) {
creates++;
for ( int i=0; i<propertyNames.length; i++ ) {
if ( "updated_at".equals( propertyNames[i] ) ) {
state[i] = new Timestamp(Calendar.getInstance().getTime().getTime());
return true;
}
}
}
return false;
}
public void afterTransactionCompletion(Transaction tx) {
if ( tx.wasCommitted() ) {
System.out.println("Creations: " + creates + ", Updates: " + updates +", Loads: " + loads);
}
updates=0;
creates=0;
loads=0;
}
Hibernate will flush when you tell it to and when the current transaction is "closed" (usually when the DB connection is returned to the pool somehow).
So the answer to your question depends on which framework you use. With Spring, the session is flushed when the outermost #Transactional method returns.
Your "solution" above will not work for long since it never closes the session. While it returns a result, it will leak a database connection so after a few calls, you will run out of connections.
Also your question doesn't really make sense. SELECT doesn't change objects, so they don't need to be "persisted" before you change them.
After changing them in updateConfigurations(), Hibernate can chose not to write them into the database immediately and just update the cache.
Eventually, if you configured everything correctly, Spring will commit the transaction and that will flush the cache. But when you use Spring, you should never create open and close sessions because it will mess with what Spring is doing.

Categories