Mybatis源码分析之(三)mapper接口底层原理(为什么不用写方法体就能访问到数据库)

mybatis是怎么拿sqlSession

在 上一篇的时候,我们的SqlSessionFactoryBuilder已经从xml文件中解析出了Configuration并且返回了sessionFactory。

然后我们要从session;中拿到sqlSession

public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;@Overridepublic SqlSession openSession() {//默认情况下ExecutorType是ExecutorType.SIMPLE类型的return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {//获取配置的环境信息final Environment environment = configuration.getEnvironment();//获取environment中的TransactionFactoryfinal TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);//生成Transactiontx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//生成Executor(重要,之后的查询都得靠它)final Executor executor = configuration.newExecutor(tx, execType);//返回DefaultSqlSessionreturn new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
}//DefaultSqlSession类的组成,其实新建的时候就只是把他的字段赋值而已
public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;private boolean autoCommit;private boolean dirty;private List<Cursor<?>> cursorList;
}public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {this.configuration = configuration;this.executor = executor;this.dirty = false;this.autoCommit = autoCommit;}

上面分析的是到拿到sqlSession为止,重点其实不是在上面这里,因为到上面为止,其实主要的功能只是将配置的信息解析成我们要的类,然后进行初始化赋值。

Mapper的实现原理

下面我们从SqlSession中拿到mapper,并执行方法其实才是,你感觉到mybatis框架开始帮我们做事的开始。

    public static void main(String[] args) {//拿到SqlSessionSqlSession sqlsession = MybatisUtil.getSqlsession();//拿mapperTDemoMapper mapper = sqlsession.getMapper(TDemoMapper.class);//调用mapper的方法List<TDemo> all = mapper.getAll();for (TDemo item : all)System.out.println(item);}

因为我们在项目中的TDemoMapper只是一个接口,并没有实现这个接口方法,但是为什么我们在调用这个接口方法的时候就可以得到返回结果呢?mybatis究竟做了什么?

首先我们回到之前解析Mapper的语句

mapperElement(root.evalNode("mappers"));private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) { //根据resource解析ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {//根据url 解析ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {//根据mapperClass解析//首先通过mapperClass的路径,生成mapperClass的接口类Class<?> mapperInterface = Resources.classForName(mapperClass);//降mapperClass加入到configuration中去configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}//Configuration类下public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}//MapperRegistry类下
public class MapperRegistry {private final Configuration config;private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();//最终调用这个方法public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {//将接口包装成MapperProxyFactory类放入knownMappers中(knownMappers就是存放我们的mapper接口的)knownMappers.put(type, new MapperProxyFactory<T>(type));// It's important that the type is added before the parser is run// otherwise the binding may automatically be attempted by the// mapper parser. If the type is already known, it won't try.//通过这个builder来解析mapper的statement。(把mapper和mapper.xml文件相关联,方法名与xml中的id相关联,为了之后调用的时候能找到的语句)MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);//开始解析parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}
}//MapperAnnotationBuilder类中public void parse() {String resource = type.toString();if (!configuration.isResourceLoaded(resource)) {//通过xml文件解析loadXmlResource();configuration.addLoadedResource(resource);assistant.setCurrentNamespace(type.getName());parseCache();parseCacheRef();//获得接口的方法(为了获取方法上的注解,通过注解的方式来让方法于sql语句相关联)Method[] methods = type.getMethods();for (Method method : methods) {try {// issue #237if (!method.isBridge()) {//具体的解析过程parseStatement(method);}} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}parsePendingMethods();}

具体的调用过程就不细跟了,无非就是获取节点,获取属性值,(或者是获取方法,然后获取注解信息),巴拉巴拉……然后设置到configuration中。
上面要注意的点是,若既配置xml又配置注解的情况下,注解会覆盖xml,原因非常简单,源码中注解解析在xml解析后面,然后覆盖的情况是,他们有相同的namespace+id。
然后我们继续我们的主线任务,就是mapper的设计架构。从上面我们可以知道,configuration中有一个MapperRegistry类型的字段mapperRegistry,其中有一个字段叫knownMappers,knownMappers里面存着key为接口类型,值为MapperProxyFactory的。
mapperRegistry

//我们在调用TDemoMapper mapper = sqlsession.getMapper(TDemoMapper.class);的时候
//先调用DefaultSqlSession类下的@Overridepublic <T> T getMapper(Class<T> type) {return configuration.<T>getMapper(type, this);}
//然后调用Configuration类下的public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}//最后调用MapperRegistry类下的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 {//实际生成是这段代码,通过mapperProxyFactory来生成实例对象return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}//实际调用类
public class MapperProxyFactory<T> {private final Class<T> mapperInterface;private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();public T newInstance(SqlSession sqlSession) {//实例化一个代理类final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);//通过这个函数实例化return newInstance(mapperProxy);}protected T newInstance(MapperProxy<T> mapperProxy) {//动态代理的基本操作(说明最终实现方式是动态代理)return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}
}public class MapperProxy<T> implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L;private final SqlSession sqlSession;private final Class<T> mapperInterface;private final Map<Method, MapperMethod> methodCache;public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}//动态代理中最重要的方法invoke@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {//如果是Object中的方法就不走下面的代理了,直接执行(比如toString,hashCode)return method.invoke(this, args);} else if (isDefaultMethod(method)) {//如果不是静态方法而且不是抽象方法,则不增强方法return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}//实际我们的mapper接口的方法走的逻辑就是下面这2条final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}
}

总结

我们通过sqlSession获得mapper方法,而sqlSession从configuration中的mapperRegistry中获取MapperProxyFactory对象,在通过MapperProxyFactory对象的newInstance方法得到MapperProxy的动态代理实例对象。

我们使用的mapper其实是通过MapperProxy动态代理,在运行时候生成的一个新的对象进行方法增强的,里面的接口方法都会通过下面2个语句进行数据库的操作,以及后续对数据的处理

    final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);12

这两条语句其实包含对访问数据库对象的创建,访问数据库到得到数据库返回数据后的处理等内容,非常复杂,本篇就到此为止。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/555897.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql count distinct case when_统计符合条件的去重过的数量 - - count distinct if case

现有表结构&#xff1a;CREATE TABLE example_dataset (id int(11) unsigned NOT NULL AUTO_INCREMENT,tel bigint(11) DEFAULT NULL,gender varchar(11) DEFAULT NULL,PRIMARY KEY (id)) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;插入数据INSERT INTO example_da…

Mybatis源码分析之(六)mybatis拦截器(Interceptor)的实现原理

文章目录前言InterceptorChain保存所有的Interceptor创建四大对象都走ConfigurationInterceptorChain增强对象方法Plugin封装动态代理&#xff0c;让你使用Mybatis拦截器更简单Invocation&#xff0c;让我们能在拦截器中使用动态代理类中的invoke方法中的对象调用时序图小结前言…

oauth2 java 获取token_OAuth2 Token 一定要放在请求头中吗?

Token 一定要放在请求头中吗&#xff1f; 答案肯定是否定的&#xff0c;本文将从源码的角度来分享一下 spring security oauth2 的解析过程&#xff0c;及其扩展点的应用场景。Token 解析过程说明当我们使用 spring security oauth2 时, 一般情况下需要把认证中心申请的 token …

java开发原则_java开发中,大家处理异常的原则是什么,是如何处理的?

展开全部最熟悉的陌生人&#xff1a;异常异常的类e5a48de588b63231313335323631343130323136353331333361326365型Throwable— Exception—- RuntimeException— Error需要注意的是&#xff0c;RuntimeException及其子类不需要在方法签名中显示注明异常抛出。例如&#xff1a;v…

java 线程 spring_java中spring里实现多线程

Spring通过任务执行器(TaskExecutor)来实现多线程和并发编程的可使用ThreadPoolTaskExecutor来实现基于线程池的TaskExecutor在实际开发中由于多是异步&#xff0c;所以使用EnableAsync来支持异步任务&#xff0c;且要在Bean的方法中使用Async来声明其是一个异步任务?????…

出现503错误 怎么办

展开全部 出现503错误原因及解决办法 原因&#xff1a;web服务器不能处理HTTP请求&#xff0c;可能是临时超载或者是服务器进行维护。 解决办法&#xff1a;用户需要等待服务器的临时处理。在这种状态下&#xff0c;一些服务器可以简单的拒绝socket连接&#xff0c;否则会发…

java枚举类中字段有没有必要加final____枚举类字段 Field ‘xxx‘ may be ‘final‘

java枚举类中字段有没有必要加final 今天在写一个系统统一返回码的枚举类时候&#xff0c;突然想到一个问题&#xff0c;当不小心手抖给枚举类自动生成了set方法&#xff0c;而恰巧在用的地方不小心用了set方法&#xff0c;从而修改了code值&#xff0c;由于枚举类是天然单例&a…

MySQL数据库索引及失效场景

文章目录1. MySQL索引概述1.1 索引的概念1.2 索引的特点1.3 索引的分类1.4 索引的使用场景2. 索引失效场景2.1 索引失效9种场景2.2 索引失效场景总结3. 索引失效验证3.1 全值匹配3.2 最佳左前缀3.3 索引计算3.4 索引范围&#xff1a;索引列上不能有范围查询3.5 索引覆盖&#x…

getLong java_java.lang.Long.getLong()方法实例

全屏java.lang.Long.getLong(String nm) 方法确定具有指定名称的系统属性的long值。如果没有具有指定名称的属性&#xff0c;如果指定名称为空或null&#xff0c;或者该属性没有正确的数字格式&#xff0c;则返回null。声明以下是java.lang.Long.getLong()方法的声明public sta…

@JsonProperty注解解析

1. 概述 来源: JsonPrpperty是jackson包下的一个注解&#xff0c;详细路径(com.fasterxml.jackson.annotation.JsonProperty;)作用:JsonProperty用在属性上&#xff0c;将属性名称序列化为另一个名称。例子&#xff1a;public class Person{JsonProperty(value "name&qu…

java swing panel问题_关于 Java swing Box 的使用问题

代码import javax.swing.*;import java.awt.*;public class C5Ex1_2 {final static int WIDTH 400;final static int HEIGHT 400;public C5Ex1_2() {JFrame jf new JFrame("program 1");jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);jf.setSize(WIDTH, HEI…

SpringMVC注解@RequestParam全面解析____ 注解@RequestParam如何使用加与不加的区别

SpringMVC注解RequestParam全面解析 在此之前&#xff0c;写项目一直用的是RequestParam&#xff08;value“aa” requiredfalse&#xff09;这个注解&#xff0c;但是并不知道它的意思。现在懂了&#xff0c;特来记录下。 1、可以对传入参数指定参数名 1 RequestParam Stri…

@requestbody和@requestparam到底什么作用

1、什么都不写 GET 可以自动封装为对象模型&#xff0c;没有的数值自动为0值 POST 请求体里面放了数据&#xff0c;但是还是使用了RequestParam里的数据 总结&#xff1a; 在不使用注解的情况下&#xff0c;相当于默认使用了RequestParam里的数据 &#xff08;这种理解是错…

linux mysql学习_Linux学习笔记(MySql操作)

忘记MySql密码&#xff1a;编辑mysql主配置文件 my.cnf 在[mysqld]字段下添加参数 skip-grant重启数据库服务,这样就可以进入数据库不用授权了 mysql -uroot修改相应用户密码 use mysql;update user setpasswordpassword(密码) where userroot;flushprivileges; (刷新)最后…

注解@RequestParam【不添加默认项注解】与@RequestBody的使用场景

一、前言 一直有这么一个疑问&#xff1a;在使用postman工具测试api接口的时候&#xff0c;如何使用 json 字符串传值呢&#xff0c;而不是使用 x-www-form-urlencoded 类型&#xff0c;毕竟通过 key-value 传值是有局限性的。假如我要测试批量插入数据的接口呢&#xff0c;使用…

SpringMVC参数的传递——接收List数组类型的数据

前言 本文主要是记录SpringMVC中当前台传过来数组的时候&#xff0c;如何把前台传过来的数据封装到Controller层方法的形参中。 在了解下面参数如何传递前先记住两个结论&#xff1a; 当Ajax以application/x-www-form-urlencoded编码格式上传数据&#xff0c;必须使用JSON对…

有了 IP 地址,为什么还要用 MAC 地址?

我认为&#xff0c;IP地址和MAC地址可以类比生活中寄快递的过程。 在整个网络中数据被封装成数据报文进行发送&#xff0c;就像我们生活中寄快递时将物品放进包裹中。而数据在路由器之间的跳转也可以看作是不同地区快递小哥对物流的交接。 IP地址 ip地址等价于快递包裹上的…

java运动员最佳配对_运动员最佳配对问题 - osc_y1pyjby5的个人空间 - OSCHINA - 中文开源技术交流社区...

这道题可以看为排列数的一个典型模块一、算法实现题&#xff1a;1、问题描述&#xff1a;羽毛球队有男女运动员各n人&#xff0c;给定2个nn矩阵P和Q。P[i][j]是男运动员i和女运动员j配对组成混合双打的男运动员竞赛优势&#xff1b;Q[i][j]则是女运动员i和男运动员j配合的女运动…

为什么POJO中变量不能用is开头

一、前言 在阿里编码规约中&#xff0c;有一个约定如下 【强制】POJO 类中的任何布尔类型的变量&#xff0c;都不要加 is 前缀&#xff0c;否则部分框架解析会引起序列 化错误。 但为什么类中的field不能用is开头呢&#xff1f;本文将从问题演示、框架源码&#xff08;本文使用…

什么是RPC?RPC框架dubbo的核心流程

一、REST 与 RPC&#xff1a; 1、什么是 REST 和 RPC 协议&#xff1a; ​ 在单体应用中&#xff0c;各模块间的调用是通过编程语言级别的方法函数来实现&#xff0c;但分布式系统运行在多台机器上&#xff0c;一般来说&#xff0c;每个服务实例都是一个进程&#xff0c;服务…