mybatis对java自定义注解的使用——入门篇
最近在学习 spring 和 ibatis 框架。
以前在天猫实习时做过的一个小项目用到的 mybatis,在其使用过程中,不加思索的用了比较原始的一种持久化方式:
在一个包中写一个 DAO 的接口,在另一个包里面写 DAO 的实现,使用 sqlMapClient 来从 ***-sql.xml 中读取相应的 sql。
public interface IBaseDaoiBatis {Object get(String statementName); } public class BaseDaoiBatis implements IBaseDaoiBatis { public Object get(String statementName) { return getSqlMapClientTemplate().queryForObject(statementName); } } //对应的 mybatis 配置文件里面的 sql: <sqlMap> <typeAlias alias="sonarBean" type="com.**--**.SonarScanDataDisplayBean" /> <select id="getSonarScanData" parameterClass="java.lang.Integer" resultClass="java.lang.String"> <![CDATA[ SELECT name FROM mm_test where id=#id#; ]]> </select> </sqlMap>
最近搭建了一个 spring+ibatis 的项目,发现了一种新的持久化方式:
只写一个 dao 的接口,在接口的方法中直接注解上用到的 sql 语句,觉得蛮巧妙的。借来用一下。注意,接口上方多了一个 @Mapper 注解。而每个方法上都是 @Select() 注解,值为对应的 sql。
@Mapper public interface TestDao { @Select("select id, name, name_pinyin from mm_test;") List<MmTest> selectAll();@Insert(</span>"insert into mm_test(id, name) values(#{id}, #{name})"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> insertUser(MmTest mmtTestS);
}
那么这个 @Mapper 注解究竟是个什么东西,是怎么起到注解的作用的?ibatis 是怎么来识别这种注解的呢?对我这个 java 小白来说,注解,是 spring 特有的东西嘛?自学 java 的时候好像很少接触注解啊。不过竟然有java.lang.annotation 这个包,这到底是怎么回事?
那我们先来看一下 Mapper 这个自定义注解的定义:
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Mapper {String value() default ""; }
关于自定义注解:(查的别人的博客:http://www.cnblogs.com/mandroid/archive/2011/07/18/2109829.html)博客里面写的非常详细,并且注解的使用机制很容易理解。
拿上述的 @Mapper 来说,Retention 选择的是 RUNTIME 策略,就是运行时注入。那么要在运行时获得注入的值,必然要用到 java 的反射机制。通过反射,拿到一个类运行时的方法变量等,来进行一系列的操作。
那我要考虑的下一个问题是,我定义的 @Mapper,在我的工程里面是怎么识别的呢?
来看一下我 spring 的配置文件中关于 mybatis 的配置
<!--mybatis--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation"> <value>classpath:myBatis/mapper.xml</value> </property> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.**.**.**.dao" /> <property name="annotationClass" value="com.nuomi.crm.annotation.Mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean>
在org.mybatis.spring.mapper.MapperScannerConfigurer 这个类里面,应该是会去扫描我自定义的 com.nuomi.crm.annotation.Mapper 这个类的。
<configuration> <settings> <!-- 将下划线字段名称映射为驼峰变量 --> <setting name="mapUnderscoreToCamelCase" value="true" /> <!-- 进制 mybatis 进行延迟加载 --> <setting name="lazyLoadingEnabled" value="false"/> </settings> <mappers> </mappers> </configuration>
在我的 mapper.xml 里面只需要进行这一简单的配置就可以了(配置的含义后续补充)
接下来看一下 mybatis 自带的这个 MapperScannerConfigurer 究竟怎么实现的,来使用我这个自定义的注解 @Mapper 呢。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { private Class<? extends Annotation> annotationClass; public void setAnnotationClass(Class<? extends Annotation> annotationClass) { this.annotationClass = annotationClass; }/** * {@inheritDoc} * * @since 1.0.2 */ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders(); }ClassPathMapperScanner scanner </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ClassPathMapperScanner(registry); scanner.setAddToConfig(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.addToConfig); scanner.setAnnotationClass(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.annotationClass); scanner.setMarkerInterface(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.markerInterface); scanner.setSqlSessionFactory(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.sqlSessionFactory); scanner.setSqlSessionTemplate(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.sqlSessionTemplateBeanName); scanner.setResourceLoader(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.applicationContext); scanner.setBeanNameGenerator(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.nameGenerator); scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
/*
- BeanDefinitionRegistries are called early in application startup, before
- BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been
- loaded and any property substitution of this class' properties will fail. To avoid this, find
- any PropertyResourceConfigurers defined in the context and run them on this class' bean
- definition. Then update the values.
*/
private void processPropertyPlaceHolders() {
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!prcs.isEmpty() && applicationContext <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> GenericApplicationContext) { BeanDefinition mapperScannerBean </span>=<span style="color: rgba(0, 0, 0, 1)"> ((GenericApplicationContext) applicationContext) .getBeanFactory().getBeanDefinition(beanName); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> PropertyResourceConfigurer does not expose any methods to explicitly perform </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> property placeholder substitution. Instead, create a BeanFactory that just </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> contains this mapper scanner and post process the factory.</span> DefaultListableBeanFactory factory = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DefaultListableBeanFactory(); factory.registerBeanDefinition(beanName, mapperScannerBean); </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (PropertyResourceConfigurer prc : prcs.values()) { prc.postProcessBeanFactory(factory); } PropertyValues values </span>=<span style="color: rgba(0, 0, 0, 1)"> mapperScannerBean.getPropertyValues(); </span><span style="color: rgba(0, 0, 255, 1)">this</span>.basePackage = updatePropertyValue("basePackage"<span style="color: rgba(0, 0, 0, 1)">, values); </span><span style="color: rgba(0, 0, 255, 1)">this</span>.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName"<span style="color: rgba(0, 0, 0, 1)">, values); </span><span style="color: rgba(0, 0, 255, 1)">this</span>.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName"<span style="color: rgba(0, 0, 0, 1)">, values); }
}
}
上面只是截取的关于 annotation 的代码片段.
scanner.setAnnotationClass(this.annotationClass);
这里会去扫描配置的那个注解类。
mybatis 的内部实现会使用 java 反射机制来在运行时去解析相应的 sql。
(上面写的还不是很完全,后续补充。)