You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 08:28:10