Spring注入具体类而不是代理类

5
我有一个问题,Spring将代理注入到DAO对象中的服务中,但是这个服务被注入到控制器中,它是具体的类。这不允许我使用服务范围内的事务,并为每个DAO调用单独启动事务。这是我所期望的行为。
配置:
控制器是带有@Controller注释和构造函数DI的类。
服务:
@Component @Transactional public class UserServiceImpl implements UserService { ...}
DAO:
@Component @Transactional public class UserDaoImpl implements UserDao {
JPA配置:
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" >
    <property name="dataSource" ref="dataSource"/>
    <property name="persistenceUnitName" value="xxxPersistenceUnit"/>
    <property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml"/>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        </bean>
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
            <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
            <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
        </props>
    </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<tx:annotation-driven />

有人知道为什么会发生这种情况吗?


我会放弃在服务上使用'@Component',并使用'@Service'注释。至于DAO,我会使用'@Repository'注释,并放弃组件注释。此外,'@Transactional'属性意味着每个服务方法都将具有相同的事务传播。我会根据它们的预期功能单独注释我的每个方法。 - Nikola Yovchev
我添加了一个答案,请检查您的交易配置XML文件是否由声明用户服务的相同应用程序上下文处理。如有任何疑问,请随时提出。 - Boris Treukhov
4个回答

5

很可能您的UserServiceImpl是错误地在servlet上下文中创建的 - 请检查context:component-scan表达式,以确保只包含Controller类。

有关组件扫描过滤器的示例,请参见@Service会被构建两次

例如,如果事务管理器bean和<tx:annotation-driven>根Web应用程序上下文中声明,则事务代理仅针对根应用程序上下文中的bean创建(来自Spring文档):

BeanPostProcessor接口是按容器范围划分的。这仅在使用容器层次结构时才相关。如果您在一个容器中定义了BeanPostProcessor,则它只会在该容器中的bean上执行其工作。即使两个容器都是同一层次结构的一部分,也不会通过另一个容器中的BeanPostProcessor对在一个容器中定义的bean进行后处理。

更不太可能的是,用户服务的事务配置被配置为使用另一个事务管理器(或另一个默认传播方式),但在这种情况下,DAO方法的堆栈跟踪中将存在TransactionInterceptor调用。

如果你理解自己在做什么,那么在Spring中DAO类上使用@Transactional是完全可以的——认为仓库或DAO不能开启事务的想法来自于早期需要创建样板代码来打开事务且很难管理事务实例的时代(并且你不能确定事务是如何被管理的)。但是当你使用声明式配置时,情况不会那么糟糕。Spring提倡约定优于配置的风格,在大多数方法中使用Propagation.REQUIRED事务模式。在Spring中,当你用@Transactional修饰方法时,Propagation.REQUIRED是默认模式(这个传播被硬编码到@Transactional注释声明中),这意味着新的逻辑事务映射到相同的物理事务,因此在DAO类上使用@Transactional是无害的。

有关Spring事务传播的参考,请参见http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/transaction.html#tx-propagation

在Spring Data JPA中(我相信他们知道自己在做什么),例如,存储库实例上的CRUD方法默认情况下是事务性的。在某些情况下可能会有用,机制与Hibernate允许您从Session获取一些任意对象以进行显示而无需声明显式事务的方式相同(当然,这并不意味着框架以某种方式可以没有事务 - 在这种情况下是隐式的)。

当然,“永远不要说永远”,但我认为在绝大多数情况下,您会想要在服务中设置事务边界,因为这定义了业务工作单元的逻辑分组。问题中传统的UserServiceUserDAO的使用肯定表明这种习语在这种情况下是合适的。 - ach
1
@ach 是的,你关于大多数情况是正确的。但在我看来,这些模式(DAO/Repository)是在2003年发布的,在具有声明式事务传播的框架变得流行之前。我认为,只有Java EE应用服务器才提供了声明式事务(如果您想使用独立的TM与容器,则必须直接调用TM方法),在Spring出现之前。或者您必须开发自定义AOP解决方案(并告诉团队如何使用它),但在Spring中,我们没有这个问题-一切都经过测试和文档化。 - Boris Treukhov
嗨,你是对的。问题确实出在在servlet上下文中创建服务bean,而不是根上下文中。在<component-scan />中添加过滤器后,一切都像魔术般地运行。 - mihn
谢谢,这也帮了我很大的忙。我的dispatcher-servlet组件扫描设置得非常通用,比如“com.mycompany”,这也包含了应用程序上下文中的bean。 - Forge_7

2

我有点难以理解你的意思,但看起来你对于每次DAO调用都获得一个新的事务感到惊讶,而不是只在服务调用时获得。不幸的是,这正是你在DAO类上放置"@Transactional"所指定的。按照通常的模式,你的DAO不应该是事务性的。如果我理解正确,你应该删除DAO类上的@Transactional注释。


如果我在应用程序服务上使用@Transactional(默认框架为Propagation.REQUIRED),每个DAO调用都会得到一个新的事务,那我也会感到惊讶! - Boris Treukhov
@BorisTreukhov 对你的评论打个负号。OP说他的服务被注入为一个具体类(而不是代理),这意味着它不是事务性的。因此,每次调用DAO方法都应该在新的事务中执行(除了DAO方法相互调用的情况)。 - ach
A) OP已经使用transactional修饰了DAO和应用服务。B)问题在于该服务是一个具体类(而不是代理类),Spring将代理注入到DAO对象中,但是当该服务被注入到控制器时,它是一个具体类。 - Boris Treukhov
@BorisTreukhov 是的,这就是我想说的。但是,你说“如果我在每个DAO调用中都获得一个新事务,你也会感到惊讶”... 如果服务不是事务性的,那么你不应该感到惊讶,因为这正是预期的行为。 - ach
1
@ach 你有注意到“当我的应用服务被注释为@Transactional时”的部分吗?因此,我因过度讽刺和错误信息而对这篇文章进行了负评。 - Boris Treukhov

1
其他回答者说得没错,你不应该将DAO注释为@Transactional,但要真正理解发生了什么,你应该参考Spring参考手册中的事务传播部分。使用@Transactional时的默认传播是REQUIRES_PROPAGATION,因此请特别注意查看。
你的问题不是很具体,所以我不确定你究竟在寻找什么。 编辑:重新阅读你的问题后,可能存在组件扫描的问题。请检查确保<tx:annotation-driven />与扫描服务类的应用程序上下文位于同一位置。

0

你不应该在DAO对象中使用"@Transactional"注解。你应该在Service中定义它,这将确保所有在服务方法内部调用的DAO方法都在同一个事务中执行,这似乎正是你想要的“服务范围事务”,对吧?

另外,建议你将UserServiceImpl中的注解从"@Component"更改为"@Service",将UserDaoImpl中的注解更改为"@Repository"。

最好的问候。


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