Mybaits和Spring的那点事
前言
在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的那点事的更多相关文章
- Mybaits整合Spring
整合思路 1.SqlSessionFactory对象应该放到spring容器中作为单例存在. 2.传统dao的开发方式中,应该从spring容器中获得sqlsession对象. 3.Mapper代理形 ...
- 关于Spring的那点事
一.Spring约束 <?xml version="1.0" encoding="UTF-8"?><beans xmlns="htt ...
- Mybaits整合Spring自动扫描 接口,Mybaits配置文件.xml文件和Dao实体类
1.转自:https://blog.csdn.net/u013802160/article/details/51815077 <?xml version="1.0" enco ...
- 以Spring整合EhCache为例从根本上了解Spring缓存这件事(转)
前两节"Spring缓存抽象"和"基于注解驱动的缓存"是为了更加清晰的了解Spring缓存机制,整合任何一个缓存实现或者叫缓存供应商都应该了解并清楚前两节,如果 ...
- spring框架的学习->从零开始学JAVA系列
目录 Spring框架的学习 框架的概念 框架的使用 Spring框架的引入 概念 作用 内容 SpringIOC的学习 概念 作用 基本使用流程 SpringIOC创建对象的三种方式 通过构造器方式 ...
- 全面分析 Spring 的编程式事务管理及声明式事务管理
开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...
- (原创)mybatis学习二,spring和mybatis的融合
mybatis学习一夯实基础 上文介绍了mybatis的相关知识,这一节主要来介绍mybaits和spring的融合 一,环境搭建 1,jar包下载,下载路径为jar包 2,将包导入到java工程中 ...
- spring源码学习之路---AOP初探(六)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...
- mybatis0211 mybatis和spring整合
1mybatis和spring整合 1.1 mybaits和spring整合的思路 .让spring管理SqlSessionFactory .让spring管理mapper动态代理对象和dao. 使用 ...
随机推荐
- css强制换行显示省略号之显示两行后显示省略号
1,首先来一个固定宽度,在一行显示,超出隐藏,显示省略号的样式 display:block; white-space:nowrap; overflow:hidden; text-overflow:el ...
- Java高并发程序设计学习笔记(四):无锁
转自:https://blog.csdn.net/dataiyangu/article/details/86440836#1__3 1. 无锁类的原理详解简介:1.1. CAS1.2. CPU指令2. ...
- layDate面板出现红色花纹图案
要使用layDate,有两种方法: 1. 要么在引用layui.js和layui.css,然后通过layui.use('laydate', callback) 加载模块后,调用方法使用. 2. 去la ...
- PCB检查步骤
1.原理图先每个模块都检查一边. 2.特别注意容易接错的信号线,比如RX,TX是否接反了. 3.检查容易出错的封装和新封装.比如三极管的管脚是否与实物对应.连接器等的封装是否忘记了镜像. 4.分层查看 ...
- Linux系统的grub和BIOS加密
启动流程: BIOS ——> MBR(主引导记录,找到系统在哪个分区) ——> boot loader(grub) ——>kernel(内核)——>进入启动级别init 硬盘的 ...
- Java语言基础(14)
1 访问控制修饰符(二) 1)public:公共的,可以用来修饰类,属性,构造方法以及方法,被public修饰的类,属性,构造方法以及方法,可以任意的进行访问. 2)private:私有的,可以用来修 ...
- zencart分类页每页显示产品数量自定义选择的方法
zencart默认分类页每页显示产品数量是固定的,如何让顾客可以选择每页显示的产品的数量呢?效果图 方式一:全部展示 方式二:下拉菜单 修改方法 1.导入sql INSERT INTO configu ...
- 在RecyclerView中集成QQ汽泡二
上次已经将GooView集成到RecyclerView当中了[http://www.cnblogs.com/webor2006/p/7787511.html],但是目前还有很多问题,下面先来运行看一下 ...
- Loadrunner:录制APP脚本
一.问题 使用 Loadrunner 12 自带的代理进行录制APP脚本时,遇到了各种阻碍 二.解决途径 使用 Fiddler 抓包后导出成 saz 格式文件,再导入到 Loadrunner 中,完美 ...
- 前端每周清单第 49 期:Webpack 4 Beta 尝鲜,React Windowing 与 setState 分析,Web Worker 实战
前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点:分为新闻热点.开发教程.工程实践.深度阅读.开源项目.巅峰人生等栏目.欢迎关注[前端之巅]微信公众号(ID: fron ...