mybatis的注解使用非常简便(因为不用编写一大堆xml语句)。
网上给出的使用教程简化如下
1.在spring配置文件添加以下bean
<!-- 配置数据源,使用dbcp -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://111.11.11.111:3306/database"></property>
<property name="username" value="root"></property>
<property name="password" value="*******"></property>
<property name="maxActive" value="10"></property>
<property name="maxIdle" value="5"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" >
<!-- 加载mybatis的配置信息 -->
<!--<property name="configLocation" value="cn/labelnet/mybatis/config/SqlmapDaoConfig.xml"></property>-->
<!-- 加载数据源 dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--com.dao是mapper类所在的包-->
<property name="basePackage" value="cn.dao"/>
<!--下面这个省略掉的话也不会出错,建议保留它-->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
里面的类中最关键的是MapperScannerConfigurer,自动扫描就是在这里实现的。
2.再写个mapper接口就行了
public interface UserMapper {
@Insert("insert into User(uid,wxid,createTime,lastTime) values(#{uid},#{wxid},#{createTime},#{lastTime})")
public void insert(User user);
@Delete("delete from User where uid=#{uid}")
public void deleteById(String uid);
@Update("update User set wxid=#{wxid},lastTime=#{lastTime} where uid=#{uid}")
public void updateT(User user);
@Select("select * from User where uid=#{uid}")
public User getUser(String uid);
}
网上的教程都是把basePackage固定再一个dao包中,但我却想不管mapper接口在那,mybatis都能自动生成对应的类。
最简单的方法当然是把basePackage的value改成根包了。而这时spring会报出一个bean不唯一的异常
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [cn.pingweb.service.ITeacherService] is defined: expected single matching bean but found 2: teacherServiceImpl,ITeacherService
解决问题
百度一番没什么结果,当然就去看源码了。
MapperScannerConfigurer对mapper的扫描注册可以在这里开始看
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
接着看ClassPathMapperScanner,其中registerFilters方法负责过滤扫描到的接口。再结合其他信息,我们可以分析到网上的教程都把basePackage固定再一个dao包中是很有道理的!因为mybatis会为basePackage范围内的每一个接口都自动生成一个类!这个就是报错的原因。
/**
* Configures parent scanner to search for the right interfaces. It can search
* for all interfaces or just for those that extends a markerInterface or/and
* those annotated with the annotationClass
*/
public void registerFilters() {
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// override AssignableTypeFilter to ignore matches on the actual marker interface
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}
// exclude package-info.java
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}
再来看下上面方法中的这个,可见我们通过添加一个自定义注解把不是mapper的接口过滤掉
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
而this.annotationClass是在MapperScannerConfigurer传入的
所以我们如下只要改一下配置(注入annotationClass属性),添加一个自定义注解就行了
代码如下
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--com.dao是mapper类所在的包-->
<property name="basePackage" value="cn.dao"/>
<!--下面这个省略掉的话也不会出错,建议保留它-->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<!--下面这个省略掉的话也不会出错,建议保留它-->
<property name="annotationClass" value="framework.database.annotation.Dao"/>
</bean>
package framework.database.annotation;
public @interface Dao {
}
@Dao
public interface UserMapper {
@Insert("insert into User(uid,wxid,createTime,lastTime) values(#{uid},#{wxid},#{createTime},#{lastTime})")
public void insert(User user);
@Delete("delete from User where uid=#{uid}")
public void deleteById(String uid);
@Update("update User set wxid=#{wxid},lastTime=#{lastTime} where uid=#{uid}")
public void updateT(User user);
@Select("select * from User where uid=#{uid}")
public User getUser(String uid);
}