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.