MyBatis底层实现—getMapper获取到接口的代理对象

本文解析了MyBatis中自动映射器Mapper的底层实现原理,包括使用动态代理实现接口方法调用,及如何避免方法重载带来的问题。通过源码分析,详细解释了getMapper方法的工作流程。
原创

MyBatis的底层实现原理

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.youkuaiyun.com/w372426096/article/details/82622418
         <!--一个博主专栏付费入口结束-->
        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
                                    <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
            <div class="htmledit_views" id="content_views">
                                        <p><strong>动态代理的功能:通过拦截器方法回调,对目标target方法进行增强。</strong></p>

言外之意就是为了增强目标target方法。上面这句话没错,但也不要认为它就是真理,殊不知,动态代理还有投鞭断流的霸权,连目标target都不要的科幻模式。

注:本文默认认为,读者对动态代理的原理是理解的,如果不明白target的含义,难以看懂本篇文章,建议先理解动态代理。

一、自定义JDK动态代理之投鞭断流实现自动映射器Mapper

首先定义一个pojo

再定义一个接口UserMapper.java

接下来我们看看如何使用动态代理之投鞭断流,实现实例化接口并调用接口方法返回数据的。

自定义一个InvocationHandler。

上面代码中的target,在执行Object.java内的方法时,target被指向了this,target已经变成了傀儡、象征、占位符。在投鞭断流式的拦截时,已经没有了target。

写一个测试代码:

output:

这便是Mybatis自动映射器Mapper的底层实现原理。

可能有读者不禁要问:你怎么把代码写的像初学者写的一样?没有结构,且缺乏美感。

必须声明,作为一名经验老道的高手,能把程序写的像初学者写的一样,那必定是高手中的高手。这样可以让初学者感觉到亲切,舒服,符合自己的Style,让他们或她们,感觉到大牛写的代码也不过如此,自己甚至写的比这些大牛写的还要好,从此自信满满,热情高涨,认为与大牛之间的差距,仅剩下三分钟。

二、Mybatis自动映射器Mapper的源码分析

首先编写一个测试类:

Mapper长这个样子:

org.apache.ibatis.binding.MapperProxy.java部分源码。

org.apache.ibatis.binding.MapperProxyFactory.java部分源码。

这便是Mybatis使用动态代理之投鞭断流。

三、接口Mapper内的方法能重载(overLoad)吗?(重要)

类似下面:

Answer:不能。

原因:在投鞭断流时,Mybatis使用package+Mapper+method全限名作为key,去xml内寻找唯一sql来执行的。类似:key=x.y.UserMapper.getUserById,那么,重载方法时将导致矛盾。对于Mapper接口,Mybatis禁止方法重载(overLoad)。

 

文章最后发布于: 2018-09-11 10:56:32

原创

深入MyBatis-运行原理-getMapper获取到接口的代理对象

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.youkuaiyun.com/weixin_40288381/article/details/88566168
         <!--一个博主专栏付费入口结束-->
        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
                                    <div id="content_views" class="markdown_views prism-atom-one-dark">
                <!-- flowchart 箭头图标 勿删 -->
                <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
                    <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
                </svg>
                                        <h4><a id="_0"></a>流程图</h4>

在这里插入图片描述

  1. DefaultSqlSessionF调用getMapper方法,其中为configuration下的getMapper方法
  2. configuration下的getMapper方法,其中为mapperRegistry下的getMapper方法
  3. mapperRegistry下的getMapper方法下根据接口类型获取MapperProxyFactory
  4. MapperProxyFactory调用newInstance生成MapperProxy
  5. 创建MapperProxy的代理对象,一路返回到最初调用的DefaultSqlSession
剖析源码
1.DefaultSqlSessionF调用getMapper方法
//传入接口类
sqlSession.getMapper(StudentMapper.class);

 
  • 1
  • 2
2.调用configutaion的getMapper方法
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

 
  • 1
  • 2
  • 3
3.调用mapperRegistry的getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

 
  • 1
  • 2
  • 3
  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 {
     //此处将返回MapperProxy的代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
4.创建MapperProxy代理对象
 public T newInstance(SqlSession sqlSession) {
 	//生成MapperProxy
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //生成MapperProxy的代理对象,之后一路返回
    return newInstance(mapperProxy);
  }

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
小结

DefaultSqlSession下调用getMapper方法,最终根据传入接口的类型生成MapperProxy工厂,创建MapperProxy,最终返回MapperProxy的代理对象。

                                </div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet">
                </div>
</article>
<div class="postTime"> 
    <div class="article-bar-bottom">
        <span class="time">
            文章最后发布于: 2019-03-15 09:42:06            </span>
    </div>
</div>

原创

2. mybatis为什么直接调用接口方法就可以实现语句的查询

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.youkuaiyun.com/u010805617/article/details/85270379
         <!--一个博主专栏付费入口结束-->
        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
                                    <div id="content_views" class="markdown_views prism-atom-one-dark">
                <!-- flowchart 箭头图标 勿删 -->
                <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
                    <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
                </svg>
                                        <p>上一篇文章我们了解了什么是mybatis,并通过一个用例实现了一个简单的mybatis查询数据库的数据,在文章的最后针对mybatis如何实现这些功能提出了几个问题。接下来的文章中会对这些问题做一一解答。<br>

本篇文章中解决第一个问题,mybatis为什么调用接口方法就可以实现语句的查询?

实现该功能主要分成两块进行:

  1. 注册Mapper
  • 通过new SqlSessionFactoryBuilder().build(inputStream);构建SqlSessionFactory。在builder方法中将配置文件中的内容转换为Configuration中的对象属性。SqlSessionFactory持有Configuration对象的引用。
  1. 获取Mapper,调用接口方法
    mybatis查询通过以下几个关键类和代码实现
  • 通过sqlSession = factory.openSession();创建SqlSession对象
  • 通过TestMapper testMapper = sqlSession.getMapper(TestMapper.class);获取mapper代理对象
  • 通过testMapper.queryTestDO(1));获取数据库结果集

注册部分关键代码:

XmlConfigBuilder.java

 private void 
<think>我们正在讨论MyBatis中XML映射文件如何映射到DAO接口的原理。根据之前的引用,特别是引用[1][2][3][4],我们可以总结出以下关键点:1.**Mapper接口与XML映射文件的对应关系**:-Mapper接口的全限定名(包括包名和接口名)必须与XML映射文件中`<mapper>`标签的`namespace`属性值完全一致。-Mapper接口中的方法名必须与XML映射文件中定义的SQL语句的`id`属性值相同。-方法的输入参数类型和输出参数类型也需要与XML中定义的`parameterType`和`resultType`(或`resultMap`)相匹配。2.**动态代理机制**:-MyBatis在运行时通过动态代理技术为Mapper接口生成代理对象(ProxyObject)。-当调用Mapper接口的方法时,代理对象会拦截方法调用,并根据接口的全限定名和方法名,拼接成一个字符串(如`com.example.MyMapper.selectById`),然后根据这个字符串去查找对应的MappedStatement(即映射的SQL语句)。3.**MappedStatement的作用**:-每一个在XML映射文件中定义的`<select>`,`<insert>`,`<update>`,`<delete>`标签都会被解析为一个MappedStatement对象,其中包含了SQL语句、参数映射、结果映射等信息。4.**绑定过程**:-在MyBatis初始化时,会解析XML映射文件,将解析得到的MappedStatement注册到全局配置对象(Configuration)中,以`namespace.id`作为key。-同时,当解析到一个XML映射文件时,会检查`namespace`属性是否对应一个存在的接口,如果存在,则为该接口生成动态代理类,并将该接口代理工厂注册到MapperRegistry中。5.**方法调用流程**:-当我们通过SqlSession的`getMapper`方法获取Mapper接口的实例时,实际上是从MapperRegistry中获取对应的代理工厂,然后创建代理对象。-当我们调用代理对象的方法时,代理对象会调用`MapperProxy`的`invoke`方法,该方法会根据接口类型和方法信息,获取对应的MappedStatement,并执行相应的数据库操作。现在,我们将根据以上原理详细解释MyBatis底层实现机制,并按照要求进行回答。###MyBatisXML映射文件到DAO接口的映射原理MyBatis的核心设计思想是通过XML配置或注解将Java方法与SQL语句进行解耦,实现SQL和Java代码的分离。其中,XML映射文件与DAO接口的映射机制建立在**动态代理**和**方法签名解析**的基础上。####🔧1.**映射文件解析过程**在MyBatis启动时,配置文件(如`mybatis-config.xml`)中的每个`<mapper>`配置都会被加载:-如果配置的是XML映射文件路径(如`<mapperresource="com/example/UserMapper.xml"/>`),MyBatis会解析该XML文件。-解析时,会将`<mappernamespace="...">`中的`namespace`属性作为接口的全限定名进行验证(是否与某个Java接口对应)。-随后解析XML中的SQL标签(如`<selectid="methodName">`),每个SQL标签被解析为一个`MappedStatement`对象,该对象包含以下关键信息:-`id`:SQL语句的唯一标识,格式为`${namespace}.${id}`(如`com.example.UserMapper.selectById`)。-`sqlSource`:实际的SQL语句(可能包含动态SQL标签)。-`parameterType`:参数类型。-`resultType`或`resultMap`:结果映射。这些`MappedStatement`对象会被存储在全局`Configuration`对象的`mappedStatements`属性中(一个Map结构)[^4]。####🧬2.**接口与XML绑定机制**-**接口合法性验证**:在解析XML过程中,MyBatis会检查`namespace`属性值是否指向一个存在的Java接口。如果存在,则建立映射关系。-**动态代理对象生成**:通过`SqlSession.getMapper(Class<T>type)`获取接口实例时:1.MyBatis从`MapperRegistry`中获取接口对应的`MapperProxyFactory`(代理工厂)[^1]。2.调用`newInstance(MapperProxy<T>)`生成代理对象:```javapublicTnewInstance(SqlSessionsqlSession){finalMapperProxy<T>mapperProxy=newMapperProxy<>(sqlSession,mapperInterface,methodCache);return(T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),newClass[]{mapperInterface},mapperProxy);}```3.生成的代理对象实现了目标接口,并将所有方法调用委托给`MapperProxy`[^1]。####🔌3.**方法调用拦截与路由**当调用代理对象的方法时:1.动态代理拦截器`MapperProxy.invoke()`会执行以下操作:-如果方法是`Object`类中的方法(如`toString`,`equals`),直接调用。-否则,将方法签名(接口名+方法名)作为key,查找对应的`MappedStatement`[^2]。2.根据方法参数类型和XML配置,动态执行以下步骤:-将Java参数转换为SQL参数(通过`TypeHandler`)。-执行SQL(调用`Executor.query()`或`Executor.update()`)。-将结果集转换为返回类型(如`List<User>`或`User`)。####📊4.**映射关系匹配示例假设有以下DAO接口:```javapackagecom.example;publicinterfaceUserMapper{UserselectById(intid);}```对应的XML映射文件(UserMapper.xml):```xml<mappernamespace="com.example.UserMapper"><selectid="selectById"parameterType="int"resultType="com.example.User">SELECT*FROMuserWHEREid=#{id}</select></mapper>```当调用`userMapper.selectById(1)`时:1.代理对象解析方法签名为:`com.example.UserMapper.selectById`。2.在`Configuration.mappedStatements`中查找key为`com.example.UserMapper.selectById`的`MappedStatement`。3.执行SQL语句:`SELECT*FROMuserWHEREid=?`,参数`?`替换为1。####🌐5.**动态代理技术的应用**MyBatis通过JDK动态代理机制实现DAO接口的映射:-**依赖接口而非类**:代理对象基于接口实现,因此要求DAO必须是接口(无实现类),这也是为什么MyBatis的DAO通常定义为接口[^1]。-**方法签名一致性**:方法名称、参数类型、返回值类型必须与XML配置严格匹配,否则会因找不到`MappedStatement`而报错[^3]。####⚙️6.**注解与XML的共存机制**如果同时使用XML和注解配置同一方法,MyBatis的处理逻辑是:-优先使用XML配置的`MappedStatement`(覆盖注解)。-注解配置通过`@Select`,`@Insert`等声明SQL语句,同样会转化为`MappedStatement`存储在全局配置中。####🚀性能优化策略-**`MapperRegistry`缓存**:每个接口代理工厂只创建一次,提升多次调用性能。-**方法级缓存**:`MapperMethod`对象(包含SQL执行逻辑)被缓存在`methodCache`中,避免重复解析方法签名[^1]。####§相关问题1.如果XML中的`namespace`写错,MyBatis会如何处理?2.如何在MyBatis中同时使用XML映射和注解配置?3.MyBatis如何支持重载的DAO方法?4.为什么Dao接口没有实现类,MyBatis却能执行方法?5.如何动态替换MyBatis映射文件中的SQL?---以上原理深刻体现了MyBatis通过**接口抽象**和**动态代理**实现了SQL操作的优雅封装,同时提供了灵活的扩展机制。####参考资料[^1]:MyBatis动态代理机制源码分析:`org.apache.ibatis.binding.MapperProxy`[^2]:XML映射与DAO接口绑定原理:`org.apache.ibatis.session.Configuration#addMapper`[^3]:MappedStatement注册过程:`org.apache.ibatis.builder.xml.XMLMapperBuilder#parse`[^4]:MyBatis官方文档关于XML配置说明:[mybatis.org](https://mybatis.org/mybatis-3/configuration.html)</think> ### MyBatis XML映射到DAO接口实现原理 MyBatis通过**动态代理**和**方法签名映射**实现XML文件到DAO接口的无缝连接,以下是核心实现机制: #### 🔍 一、核心映射机制(基于方法签名) 1. **命名空间绑定** XML中的`<mapper namespace="com.example.UserDao">`必须**完全匹配**DAO接口的全限定名: ```xml <!-- XML映射文件 --> <mapper namespace="com.example.UserDao"> <select id="findById" parameterType="int" resultType="User"> SELECT * FROM users WHERE id = #{id} </select> </mapper> ``` 2. **方法签名映射** 接口方法与XML语句的映射关系: ```java // DAO接口声明 public interface UserDao { User findById(int id); // 方法名匹配XML的id } ``` - **映射键值**:`namespace.id` → `com.example.UserDao.findById` - **参数匹配**:方法参数类型对应XML的`parameterType` - **返回类型**:方法返回类型匹配XML的`resultType/resultMap` #### 🧬 二、动态代理实现流程 1. **初始化阶段(加载映射)** MyBatis启动时解析XML映射文件: ```mermaid graph TB A[XML文件解析] --> B[创建MappedStatement对象] B --> C{存储到<br>Configuration.mappedStatements} C -->|Key = namespace+id| D["Map<String, MappedStatement>"] ``` 2. **接口代理生成** ```java // 通过SqlSession获取DAO代理 UserDao userDao = sqlSession.getMapper(UserDao.class); // 底层实现: public class DefaultSqlSession { public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } } ``` - **代理工厂**:`MapperProxyFactory`创建JDK动态代理对象 - **代理处理器**:`MapperProxy`实现`InvocationHandler`接口 3. **方法调用拦截** ```java // MapperProxy核心逻辑 public Object invoke(Object proxy, Method method, Object[] args) { // 拼接方法全路径:com.example.UserDao.findById String commandKey = method.getDeclaringClass().getName() + "." + method.getName(); // 获取MappedStatement MappedStatement ms = configuration.getMappedStatement(commandKey); // 执行SQL(委托给Executor) return executor.query(ms, args); } ``` #### ⚙️ 三、关键数据结构 1. **MappedStatement 成员** | 属性 | 说明 | XML对应标签 | |------|------|-------------| | `id` | 唯一标识 | `<select>/<update>`等 | | `sqlSource` | SQL语句 | SQL文本 | | `parameterType` | 参数类型 | `parameterType` | | `resultType` | 返回类型 | `resultType` | 2. **Configuration 注册中心** ```java public class Configuration { // 存储所有映射关系 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>(); // 注册Mapper接口 public <T> void addMapper(Class<T> type) { // 验证接口名=namespace // 动态生成代理类 } } ``` #### 💡 四、设计优势解析 1. **解耦实现** - **接口实现类**:开发者只需声明接口方法 - **SQL集中管理**:XML分离业务代码与SQL逻辑 2. **动态扩展能力** 通过`@Select`等注解可实现XML替代方案: ```java @Select("SELECT * FROM users WHERE id = #{id}") User findById(int id); ``` #### ⚠️ 五、常见问题处理 1. **映射失败场景** | 问题类型 | 原因 | |----------|------| | `BindingException` | namespace不匹配或方法名错误 | | `ParameterType`错误 | 接口参数与SQL参数不兼容 | | `ResultType`冲突 | 返回对象字段与SQL列不匹配 | 2. **接口方法重载限制** MyBatis**不支持重载方法**(如`findById(int)`和`findById(String)`),因为: $$ \text{映射键} = \text{namespace} + \text{方法名} $$ 方法名重复会导致映射键冲突 #### 🧪 六、验证实验 可在MyBatis日志开启DEBUG模式观察: ```properties # log4j.properties log4j.logger.org.apache.ibatis=DEBUG ``` 执行时输出: ``` DEBUG - Creating proxy for mapper interface com.example.UserDao DEBUG - Mapped statement: com.example.UserDao.findById ``` ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值