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. 使用 ...
随机推荐
- ubuntu更改apt源为阿里云源
https://blog.csdn.net/zhangjiahao14/article/details/80554616 https://yq.aliyun.com/articles/704603 h ...
- 第十章、typing模块
目录 第十章.typing模块 一.说明 二.typing模块的作用 三.使用typing模块 四.typing常用类型 第十章.typing模块 一.说明 二.typing模块的作用 类型检查,防止 ...
- BLE 5协议栈-主机控制接口(HCI)
文章参考自:http://www.sunyouqun.com/2017/04/page/3/ .https://www.cnblogs.com/yuqilihualuo/p/9790164.html ...
- springboot集成redis使用redis作为session报错ClassNotFoundException类RememberMeServices
springboot 集成redis使用redis作为缓存,会报错的问题. 错误信息: java.lang.IllegalStateException: Error processing condit ...
- (九)zabbix监控web应用
1)web应用监控介绍 使用zabbix自带的web场景可以监控url的状态码,响应时间,url的下载速度,非常的棒 思路:定义模板-->创建应用集--->定义web场景--->定义 ...
- nginx跨站访问,防盗链
跨站访问 从网站A利用AJAX跨站访问网站B 浏览器会根据服务端返回的头部信息(Access-Control-Allow-Origin)判断是否允许跨域访问.如果服务端都允许跨站访问,浏览器段也就没必 ...
- django之ORM的查询优化、Ajax 06
目录 ORM查询优化 only与defer select_related与prefetch_related查询优化 choices参数 MTV与MVC模型 Ajax简介 AJAX常见应用情景 AJAX ...
- SQL 修复表
alter database dcdata set single_user with rollback immediate设置为单用户模式然后执行修复.DBCC CHECKTABLE(ZLBZSGPH ...
- Codeforces 838E Convex Countour
题 OvO http://codeforces.com/contest/838/problem/E (IndiaHacks 2nd Elimination 2017 (unofficial, unra ...
- Luogu P4139 上帝与集合的正确用法
题目链接:Click here Solution: 这道题就考你会不会扩展欧拉定理,根据扩展欧拉定理可知 \[ a^b \equiv a^{(b\,mod\,\varphi(p))+\varphi(p ...