使用Spring MVC 3与Hibernate (Spring ORM)的正确方式

8

我正在开始一个新项目,这次想做得更好(所以有多个问题),我可能需要一些帮助,不确定我做错了什么:

  1. Spring上下文
  2. 控制器
  3. 服务接口
  4. 服务实现
  5. DAO接口
  6. DAO实现

我想尽可能利用Spring MVC,如何通过 @Transactional 处理会话的开启和关闭?

如果有任何异常(例如不存在的记录或数据库失败),我该如何捕获它们?例如,我的数据库不接受重复的条目,像这样:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry

我该如何捕获这个错误?

每次发出下一个请求时,我都会收到此异常:

org.hibernate.AssertionFailure: null id in com.test.spring.ws.service.impl.TestObject entry (don't flush the Session after an exception occurs)

我做错了什么?有谁能建议我在项目中作出一些改进吗?

这不是一个问题,而是5或10个问题。抱歉,投票关闭。 - Sean Patrick Floyd
4个回答

12

组件扫描

首先要明确的是:你正在使用 @Controller、@Service、@Repository 和 @Autowired,但却没有使用它们。我建议使用 类路径扫描。从 Spring 上下文文件中删除 "testServiceDAO" 和 "testService" beans,改为使用以下内容:

<context:component-scan base-package="com.test.spring.ws"/>

这将通过它们的注释来查找和创建这些bean,而不需要您在XML中声明它们。在您的服务中将testServiceDAO字段和DAO中的sessionFactory字段添加@Autowired注释。移除这些字段的setter方法,它们不再需要了。component-scan标签也会为您执行自动装配。要使用context命名空间,您需要将其添加到根元素中。例如:

<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context="http://www.springframework.org/schema/context"
     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">

事务管理

如Sean所说,要使用@Transactional,您需要向Spring上下文文件中添加一个元素:

<tx:annotation-driven/>

由于您的事务管理器bean的名称为"transactionManager",因此它会自动找到它。您还需要将"tx"命名空间添加到根元素中,因此应该类似于:

<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context="http://www.springframework.org/schema/context"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
         http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/tx 
         http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-3.0.xsd">
为了让代码正常运行,你需要从DAO方法中删除session.beginTransaction()session.close()。以这种方式打开自己的事务是混合编程和声明性事务划分,而声明性方式通常更好。此外,在真实项目中,您不应该在DAO中关闭会话,这将导致各种问题。
异常处理:
由于MySQLIntegrityConstraintViolationException是特定于数据库的异常,因此Hibernate会捕获并包装为ConstraintViolationException,它是DAO返回的结果。但是,由于DAO现在是@Repository,所以您可以受益于Spring的异常转换。使用此选项,Spring将捕获Hibernate异常并将其翻译为DataIntegrityViolationException。数据库异常处理总是有趣!
会话管理:
您是否正在使用OpenSessionInViewFilterOpenSessionInViewInterceptor?如果是,则Hibernate会话在首次收到请求时打开,并在响应编写后关闭。否则,会话不会开始,直到事务开始(在@Transactional方法中),并在该事务完成时关闭。使用过滤器/拦截器时,您可以在“视图”层中执行需要回调数据库的操作,特别是当您需要为呈现视图而需要惰性关系或惰性加载对象时。如果会话不可用-如果它仅存在于事务服务方法的长度中,则无法在视图中执行这些操作,您将收到臭名昭着的LazyInitializationException
至于您遇到的“在异常发生后不要刷新会话”错误,我没有立即看到任何可能导致这种情况发生的原因。也许您Web层Spring上下文中的某些配置不正确,或者在DAO中直接处理事务和会话的方式存在某些奇怪的相互作用。

我认为“在异常发生后不要刷新Session”是因为他在DAO中关闭了Session。 - Nathan Hughes
我考虑过这个问题,甚至稍微查看了一下SessionImpl的源代码,但我相当确定close()不会导致flush()。我可能是错的。如果有人能证明它,我会更新答案。 - Ryan Stewart
1
请参阅http://docs.jboss.org/hibernate/core/3.3/reference/en/html/transactions.html#transactions-basics-issues,其中描述了“Hibernate引发的异常意味着您必须回滚数据库事务并立即关闭会话。”一旦出现此异常,您的会话将无法继续。 - Will Iverson

2
如何让会话的打开和关闭由 @Transactional 处理?
您需要在 Spring 上下文中使用 <tx:annotation-driven />(以及 tx 命名空间)。
(参见 使用 @Transactional

1
我建议通过扩展 HibernateDaoSupport 并使用 HibernateTemplate 来代替在您的 DAO 代码中显式地使用 SessionFactory (和创建事务)。

1
那是老派的做法。当前最佳实践与他编写的DAO差不多:注入一个SessionFactory并使用getCurrentSession()。 - Ryan Stewart
2
在Spring 3.x中,不再建议使用HibernateTemplate等。请参阅http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/orm.html#orm-hibernate-straight - Sean Patrick Floyd
有趣,我不知道这一点。虽然链接中的文档没有提到不再推荐使用HibernateTemplate,但似乎只是稍微提到了一下。就个人而言,我更喜欢将HibernateException转换为DataAccessException系列,因此更倾向于使用HibernateTemplate。 - matt b
您可以使用@Repository注释将HibernateException转换为DataAccessException。 - danny.lesnik

-1

强烈建议远离Spring中的@Transactional。对于您的使用,应坚持使用open-session-in-view模式。当请求到达时,会打开一个会话并启动事务。如果遇到异常或其他错误,则回滚事务。否则提交。这样,Hibernate将为您处理所有繁重的工作。

如果您选择使用@Transactional,您将进入一个模糊的领域,其中包括懒加载异常和其他奇怪的行为,因为您尝试解决来自何处的问题。最糟糕的是,如果您在已经在Hibernate一级或二级缓存中的对象上放置@Transactional,您将会得到大量的BEGIN/END事务传递到数据库中,而实际上没有执行任何查询(因为它们在缓存中)。这可能会影响您的性能,并且几乎不可能消除。

在您的会话事务失败后,您需要回滚。根据语义,您可能需要重做您的会话。第一个问题的解决方法很简单-在保存之前首先检查实体是否已经存在。那将解决第二个问题。

请查看这个比较Hibernate/JPA和myBatis的文章获取更多评论。


2
开放会话视图模式并不能替代声明式事务。OSIVFilter不会启动事务。Spring的事务管理和其OSIVFilter是相互补充的,但各自都有非常具体的作用。你所描述的问题源于对它们的误解或错误使用。 - Ryan Stewart
好的...你会如何描述他们的角色?你会如何解决这些问题?我并不是说他们做同样的事情,我是说添加Spring事务管理(特别是对于OP所描述的典型Web应用程序)会增加很多复杂性和麻烦,而没有特别的收益。 - Will Iverson
我今天早些时候在答案中简要描述了角色。Spring事务管理是最简单的,专门为此处讨论的应用程序类型而设计。它非常简单,我几乎可以在这个评论的限制内给出一个教程。一个bean:事务管理器,与您选择的持久性机制连接;一行XML以启用注释;并在服务方法上使用注释。完成。 - Ryan Stewart
那就是教程描述的方式。一旦你开始处理任何复杂性,你就回到了冲突事务注释的领域,不同的设置在不同的访问中产生不同的影响。琐碎的、一次性的方法请求并不是问题所在。一旦你开始尝试跨越具有不同事务的方法边界,你就完蛋了——结果只能通过仔细比较代码发出的事务并使用调试器逐步查看堆栈来进行调试(或理解)。 - Will Iverson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接