关于方法上的 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之004: jdbcTemplate基本使用Spring实物控制