-1

I am quite new in Spring world and I am trying to implement a simple Hibernate DAO but I have some doubts with delete and update operation

I am using Spring 3.2.1 and Hibernate 4.1.9 and MySQL database.

So in a MySQL database I have a table named person having the following structure:

mysql> describe person;
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| pid       | int(11)      | NO   | PRI | NULL    | auto_increment |
| firstname | varchar(255) | YES  |     | NULL    |                |
| lastname  | varchar(255) | YES  |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+

In my Spring application I have defined the following interface for my DAO that defined the required CRUD operation:

package org.andrea.myexample.HibernateOnSpring.dao;

import java.util.List;

import org.andrea.myexample.HibernateOnSpring.entity.Person;

public interface PersonDAO {

    public void addPerson(Person p);

    public Person getById(int id);

    public List<Person> getPersonsList();

    public void delete(int id);

    public void update(Person person);

}

Then I have implement this interface by the class PersonDAOImplement in this way:

package org.andrea.myexample.HibernateOnSpring.dao;

import java.util.List;

import org.andrea.myexample.HibernateOnSpring.entity.Person;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.transaction.annotation.Transactional;

public class PersonDAOImpl implements PersonDAO {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    // Metodo che inserisce un nuovo record nella tabella person
    @Transactional(readOnly = false)
    public void addPerson(Person p) {
        Session session = sessionFactory.openSession();
        session.save(p);
        session.close();
    }

    /*
     * Metodo che recupera un record, rappresentante una persona, avente uno
     * specifico id dalla tabella.
     * 
     * @param L'id univoco della persona
     */

    public Person getById(int id) {
        Session session = sessionFactory.openSession();
        try {
            return (Person) session.get(Person.class, id);
        } finally {
            session.close();
        }
    }

    /*
     * Metodo che recupera la lista di tutti le persone rappresentanti dalle
     * righe della tabella person
     */
    @SuppressWarnings("unchecked")
    public List<Person> getPersonsList() {

        Session session = sessionFactory.openSession();
        try {

            Criteria criteria = session.createCriteria(Person.class);
            return criteria.list();

        } finally {
            session.close();
        }

    }

    /*
     * Metodo che elimina dalla tabella person la riga avente uno specifico id
     * 
     * @param l'id della persona da eliminare dalla tabella person
     */
    @Transactional
    public void delete(int id) {
        Person personToDelete = getById(id);
        sessionFactory.getCurrentSession().delete(personToDelete);
        /*Session session = sessionFactory.openSession();

        try {
            Person personToDelete = getById(id);
            System.out.println("person to delete: " + personToDelete);
            session.delete(personToDelete);


        } finally {
            session.close();
        }
        */
    }

    @Transactional
    public void update(Person person){

        sessionFactory.getCurrentSession().update(person);
        /*
        Session session = sessionFactory.openSession();

        try {
            System.out.println("UPDATING");
            session.merge(person);

        } finally {
            System.out.println("CLOSE SESSION");
            session.close();
        }
        */
    }

}

This example seems to work fine (I have tested it using a main class containing a main method in which I execute the CRUD method to insert row in the table, query for a single row or a list of row, delete a row and update the value in a row)

The only thing that I find strange is that to work correctly in the delete and update method I have to get the current session from my sessionFactory object in this way:

sessionFactory.getCurrentSession().delete(personToDelete);
sessionFactory.getCurrentSession().update(person);

On the contrary when I have to add or query a row I have to open a new session, in this way:

Session session = sessionFactory.openSession();

Why?

In the previous PersonDAOImpl class I have commented the old implementation of delete and update method in wich I tried to open a new session (as I do without problem in the addPerson and query method) but in this way don't work...work fine only if I get the current session

Why? Is it correct this implementation of DAO object?

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
user2104160
  • 49
  • 1
  • 2
  • 3
  • If you are using Spring why not use it's transactional annotations (Repository / Service / Transactional) to do session management (and transaction management) for you? – skuntsel Feb 24 '13 at 12:26
  • I have use Transactional and SuppressWarnings("unchecked") annotation in my class...I have not used Repository because, in this time, I have configured the injection by xml file... However, unless you have to understand something wrong, I do not think that has to do with my question ... – user2104160 Feb 24 '13 at 12:34
  • 1
    It's possible question duplicate, http://stackoverflow.com/questions/15046622/problems-only-in-deleting-and-upgrading-table-rows-using-hibernate-on-spring-app – hekomobile Feb 24 '13 at 18:13
  • I extended my comment into the answer. – skuntsel Feb 25 '13 at 10:08

2 Answers2

1

A good place to start is to read the well-written Hibernate documentation on transactions. It is clearly stated that both SessionFactory.openSession() and SessionFactory.getCurrentSession() can be used to obtain session instances, but the do differ in the way the session is obtained.

With SessionFactory.openSession() you are forcing Hibernate to open a new session for your needs, which may lead to undesired/unexpected consequences. On the contrary, if SessionFactory.getCurrentSession() is used a Session is received from a current context, which may mean transaction, request, etc.

To understand it better, consider a following example: you have a service layer which needs to call methods from two DAO objects, like:

@Service
public class Service {

    @Autowired
    private FirstDao firstDao;
    @Autowired
    private SecondDao secondDao;

    @Transactional
    public void serviceMethod(int param) {
        FirstEntity firstEntity = firstDao.getFirstEntity(param);
        SecondEntity secondEntity = secondDao.updateSecondEntity(firstEntity);
    }

}

You have two options now: to have each of your DAOs implement their methods like Session session = factory.openSession(); or Session session = factory.getCurrentSession(). Your belief is that both method calls (firstDao.getFirstEntity and secondDao.updateSecondEntity) should use the same session, because, at the very least your firstEntity is in detached state after firstDao method call finishes (the session should be closed there), and you should reattach it to the new session to make the code work.

But when you call getCurrentSession in both DAO methods the same session instance will be returned (as it was opened beforehand by transaction management mechanism). In this case the Hibernate documentation clearly states that

You do not need to use the Hibernate Transaction API at all with BMT or CMT, and you get automatic propagation of the "current" Session bound to the transaction.

and all of your session management happens when you get a session instance in your DAO by calling Session session = factory.getCurrentSession(); and that's it. Another excerpt from the Hibernate documentation:

Your application code can access a "current session" to process the request by calling sessionFactory.getCurrentSession(). You will always get a Session scoped to the current database transaction (italics mine).

Mind that Declarative transaction demarcation by Spring belobgs to the service/business layer, from which you may need to call several DAOs that use the same Session object, that is bound to the current transaction context. The session is opened when and transaction is started when the method marked @Transactional is called and transaction is committed and the session is closed when the method is executed.

Of course that doesn't imply that session-per-transaction is the only method of implementing transaction management. There are other concepts like Open session in view, Session-per-conversation, etc. But in each case, your DAOs will get a Session object from the current context that is in turn managed by Container, or Framework, or by your written code.

Regarding your code, I would suggest to fix the following inconsistencies:

  • remove all @Transactional annotations from DAO layer implementations
  • use only sessionFactory.getCurrentSession() to manipulate with the data
  • go for a service layer with transaction demarcation in mind: your methods there should me marked as @Transactional and this way Spring will manage sessions and transactions for you (otherwise look for programmatic transaction demarcation in Hibernate documentation.

Related material:

Community
  • 1
  • 1
skuntsel
  • 11,624
  • 11
  • 44
  • 67
0

I will give some hints but you need to dig into source code and documentation.There are lot of concepts here.

Doing some operation on session does not mean that the changes are immediately pushed to db. This depends a lot on the flush mode of the session and operation you are doing. Refer to hibernate documentation on this. Changes are pushed to db when session is flushed. The changes are durable once transaction is committed.

It would have worked if you called session.flush() before close, in the commented code but it is unnecessary because of explanation below.

In the above code for update and delete, when call 'getCurrentSession', you either get the existing session or get the new session from org.springframework.orm.hibernate4.SpringJtaSessionContext(assuming you are using JTA, again dig into code to understand this).

When new session is created Hibernate register synchronization with the transaction. Before complete of the transaction Hibernate will get call back. Here Hibernate will flush the session. In your case the transactions demarcated by spring through the annotations you placed.

I advice you to debug the code from call to 'getCurrentSession' in order t understand what is happening. You will come across these classes/methods depending on your configuration.

 org.hibernate.internal.SessionFactoryImpl.getCurrentSession()
 org.springframework.orm.hibernate4.SpringJtaSessionContext.buildOrObtainSession()
 org.hibernate.engine.transaction.synchronization.internal.RegisteredSynchronization
 org.hibernate.engine.transaction.synchronization.internal.SynchronizationCallbackCoordinatorImpl.beforeCompletion()
Adisesha
  • 5,200
  • 1
  • 32
  • 43