Spring中@Transactional与@EnableTransactionManagement引发BeanNotOfRequiredTypeException问题
学习Spring过程中未使用Spring Boot,尝试在@Repository标注的MessageRepository类方法上添加@Transactional,并在主类TestSpringApplication添加@EnableTransactionManagement,运行时抛出BeanNotOfRequiredTypeException:
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'messageRepository' is expected to be of type 'com.cypherf.repository.MessageRepository' but was actually of type 'jdk.proxy2.$Proxy25'
移除@Transactional或@EnableTransactionManagement其中一个后应用可正常启动,但事务功能失效。
相关代码如下:
主类TestSpringApplication
package com.cypherf; import com.cypherf.model.Message; import com.cypherf.repository.MessageRepository; import com.cypherf.service.MessageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @ComponentScan @EnableTransactionManagement public class TestSpringApplication { @Autowired private ApplicationContext context; @Autowired private MessageRepository messageRepository; @Autowired private MessageService messageService; final static Logger logger = LoggerFactory.getLogger(TestSpringApplication.class); public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(TestSpringApplication.class); TestSpringApplication app = ctx.getBean(TestSpringApplication.class); logger.info("Running app..."); app.run(args); } public void run(String... args) { messageRepository.mrSave(new Message("Message one")); messageRepository.mrSave(new Message("Message two")); messageService.printAllMessages(); messageRepository.mrSaveTransactional(new Message("Message two")); messageService.printAllMessages(); } }
MessageRepository类
package com.cypherf.repository; import com.cypherf.model.Message; import java.util.List; import java.util.Optional; import com.cypherf.service.MessageService; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceException; import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @Repository public class MessageRepository implements CrudRepository<Message, Long> { @Autowired PersistenceUtil pu; final static Logger logger = LoggerFactory.getLogger(MessageRepository.class); public void mrSave(Message message) { EntityManager em = pu.getEntityManager(); try { em.getTransaction().begin(); em.persist(message); em.getTransaction().commit(); System.out.println("here"); } catch (PersistenceException e) { System.err.printf("%s already exists%n", message); e.printStackTrace(); } finally { System.out.println(); em.close(); } } @Transactional public void mrSaveTransactional(Message message) { logger.info("MessageRepository.mrSaveTransactional()"); pu.getEntityManager().persist(message); } public List<Message> getAll() { EntityManager em = pu.getEntityManager(); List<Message> messages = em.createQuery("from Message", Message.class).getResultList(); em.close(); return messages; } [...] }
hibernate.cfg.xml配置
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="show_sql">true</property> <property name="format_sql">true</property> <property name="dialect">org.hibernate.community.dialect.H2LegacyDialect</property> <property name="connection.driver_class">org.h2.Driver</property> <property name="connection.url">jdbc:h2:./spring</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <property name="hibernate.hbm2ddl.auto">create-drop</property> <property name="hibernate.show_sql">true</property> <mapping class="com.cypherf.model.Message"/> </session-factory> </hibernate-configuration>
Spring默认使用JDK动态代理生成事务代理对象,JDK代理的目标是接口而非具体类。你的MessageRepository实现了CrudRepository接口,因此Spring生成的代理类只会实现CrudRepository接口,而非继承MessageRepository类。当你在主类中以MessageRepository类类型注入时,代理对象无法转换为该类类型,就会抛出类型不匹配的异常。
提供三种可行方案,按推荐程度排序:
方案1:按接口类型注入(推荐)
将主类中MessageRepository的注入类型改为它实现的接口CrudRepository<Message, Long>。如果需要调用自定义方法(如mrSave、mrSaveTransactional),建议自定义一个接口(比如CustomMessageRepository),让MessageRepository实现该接口,然后注入自定义接口类型。
示例修改主类注入:
@Autowired private CrudRepository<Message, Long> messageRepository;
自定义接口示例:
public interface CustomMessageRepository extends CrudRepository<Message, Long> { void mrSave(Message message); void mrSaveTransactional(Message message); List<Message> getAll(); }
然后修改MessageRepository实现该接口:
@Repository public class MessageRepository implements CustomMessageRepository { // 原有代码不变 }
主类注入自定义接口:
@Autowired private CustomMessageRepository messageRepository;
方案2:强制使用CGLIB代理
在@EnableTransactionManagement注解中设置proxyTargetClass = true,强制Spring使用CGLIB代理(基于类的代理),这样生成的代理类会继承MessageRepository,就能正常注入类类型。
修改主类的注解:
@EnableTransactionManagement(proxyTargetClass = true)
注意:使用CGLIB代理需要确保类不是final的,且Spring核心包中包含CGLIB依赖(Spring 5+默认已包含)。
方案3:将事务注解移至Service层(规范做法)
事务控制通常应该放在业务逻辑层(Service)而非数据访问层(Repository)。将@Transactional注解移到MessageService的方法中,调用Repository的方法,这样既符合分层架构规范,也能避免代理类型问题。
示例修改MessageService:
@Service public class MessageService { @Autowired private MessageRepository messageRepository; @Transactional public void saveTransactional(Message message) { messageRepository.mrSaveTransactional(message); } // 其他方法... }
然后在主类的run方法中调用Service的方法:
messageService.saveTransactional(new Message("Message two"));
内容的提问来源于stack exchange,提问作者Gaetan L.

