关于方法上的 Spring @Transactional 注释的一些说明

Posted

技术标签:

【中文标题】关于方法上的 Spring @Transactional 注释的一些说明【英文标题】:Some clarification about Spring @Transactional annotation on a method 【发布时间】:2013-02-24 09:07:51 【问题描述】:

我是 Spring 世界的新手,我开发了一个使用 Spring 3.2.1 和 Hibernate 4.1.9 来实现 DAO 的简单项目。该项目工作正常,但我对在此 DAO 的 CRUD 方法上使用 @Transactional Spring 注释有一些疑问。

这是实现我项目的CRUD操作的类的全部代码:

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();
           

    


好的,你可以看到一些方法是使用@Transactional注解来注解的。

我在这里阅读了http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/transaction.html 的官方文档,关于在方法上使用这个注释,它看到:使用@Transactional 注释的方法必须具有事务语义,但它对事务语义意味着什么?

意思是methos的执行必须被认为是事务的执行?所以这意味着方法操作必须被视为可能导致成功或失败的单个操作,如果成功,则操作结果必须是永久的,而如果失败则返回到之前的状态交易的开始。

这就是在方法上使用@Transactional注解的意思吗?

addPerson() 方法的@Transactional 注释中的readOnly = false 属性究竟是什么意思?这意味着我也可以在数据库中写入一条记录(而不仅仅是读取它)还是什么?这个疑问是相关的,因为我知道,默认情况下,使用 @Transactional 注释定义的事务是 read/write 而不仅仅是读取... 我也尝试删除 (readOnly = false) 属性并且仍然可以正常工作(在数据库表中插入新记录)

下面的问题是:“为什么有些方法使用@Transactional 注释而其他一些方法没有?用@Transactional 注释所有CRUD 方法是一个好习惯吗?”

Tnx

安德烈亚

【问题讨论】:

我猜您的事务配置无效,因为您可以插入带有只读事务的内容。我的猜测是你根本不使用交易。请向我们提供更多详细信息,您是如何配置交易环境(appcontext)的。也不要在 DAO 级别声明您的交易,而是在业务级别(您实际使用 DAO 的地方)。 【参考方案1】:

首先,您不应该使 DAO 方法具有事务性,而应使方法具有服务性。

其次,使用 Transactional 是一种让 Spring 为您启动和提交/回滚事务的方法。所以你不应该自己开始和提交事务。

第三:这仅在您使用知道如何将 Hibernate 会话与事务关联的事务管理器(通常是HibernateTransactionManager)时才有效。会话工厂也应该由 Spring 处理,并由 Spring 在您的 DAO 中注入。 DAO 的代码应如下所示:

第四:你不应该打开一个新的会话,而是获取当前会话,通过 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);
    

阅读the documentation了解更多信息。

【讨论】:

好的...现在我比以前有更多的疑问发布我的问题:-) 1) 你的意思是我不应该让 DAO 方法具有事务性,而是服务方法?我发现了很多使用@Transaction 注释对DAO 方法进行注释的示例2)为什么要从答案中发布的方法中删除此注释3)在此处阅读:tutorialspoint.com/hibernate/hibernate_sessions.htm 它说:“Session 对象是轻量级的,并且设计为在每次需要与数据库交互时实例化” 还有:“会话对象不应长时间保持打开状态,因为它们通常不是线程安全的,应根据需要创建和销毁它们。”那么你说我的好习惯是只打开一次会话然后获取当前打开的会话? 我不是这么说的。我的意思是,您应该通过 Spring 获取与当前事务关联的会话,并且该会话也将在事务结束时由 Spring 关闭。每次打开/关闭事务时,Spring 都会打开和关闭一个会话。我的意思是服务与 DAO 方法是一个服务通常会调用几个 DAO 方法,所有这些调用都应该是同一个 trnsaction 的一部分(例如:减少一个帐户的余额,增加另一个帐户的余额,创建一个转移对象,在审计表中创建一行。 (续):所有这些都应在单个事务中完成,该事务应在调用服务方法transferMoney() 时开始。所以Transactional注解应该在这个transferMoney()方法上,而不是在它内部调用的DAO方法上。这在 Spring 文档中进行了解释,顺便说一句。 好的,现在关于如何获取当前会话的问题对我来说很清楚了。我将提供研究与调用 DAO 方法的服务相关的文档部分......只有最后一个问题:禁止调用 DAO 方法的事实(或者简单地说它非常糟糕)或者只是使用服务创建更好的架构?【参考方案2】:

@Transactional 用于方法。

我们首先在方法级别声明它打开事务,执行操作并关闭事务。

如果操作失败则回滚,如果操作成功则自动提交

这是关于@Transactional注解 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();
    

【讨论】:

以上是关于关于方法上的 Spring @Transactional 注释的一些说明的主要内容,如果未能解决你的问题,请参考以下文章

spring源码学习spring的事务管理源码学习

最新最全面的Spring详解——事务管理

Spring之004: jdbcTemplate基本使用Spring实物控制

使用服务和 DAO 测试事务传播

关于vue+Spring框架中,Javascript判断resp.data是否为空的问题

转载---关于Spring的69个面试问答