Some clarification about Spring @Transactional annotation on a method

71,542

Solution 1

First of all, you shouldn't make DAO methods transactional, but service methods.

Second, using Transactional is a way to let Spring start and commit/rollback transactions for you. So you shouldn't start and commit transactions yourself.

Third: this will only work if you use a transaction manager that knows how to associate a Hibernate session with the transaction (typically, a HibernateTransactionManager). The session factory should also be handled by Spring, and injected by Spring in your DAOs. The code of the DAO should look like this:

Fourth: you should not open a new session, but get the current one, associated to the current transaction by Spring.

public class PersonDAOImpl implements PersonDAO {

    @Autowired
    private SessionFactory sessionFactory;

    public Integer addPerson(Person p) {
        Session session = sessionFactory.getCurrentSession();
        Integer personID = (Integer) session.save(p);
        return personID;
    }

    public Person getById(int id) {
        Session session = sessionFactory.getCurrentSession();
        Person retrievedPerson = (Person) session.get(Person.class, id);
        return retrievedPerson;
    }

    @SuppressWarnings("unchecked")
    public List<Person> getPersonsList() {
        Session session = sessionFactory.getCurrentSession();
        Criteria criteria = session.createCriteria(Person.class);
        return criteria.list();
    }

    public void delete(int id) {
        Session session = sessionFactory.getCurrentSession();
        Person personToDelete = getById(id);
        session.delete(personToDelete);
    }

    public void update(Person personToUpdate) {
        Session session = sessionFactory.getCurrentSession();
        session.update(personToUpdate);
    }
}

Read the documentation for more information.

Solution 2

@Transactional is used upon the method.

We declare on method level first it opens the transaction, perform the operation and close the transaction.

If an operation is failed it will be rollback, if an operation is a success it will automatically be committed

This is about @Transactional annotation finally&short.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        ">

    <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans -->
    <context:component-scan base-package="hu.daniel.hari.learn.spring" />
    <!-- Activates various annotations to be detected in bean classes e.g: @Autowired -->
    <context:annotation-config />


    <!-- creating the internal datasource object -->

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url" value="jdbc:hsqldb:mem://productDb" />
        <property name="username" value="sa" />
        <property name="password" value="" />
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
            p:packagesToScan="hu.daniel.hari.learn.spring.orm.model"
            p:dataSource-ref="dataSource"
            >
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="true" />
                <property name="showSql" value="true" />

            </bean>
        </property>
    </bean>

    <!-- Transactions -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager" />

</beans>
package hu.daniel.hari.learn.spring.orm.main;

import hu.daniel.hari.learn.spring.orm.model.Product;
import hu.daniel.hari.learn.spring.orm.service.ProductService;

import java.util.Arrays;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.DataAccessException;

public class SpringOrmMain {

    public static void main(String[] args) {

        //Create Spring application context
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/spring.xml");

        //Get service from context. (service's dependency (ProductDAO) is autowired in ProductService)
        ProductService productService = ctx.getBean(ProductService.class);

        //Do some data operation

        productService.add(new Product(1, "Bulb"));
        productService.add(new Product(2, "Dijone mustard"));

        System.out.println("listAll: " + productService.listAll());

        //Test transaction rollback (duplicated key)

        try {
            productService.addAll(Arrays.asList(new Product(3, "Book"), new Product(4, "Soap"), new Product(1, "Computer")));
        } catch (DataAccessException dataAccessException) {
        }

        //Test element list after rollback
        System.out.println("listAll: " + productService.listAll());

        ctx.close();
    }
}
Share:
71,542
AndreaNobili
Author by

AndreaNobili

Updated on July 09, 2022

Comments

  • AndreaNobili
    AndreaNobili almost 2 years

    I am quite new in Spring world and I have developed a simple project that use Spring 3.2.1 and Hibernate 4.1.9 to implement a DAO. The project work correctly but I have some doubts about the use of @Transactional Spring annotation on CRUD method of this DAO.

    This is the entire code of the class that implement the CRUD operation of my project:

    package org.andrea.myexample.HibernateOnSpring.dao;
    
    import java.util.List;
    
    import org.andrea.myexample.HibernateOnSpring.entity.Person;
    
    import org.hibernate.Criteria;
    import org.hibernate.HibernateException;
    import org.hibernate.Session;
    import org.hibernate.Transaction;
    import org.hibernate.SessionFactory;
    import org.hibernate.cfg.Configuration;
    import org.hibernate.service.ServiceRegistry;
    import org.hibernate.service.ServiceRegistryBuilder;
    import org.springframework.transaction.annotation.Transactional;
    
    public class PersonDAOImpl implements PersonDAO {
    
        // Factory per la creazione delle sessioni di Hibernate:
        private static SessionFactory sessionFactory;
    
        // Metodo Setter per l'iniezione della dipendenza della SessionFactory:
        public void setSessionFactory(SessionFactory sessionFactory) {
            this.sessionFactory = sessionFactory;
        }
    
        /** CREATE CRUD Operation:
         * Aggiunge un nuovo record rappresentato nella tabella rappresentato
         * da un oggetto Person
         */
        @Transactional(readOnly = false)
        public Integer addPerson(Person p) {
    
            System.out.println("Inside addPerson()");
    
            Session session = sessionFactory.openSession();
    
            Transaction tx = null;
            Integer personID = null;
    
            try {
                tx = session.beginTransaction();
    
                personID = (Integer) session.save(p);
                tx.commit();
            } catch (HibernateException e) {
                if (tx != null)
                    tx.rollback();
                e.printStackTrace();
            } finally {
                session.close();
            }
    
            return personID;
    
        }
    
        // READ CRUD Operation (legge un singolo record avente uno specifico id):
        public Person getById(int id) {
    
            System.out.println("Inside getById()");
    
            Session session = sessionFactory.openSession();
    
            Transaction tx = null;          
            Person retrievedPerson = null;  
    
            try {
                tx = session.beginTransaction();
                retrievedPerson = (Person) session.get(Person.class, id);
                tx.commit();
            }catch (HibernateException e) { 
                if (tx != null)                 
                    tx.rollback();          
                e.printStackTrace();
            } finally {                 
                session.close();
            }
    
            return retrievedPerson;
        }
    
        // READ CRUD Operation (recupera la lista di tutti i record nella tabella):
        @SuppressWarnings("unchecked")
        public List<Person> getPersonsList() {
    
            System.out.println("Inside getPersonsList()");
    
            Session session = sessionFactory.openSession();
            Transaction tx = null;
            List<Person> personList = null;
    
            try {
                tx = session.beginTransaction();
                Criteria criteria = session.createCriteria(Person.class);
                personList = criteria.list();
                System.out.println("personList: " + personList);
                tx.commit();
            }catch (HibernateException e) { 
                if (tx != null)                 
                    tx.rollback();          
                e.printStackTrace();
            } finally {
                session.close();
            }
            return personList;
        }
    
        // DELETE CRUD Operation (elimina un singolo record avente uno specifico id):
        public void delete(int id) {
    
            System.out.println("Inside delete()");
    
            Session session = sessionFactory.openSession();
            Transaction tx = null;
    
            try {
                tx = session.beginTransaction();
                Person personToDelete = getById(id);
                session.delete(personToDelete);
                tx.commit();
            }catch (HibernateException e) { 
                if (tx != null)                 
                    tx.rollback();          
                e.printStackTrace();
            } finally {
                session.close();
            }
    
        }
    
        @Transactional
        public void update(Person personToUpdate) {
    
            System.out.println("Inside update()");
    
            Session session = sessionFactory.openSession();
            Transaction tx = null;
    
            try {
                System.out.println("Insite update() method try");
                tx = session.beginTransaction();
                session.update(personToUpdate);
    
                tx.commit();
            }catch (HibernateException e) { 
                if (tx != null)                 
                    tx.rollback();          
                e.printStackTrace();
            } finally {
                session.close();
            }   
    
        }
    
    }
    

    Ok,as you can see some methods are annoted using @Transactional annotation.

    I am readin the official documentation here http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/transaction.html about the use of this annotation on methods and it see that: A method annoted using @Transactional must have transactional semantics but what it means with transactional semantics?

    It means that the methos execution has to be considered as the execution of a transaction? So it means that the method operations have to be considered as a single operation that which may lead to a success or a failure, if successful, the results of operations has to be permanent, whereas in case of failure to return to the state prior to the start of the transaction.

    Is this the meaning of use @Transactional annotation on a method?

    And what exactly mean the readOnly = false attribute in the @Transactional annotation of the addPerson() method? it mean that I can also write a record in the database (and not only read it) or what? The doubt is related because I have understand that, by default, a transaction definied using @Transactional annotaion is read/write and not just read... I have also try to delete the (readOnly = false) attribute and still work well (insert the new record in the database table)

    The following dout is: "why some method are annoted using @Transactional annotation and some other methods not? is it a good pratcice to annote ALL CRUD method withd @Transactional?"

    Tnx

    Andrea

    • Ortwin Angermeier
      Ortwin Angermeier about 11 years
      I guess your transaction configuration is not valid, since you can insert something with a read only transaction. My guess is that you do not use transactions at all. Please give us some more detail how you configured your transaction environment(appcontext). Also do not declare your transactions on the DAO level, but on the business level(there where you actually use the DAOs).
  • AndreaNobili
    AndreaNobili about 11 years
    Ok...now I have more doubt than before post my question :-) 1) What do you mean that I shouldn't make DAO methods transactional, but service methods? I have find many example where the DAO method are annoted using @Transaction annotation 2) Why do you have delete this annotation from the method posted in your answer 3) Reading here: tutorialspoint.com/hibernate/hibernate_sessions.htm it say that:"the Session object is lightweight and designed to be instantiated each time an interaction is needed with the database"
  • AndreaNobili
    AndreaNobili about 11 years
    and also: "The session objects should not be kept open for a long time because they are not usually thread safe and they should be created and destroyed them as needed." So what you say me that is good pratice open the session only one time and then get the current open session?
  • JB Nizet
    JB Nizet about 11 years
    I'm not saying that. I'm saying that you should get the session that has been associated to the current transaction by Spring, and that will also be closed by Spring at the end of the transaction. It's Spring which will open and close a session each time a transaction is opened/closed. What I mean with service vs DAO methods is that a service will typically call several DAO methods, and all these calls should be part of the same trnsaction (for example: decrement the balance of an account, increment the balance of another account, create a transfer object, create aline in an audit table.
  • JB Nizet
    JB Nizet about 11 years
    (continued): all this should be done in a single transaction, which should start when the service method transferMoney() is called. So the Transactional annotation should be on this transferMoney() method, and not on the DAO methods it calls internally. This is explained in the Spring documentation, BTW.
  • AndreaNobili
    AndreaNobili about 11 years
    Ok, now the question about how get the current session is clear for me. I will provide to study the documentation section related to the service tha call DAO method...only a last question about it: the fact that call methods of the DAO is prohibited (or in simple word it is very bad) or simply using the service create a better architecture?
  • Arun
    Arun over 7 years
    can we use the ' Session session = sessionFactory.getCurrentSession()' as a class variable? If not why??
  • JB Nizet
    JB Nizet over 7 years
    No, otherwise you wouldn't get the current session, i.e. the session assoiated to the current transaction.
  • Vineeth Bhaskaran
    Vineeth Bhaskaran over 7 years
    In distributed environment If one thread is using getCurrentSession() at the same time another thread is trying to make a transaction by using getCurrentSession() how spring will handle the situation(thread Safety)?
  • JB Nizet
    JB Nizet over 7 years
    @VineethBhaskaran getCurrentSession() returns the session bound to the current transaction, which itself is bound to the current thread.
  • Aniket Kulkarni
    Aniket Kulkarni over 6 years
    @JBNizet: Thank you very much for wonderful explaination. One question: How spring handles Rollback and Commit in case of @Transactional? Please provide some inputs.