MyBatis- Spring + @Конфигурация - Невозможно autowire mapper beans

Я пытаюсь создать проект Spring, который использует MyBatis для уровня доступа к данным в качестве доказательства концепции для моей команды. Я действительно хочу избежать конфигурации XML, если это вообще возможно, поэтому я пытаюсь связать все вместе, используя аннотированные классы @Configuration.

Кажется, что все правильно подключено, но мой сопоставитель beans не подключен к моему сервису в AutoWired.

В моем примере я пытаюсь связать UserDao, объект пользователя и UserService.

UserDao

public interface UserDao {
    @Select("SELECT * FROM users WHERE id = #{userId}")
    User get(@Param("userId") Integer userId);
}

Пользователь

@Component("User")
public class User implements Entity {
    public Integer userId;
    public String username;

    /** ... getters/setters ommitted **/
}

UserServiceImpl

@Service("UserService")
public class UserServiceImpl {
    private UserDao userDao = null;  

    public User getUserDetails(Integer userId) {
        return userDao.get(userId);        
    }

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

Я соединяю их вместе, используя два класса конфигурации.

ApplicationContextConfig

@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)
@Import(DefaultDataAccessConfig.class) // I'm importing this because I thought ordering might be important, otherwise I was hoping to just let the component scanning pull in additional configuration files
@ComponentScan(basePackages="com.example.gwtspringpoc.server",
               [email protected](type=FilterType.ANNOTATION,
                                      value=Controller.class))
public class ApplicationContextConfig {
    /** No bean definitions needed here **/
}

DefaultDataAccessConfig

@Configuration
@EnableTransactionManagement
public class DefaultDataAccessConfig implements TransactionManagementConfigurer {    
    @Bean
    public DataSource dataSource() {
        OracleDataSource ods = null;
        try {
            ods = new OracleDataSource();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        ods.setURL("jdbc:oracle:thin:@//localhost:9601/sid");
        ods.setUser("user");
        ods.setPassword("pass");        

        return ods;       
    }

    @Override
    @Bean(name="transactionManager")
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() {
        SqlSessionFactoryBean sf = new SqlSessionFactoryBean();        
        sf.setDataSource(dataSource());    
        try {
            return (SqlSessionFactory) sf.getObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Bean
    public SqlSession sqlSessionTemplate() {
        return new SqlSessionTemplate(sqlSessionFactory());
    }    

    /*
     * This did not work at all. It seems to be configured correctly, but the UserDao bean never
     * got created at any stage, which was very disappointing as I was hoping not to have to
     * create a bean definition for each DAO manually
     */
    /*@Bean
    public static MapperScannerConfigurer mapperScannerConfig() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.ca.spna.gwtspringpoc.server.model.dao");      
        msc.setAnnotationClass(Repository.class);      

        return msc;
    }*/

    /*
     * Because the above code did not work, I decided to create the mapping manually.
     * This is most likely my problem - something about this setup. My understanding
     * is that the MapperFactoryBean once instantiated by Spring, will create a proxy
     * object of type UserDao with the name "userDao" that can be injected elsewhere.
     */
    @Bean
    public MapperFactoryBean<UserDao> userDao() {
        MapperFactoryBean<UserDao> mfb = new MapperFactoryBean<UserDao>();        
        mfb.setMapperInterface(UserDao.class);
        return mfb;
    }
}

Вы можете прочитать комментарии выше двух последних методов в приведенном выше фрагменте кода, чтобы получить более полное представление о том, как я создаю UserDao bean.

Как только я получил всю настройку конфигурации, я создал unit test, чтобы попробовать протестировать UserService с помощью AnnotationConfigContextLoader, но сразу же попал со следующим исключением, когда пытаясь запустить тест:

Exception

Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.example.gwtspringpoc.server.service.UserServiceImpl.setUserDao(com.example.gwtspringpoc.server.model.dao.UserDao); nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.example.gwtspringpoc.server.model.dao.UserDao] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

После этого я прокомментировал @Autowired в UserService и вернулся к моему unit test и ввел ApplicationContext, поэтому Я мог бы проверить его, а bean с именем "userDao" на самом деле является экземпляром MapperProxy.

Итак, я понимаю, как работает MapperFactoryBean, или это просто не очень совместимо с конфигурацией, управляемой аннотациями? Кроме того, если кто-нибудь знает, как сделать работу MapperScannerConfigurer корректной, я был бы очень признателен!

Ответ 1

Через некоторое время я смог разобраться в этом, поэтому я отвечу на свой вопрос, если другие столкнутся с чем-то похожим, поскольку там было не так много информации, и это потребовало некоторых поисков.

Проблема сводится к тому, что MapperScannerConfigurer является BeanDefinitionRegistryPostProcessor. Как оказалось, это тот же механизм, используемый для обработки файлов @Configuration и регистрации аннотированных методов @Bean. К сожалению, один BeanDefinitionRegistryPostProcessor не может использовать другой, в соответствии с этим билетом Spring Jira: https://jira.springsource.org/browse/SPR-7868

Предложение состояло в том, чтобы создать конфигурацию XML для процессора, а затем добавить аннотацию @ImportResource в конфигурации на Java, чтобы втянуть ее. Ну, это предложение не совсем точно. Вы не можете просто создать XML файл с конфигурацией и вставить его в конфигурацию на основе Java, если вы все еще планируете загружать свою конфигурацию через AnnotationConfigContextLoader. Вместо этого вам нужно вернуться к загрузке конфигурации с помощью XML сначала, а затем создать bean для вашего файла конфигурации "старомодный" способ. Для меня это было довольно тривиально.

Новый контекст приложения

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd         http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

    <!--
        Because MapperScannerConfigurer is a BeanDefinitionRegistryPostProcessor, it cannot be 
        configured via @Configuration files with a @Bean annotaiton, because those files are
        themselves configured via a BeanDefinitionRegistryPostProcessor which cannot spawn
        another one.
    -->
    <bean id="myBatisMapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
       <property name="basePackage" value="com.example.gwtspringpoc.server.model.dao"/>
       <property name="annotationClass" value="org.springframework.stereotype.Repository"/>
    </bean>

    <!-- 
        Load the rest of our configuration via our base configuration class
     -->
     <bean class="com.example.gwtspringpoc.server.spring.config.ApplicationContextConfig" />
</beans>

Затем я загружаю контейнер контекста традиционным способом, предоставляя ContextConfigLocation. Это работает для меня, потому что ApplicationContextConfig, с которым я ссылаюсь в приведенном выше XML, обрабатывает все остальное - включая сканирование компонентов, которое будет загружать все мои другие файлы @Configuration.

Как только я это сделал, все мои проблемы исчезли. Я смог @Autowire UserDao, как я ожидал, и все было замечательно.

Примечание:

Когда я попытался вручную определить UserDao, создав MapperFactoryBean, как и в моем исходном примере кода вопроса, был создан UserDao bean, но он имел тип MapperProxy и не будет @Autowire. Однако я мог бы загрузить его по имени, используя @Repository ( "userDao" ), для чего это стоит. Я считаю, что MapperFactoryBean страдает от аналогичной проблемы с MapperScannerConfigurer и просто не совместим с файлами @Configuration, увы.

Ответ 2

Из mybatis.3.2.0 и mybatis- spring.1.2.0, вместо MapperFactoryBean вы можете использовать MapperScan для этого.

@Configuration 
@MapperScan("org.mybatis.spring.sample.mapper") 
public class AppConfig 
{   
    @Bean   
    public DataSource dataSource() 
    {     
      return new EmbeddedDatabaseBuilder().addScript("schema.sql").build();   
    }   
   @Bean   
   public DataSourceTransactionManager transactionManager() 
   {     
        return new DataSourceTransactionManager(dataSource());   
   }   
   @Bean   
   public SqlSessionFactory sqlSessionFactory() throws Exception 
   {     
       SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
       sessionFactory.setDataSource(dataSource());    
       return sessionFactory.getObject();   
   } 
}

Ответ 3

Еще одно возможное решение можно найти в джире, отмеченной Джейсоном. Решил мою проблему, и мне не пришлось использовать конфигурацию XML, которую я стараюсь избежать любой ценой...

https://jira.spring.io/browse/SPR-7868

@Configuration 
public class A implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {

    @Override
    public void postProcessBeanDefinitionRegistry(...) {
         ...
    }

    @Override
    public void postProcessBeanFactory(...) {
        ...
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

Ответ 4

В настройках spring вам понадобится контекст: компонентное сканирование, чтобы включить автоматическое обнаружение @Component. Проверьте reference.