前言

在spring中使用mybaits简直不要太简单,只需要几个配置,一个DAO接口和一个mapper.xml就可以完成一次数据库交互。但是简单背后往往是复杂的实现,现在我们来探讨一下里面的一点原理吧,对,在不考虑springboot自动配置的前提下。

spring和mybaits是如何集成的

spring是一个框架,mybaits也是一个框架,要想一个框架起作用,总得有一个调用入口的。springboot的入口我们应该很清楚,如下

@SpringBootApplication(scanBasePackages = "com.example.demo")
@MapperScan("com.example.demo.repository.dao")
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}

main方法里面调用了SpringApplication.run(StartApplication.class, args),就开始了springboot一系列的初始化流程。而mybaits我们好像没有调用啊,那它是怎么初始化的。

@MapperScan("com.example.demo.repository.dao")

再细看一下,我们好像是用到了mybaits包里面的MapperScan注解。它里面是这样的

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
///省略
}

上面的注解关键在于@Import(MapperScannerRegistrar.class),用了@Import,spring就可以将MapperScannerRegistrar注册成bean,还可以对实现了特殊接口的类做处理,再看看MapperScannerRegistrar是什么

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
//省略
}
void setResourceLoader(ResourceLoader resourceLoader)
{
//省略
}
//省略
}

MapperScannerRegistrar实现了ResourceLoaderAware接口,可以在实例化的时候得到资源加载器ResourceLoader。另外还实现了ImportBeanDefinitionRegistrar,可以得到BeanDefinitionRegistry,拥有注册BeanDefinition的能力。

再从spring的主流程来说,在spring容器初始化后,会调用容器的refresh方法,然后经过一系列的调用后,会执行MapperScannerRegistrar的registerBeanDefinitions方法,开始mybaits注册BeanDefinition之旅了。

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//省略
List<String> basePackages = new ArrayList<>();
//省略
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
//省略
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}

上面的代码主要是为了拿到我们定义的basePackages值,然后就执行scanner.doScan方法开始扫描符合条件的类,然后注册成BeanDefinition。注册完成后,mybaits还会对BeanDefinition列表执行processBeanDefinitions方法

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
//省略
definition.setBeanClass(this.mapperFactoryBeanClass);
//省略
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
//省略
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
//省略
}
}

从上面可以看到,mybaits在processBeanDefinitions里面将BeanClass设置为mapperFactoryBean,还会挂sqlSessionFactory和sqlSessionTemplate到属性中。这样之后,我们定义的DAO接口就静静地躺着容器中等待getBean了。

定义的DAO接口怎么变成实例

上面已经大概描述了DAO接口如何变成BeanDefinition,但是如果要拿到实例,还要经历一个getBean的过程,一般的BeanDefinition只需要通过反射就可以变成实例了。但是mybaits注册的BeanDefinition类型都改成mapperFactoryBean,如下

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
//省略
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
//省略
}

从上面看到MapperFactoryBean实现FactoryBean,这样的BeanDefinition不再通过反射拿到实例了,而是通过FactoryBean.getObject()获取。所以这个时候,DAO接口实例化的处理由Spring交给Mybaits的getMapper负责了。getMapper处理如下

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
} //mapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

在上面代码中,mybaits为接口生成了代理。而我们需要关注的是生成了什么代理,MapperProxy的代码如下

public class MapperProxy<T> implements InvocationHandler, Serializable {

 //省略
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}

MapperProxy代理的都是接口,所以invoke执行实际上就是MapperMethod的execute执行,如下

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

上面的代码简单来说就是,mybaits会根据mapper.xml中定义的标签,做不同的处理,然后将结果返回。

小结

本文简单描述一个DAO接口如何在spring和mybaits中一步步变成一个可以调用的实例。其中ImportBeanDefinitionRegistrar和FactoryBean是关键点,mybaits靠这两个关键类集成到spring中。如果我们要做一个类似框架集成到spring,也可以学习一下这种方式。

Mybaits和Spring的那点事的更多相关文章

  1. Mybaits整合Spring

    整合思路 1.SqlSessionFactory对象应该放到spring容器中作为单例存在. 2.传统dao的开发方式中,应该从spring容器中获得sqlsession对象. 3.Mapper代理形 ...

  2. 关于Spring的那点事

    一.Spring约束 <?xml version="1.0" encoding="UTF-8"?><beans xmlns="htt ...

  3. Mybaits整合Spring自动扫描 接口,Mybaits配置文件.xml文件和Dao实体类

    1.转自:https://blog.csdn.net/u013802160/article/details/51815077 <?xml version="1.0" enco ...

  4. 以Spring整合EhCache为例从根本上了解Spring缓存这件事(转)

    前两节"Spring缓存抽象"和"基于注解驱动的缓存"是为了更加清晰的了解Spring缓存机制,整合任何一个缓存实现或者叫缓存供应商都应该了解并清楚前两节,如果 ...

  5. spring框架的学习->从零开始学JAVA系列

    目录 Spring框架的学习 框架的概念 框架的使用 Spring框架的引入 概念 作用 内容 SpringIOC的学习 概念 作用 基本使用流程 SpringIOC创建对象的三种方式 通过构造器方式 ...

  6. 全面分析 Spring 的编程式事务管理及声明式事务管理

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

  7. (原创)mybatis学习二,spring和mybatis的融合

    mybatis学习一夯实基础 上文介绍了mybatis的相关知识,这一节主要来介绍mybaits和spring的融合 一,环境搭建 1,jar包下载,下载路径为jar包 2,将包导入到java工程中 ...

  8. spring源码学习之路---AOP初探(六)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...

  9. mybatis0211 mybatis和spring整合

    1mybatis和spring整合 1.1 mybaits和spring整合的思路 .让spring管理SqlSessionFactory .让spring管理mapper动态代理对象和dao. 使用 ...

随机推荐

  1. JS中数组与对象的遍历方法实例小结

    一.数组的遍历: 首先定义一个数组 1 arr=['snow','bran','king','nightking']; 1.for循环,需要知道数组的长度; 2.foreach,没有返回值,可以不知道 ...

  2. SpringBoot面试题

    详见:https://www.cnblogs.com/3xmq/p/springboot.html https://blog.csdn.net/yuzongtao/article/details/84 ...

  3. DNS缓存失败怎么解决?

    DNS的中文名是域名系统,是域名和IP地址相互映射的一个分布式数据库.有了DNS,我们上网时直接输入网站域名(即网址)即可,而不用输入网站的IP地址访问网站,对于用户来说比较方便记忆和访问. 每次当我 ...

  4. struct:二进制数据结构的打包与解包

    介绍 struct模块包括一些函数,这些函数可以完成字节串与原生Python数据类型(如数字和字符串)之间的转换 函数与Struct类 struct提供了一组处理结构值的模块级函数,另外还有一个Str ...

  5. /build-impl.xml:1030: The module has not been deployed.(netbean javaweb)

    我在netbean上创建了一个javaweb,这个项目创建成功了,但是运行时却有了错误,错误贴图如下 报错: The module has not been deployed.  See the se ...

  6. Asp.Net Zero轻量级审核流设计

    复杂的业务系统中往往会集成工作流或审核流,但有些轻量及的业务系统对这些功能的需求并不大,有的系统甚至只需要审核功能就够了.这里给大家介绍在Asp.Net Zero中通用轻量及审核流设计,功能具备审核权 ...

  7. 在配置tensorflow时踩的无数个坑

    在下午尝试配置tensorflow环境时,遇到了许多天坑,讲真的心态炸了好几次,特此写下这篇记录,希望能给看到朋友一点帮助. 先说一下这抓狂的一天的起因,比赛项目想用SVM进行一下数据分析,除了常规的 ...

  8. web页面ios浏览器img图片的坑

    大家都知道ios浏览器有个默认的内置事件,就是长按屏幕,会放大预览你点击的内容. 不同浏览器效果各异,有的浏览器弹出提示框,预览图片或保存图片.主要呈下面2中形式.体验最差的就是后者,会直接将页面中的 ...

  9. [Functional Programming] propSatisfies with implies

    // implies :: ((a -> Boolean), (a -> Boolean)) -> a -> Boolean const implies = (p, q) =& ...

  10. .Net面向对象(OOP)

    序言 virtual虚方法 virtual 关键字用于在基类中修饰方法.virtual的使用会有两种情况: 情况1:在基类中定义了virtual方法,但在派生类中没有重写该虚方法.那么在对派生类实例的 ...