首先 Mybatis 的工作原理
Mybatis 有四大对象,是一个责任链的设计模式,而Mybatis的工作原理就是这一条链。
第一个对象 Executor 判断应该执行哪一种方法,在Mybatis中只有两种方法 select/update
第二个对象 ParameterHandler 解析请求参数 #{} 会预编译对比数据库字段和请求参数类型,${}不会对比,如果不匹配则直接抛出异常
第三个对象 StatementHandler 数据库对象,拿到sql语句并执行,分页插件就是重写了这条sql。
第四个对象 ResultSetHandler 映射结果集对象。
mybatis 是如何将接口交给spring管理的。最重要的一步。
比如 我们在自己的Spring MVC中整合并写了一个mapper 的接口
手写Spring MVC 地址:https://gitee.com/wanganfen/xx-spring/tree/master/simple-mvc
@XxMapper
@XxNameSpace("userMapper")
public interface UserMapper {
@XxSelect(value = "select * from user_bo where id = #{id} and user_age = #{age}",resultType = "com.waf.entity.User")
List<User> findSysUserById(@XxParam("id") String id,@XxParam("age") Integer age);
@XxSelect(value = "select * from user_bo where id = ${id} and user_age = ${age}",resultType = "com.waf.entity.User")
List<User> findSysUserById2(@XxParam("id") String id,@XxParam("age") Integer age);
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface XxMapperScanners {
String[] value();
}
配置类的XxMapperScanners 注解会指定扫描路径下面被XxMapper注解修饰的接口
拿到这一批接口的class,我们在前面的文章中提到了注册BeanDefinition有一个很重要的属性就是beanClass。但是这个beanClass是一个接口类型的,无法通过反射去实例化
那么如何将接口交给Spring 容器管理呢。
接口->jdk动态代理->代理对象->Spring Bean
Spring提供了一个接口 叫FactoryBean(源码里面使用的是构造方法注入,这边使用的是属性注入的方式)
public class XxMapperProxyFactory<T> implements XxFactoryBean<T>, InvocationHandler {
@XxManualwired
private Class<T> clazz;
private static final List<XxExecutor> XX_EXECUTORS = new ArrayList<>();
static {
XX_EXECUTORS.add(new XxExecutorSelect());
XX_EXECUTORS.add(new XxExecutorUpdate());
}
public Class<T> getClazz() {
return clazz;
}
public void setClazz(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public T getObject() throws Exception {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz},this);
}
@Override
public Class<T> getObjectType() {
return clazz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args){
try {
Class<?>[] interfaces = proxy.getClass().getInterfaces();
Method[] declaredMethods = interfaces[0].getDeclaredMethods();
Optional<Method> optional = Arrays.stream(declaredMethods).filter(x -> compareMethodIsIdentical(x, method)).findFirst();
if(optional.isPresent()){
return doExecutors(optional.get(),args);
}
return null;
}catch (Exception e){
}
return null;
}
代理对象就生成出来了
那么beanDefinition 如何注册进去呢
Spring 也有一个接口 叫 ImportBeanDefinitionRegistrar 源码
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
提供了一个注册器,当然我们也可以写一个XxImportBeanDefinitionRegistrar 接口,而且beanClass 就是使用的FactoryBean ,
public class XxMapperScannerRegistry implements XxImportBeanDefinitionRegistrar{
private static final XxClassPathBeanDefinitionScanner scanner = new XxClassPathBeanDefinitionScanner();
private static List<Class<?>> classList = new ArrayList<>();
@Override
public void registerBeanDefinitions(XxBeanDefinitionRegistry registry) {
XxDefaultListableBeanFactory beanFactory = (XxDefaultListableBeanFactory) registry;
for (String beanName : beanFactory.getXxBeanDefinitionMaps().keySet()) {
XxBeanDefinition xxBeanDefinition = beanFactory.getBeanDefinition(beanName);
if(xxBeanDefinition.getBeanClass().isAnnotationPresent(XxMapperScanners.class)){
XxMapperScanners xxMapperScanners = (XxMapperScanners)xxBeanDefinition.getBeanClass().getAnnotation(XxMapperScanners.class);
doScanner(xxMapperScanners.value());
}
}
if(CollectionUtils.isNotEmpty(classList)){
classList.forEach(clazz->{
XxBeanDefinition beanDefinition = new XxBeanDefinition();
beanDefinition.setBeanClass(XxMapperProxyFactory.class);
beanDefinition.setObjectField(clazz);
registry.registerBeanDefinition(GenerateBeanNameUtils.generateBeanName(clazz.getSimpleName()),beanDefinition);
});
}
}
public void doScanner(String[] value){
List<Class<?>> classes = scanner.loadResources(value);
classList.addAll(classes.stream().filter(x->x.isAnnotationPresent(XxMapper.class)).distinct().collect(Collectors.toList()));
}
}
然后将这两个XxMapperScannerRegistry ,XxImportBeanDefinitionRegistrar 注入到Spring MVC的配置类中 @XxImport({XxMapperScannerRegistry.class,XxMapperProxyFactory.class})
beanDefinition.setObjectField(clazz); 这个set的属性就是我们传进去的mapper接口
@XxManualwired
private Class<T> clazz;
然后属性注入 (Spring的生命周期第三步),这样一个代理对象就交给Spring容器管理了。
private Object handleXxMangaloreAnnotation(String beanName, Object instance) {
try {
XxBeanDefinition xxBeanDefinition = beanFactory.getXxBeanDefinitionMaps().get(beanName);
if(Objects.nonNull(xxBeanDefinition.getObjectField())){
Field[] fields = instance.getClass().getDeclaredFields();
for (Field field : fields) {
if(field.isAnnotationPresent(XxManualwired.class)){
field.setAccessible(true);
field.set(instance,xxBeanDefinition.getObjectField());
}
}
}
} catch (Exception ignored) {
return instance;
}
return instance;
}
然后就开始在XxMapperProxyFactory invoke方法里面写处理sql的逻辑了
就是四大对象的执行过程了。
手写 Mybatis 的gitee地址:https://gitee.com/wanganfen/xx-spring/tree/master/simple-mybatis
映射结果集的时候,因为@select 注解里面有个 resultType 属性,当整合到Spring MVC的时候,
这个属性里面的对象是可以直接load到内存中去的,这个对象里面的属性加了一个注解,注解的值就是数据库的字段,通过循环结果集,在判断注解值是否匹配即可完成最终的映射,如果返回的是一个对象,一条数据直接循环resultType 属性对象的字段去匹配,如果返回的是一个集合,就在循环的结果集里面去多次 生成resultType 属性对象 在去匹配字段,封装结果集。其实查询就只有两种selectOne 和 selectList 。