前言

在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. JavaWeb【Servlet】

    概念 Servlet是在服务器上运行的小程序.一个Servlet请求对应一个Java类(对应一个Wrapper容器),可以通过请求-响应模式访问这个驻留在内存中的小程序. Tomcat容器等级 上图表 ...

  2. thinkphp5.0 field和with连用的问题

    field在with后面时field会不起作用,会查询数据库中全部的字段: field在with前面时会无法筛选联表的字段 解决办法把with换成join,field和join顺序无所谓 * @par ...

  3. cubase 使用冻结功能 节省电脑资源

  4. zencart清空产品商品实用命令

    TRUNCATE TABLE categories; TRUNCATE TABLE categories_description;TRUNCATE TABLE meta_tags_categories ...

  5. System.nanoTime与System.currentTimeMillis

    System.nanoTime提供相对精确的计时,但是不能用他来计算当前日期.(系统计时器的当前值,以毫微秒为单位) System.currentTimeMillis返回的是从1970.1.1 UTC ...

  6. 阿里云-docker安装rabbitmq及无法访问主页

    一.下载最新的rabbitmq docker pull rabbitmq 二.下载完以后启动容器 docker run -d -p 5672:5672 -p 15672:15672 --name my ...

  7. IntelliJ IDEA导包快捷键

    IntelliJ IDEA导包快捷键 Alt+Enter

  8. ubuntu NAT dhcp

    说明: 1.在服务器版本中,没有想桌面版一样的NetworkManager工具,所以的一切都是在命令行上操作的. 2.本文只针对DHCP默认分配的IP进行查看. 方法: 1.如果要使用DHCP,那么需 ...

  9. JAVA实现图片叠加效果

    import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.awt.image.BufferedImage; imp ...

  10. BZOJ 1107: [POI2007]驾驶考试egz / Luogu P3463 [POI2007]EGZ-Driving Exam (树状数组 LIS)

    能从iii走到所有跑道 相当于 能从iii走到111和nnn. 边反向后就相当于 能从111和nnn走到iii. 为了方便叙述,把111~nnn叫做x坐标,111~(m+1)(m+1)(m+1)叫做y ...