mybatis源码分析-利用JDK动态代理分析源码-JDK动态代理在mybatis中的应用

前言:

Start:2020.4.25 End2020.11.4 1:24

在看本文章前,需要你对JDK动态代理有所了解,
我的另一篇文件种详细讲解了JDK动态代理源码
地址:https://blog.csdn.net/qq_37391371/article/details/109460734

内容:

mybatis源码分析+手写简化版mybatis

[根据目录选择你需要的内容查看]

Mybatis源码

源码流程宏观

image-20201029210011300

Mybatis替代了传统JDBC实现了相同的功能,那么mybatis也一定有经历了以上与数据库交互的必要操作:

  1. 数据源信息从哪里来?

    解析mybatis的主配置文件得到DOM文档对象模型,根节点存入Configuration对象中,其属性储存每一个元素节点,属性节点和文本节点,其属性名与主配置文件中的标签名相同

  2. sql语句从哪里来?

    解析mybatis的主配置文件拿到mapper映射文件,根据namespace+id生成dao接口的实现类代理对象

  3. 与数据库交互操作如何实现?

    dao接口实现类代理对象属性中装配有数据源,可以连接数据库;其方法是根据mapper映射文件实现的,每个方法有其要执行的sql,并对返回值进行了解析.

源码流程微观

  public static void main(String[] args) throws IOException {
    String resource = "mybatis.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    
    
    //核心代码1    						//build(inputStream)方法执行到最后关闭了流
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

    //核心代码2
    SqlSession sqlSession = factory.openSession();

    //核心代码3
    		//方式一:类似原生jdbc的preparedStatement.execute()
    Blog blog = session.selectOne("xzq.BlogMapper.selectBlog", 1);
		    
    		//方式二:利用Dao接口实现类的代理对象(这种方式留给给后面单独探究)
    //BlogMapper blogMapper = session.getMapper(BlogMapper.class);
    //Blog blog = blogMapper.selectBlog(1);
  }

SqlSessionFactoryBuilder.build(is)

Mybatis主配置文件流---------->sqlSessionFactoryBuild.build(流)核心操作--------->返回一个Sql会话工厂

因为build(is)设计到流的操作,可以推测build(is)解决的问题如下:

  1. 如何解析mybatis主配置文件中的标签内容所包含的信息?
  2. 解析出信息后又是如何保存在内存中的?
  3. 信息保存在内存中后又根据这些信息做了什么工作?
  4. 解析Environment标签和Mapper标志最为重要,因为这两个标签包含的信息涉及到建立数据库连接和执行SQL语句
//核心代码1
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

image-20201030211719006

image-20201030212150478

核心操作是什么?就是对主配置文件进行解析,取出所有配置信息封装进Configuration对象及其属性存入内存

image-20201031141831969

解析数据源:

image-20201031140752764
image-20201030224056993

一、mybatis是如何获取数据库源的
org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)
》org.apache.ibatis.builder.xml.XMLConfigBuilder.parse
》org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration
》org.apache.ibatis.builder.xml.XMLConfigBuilder.environmentsElement
》org.apache.ibatis.builder.xml.XMLConfigBuilder.dataSourceElement
》org.apache.ibatis.session.Configuration.setEnvironment#######

解析Mapper:

给Dao接口方法指定SQL语句方法有哪几种?

  1. mapper映射文件中:namespase-id+SQL语句

    image-20201031143946975
  2. 给dao接口方法上标注解
    image-20201104000935520

Mappers文件有几种方式???

image-20201030214725278

image-20201031142456786

image-20201031143248462

image-20201031144705159

源码分析–Mappers标签的处理

package和mapperClass都是加载基于注解的Dao接口

image-20201031213225934

不管是通过拿种方式( )指定mapper(xml映射文件,Dao接口),上面这段代码表明,mybatis都会优先尝试操作mapper.xml映射文件

像mybatis主配置文件又XMLConfiguration类处理一样,mapper映射文件由XMLMapperBuilder处理

image-20201031150228220

二、mybatis是如何获取SQL语句

org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)
》org.apache.ibatis.builder.xml.XMLConfigBuilder.parse
》org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration
》org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement
》org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement
》org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode
>org.apache.ibatis.session.Configuration.addMappedStatement######

sqlSessionfactory.openSession();

//核心代码2
SqlSession sqlSession = factory.openSession();

根据Configuration对象的属性environment,创建一个sql会话(执行器,事务管理器,数据源信息),通过sql会话我们与数据库创建连接进行通信

image-20201030224900658

从数据源中开启一个sql会话

image-20201030225050817

image-20201030231939190

image-20201030232931203

mybatis执行器如下

image-20201031151642361

image-20201030124953705

image-20201031152300873

总结:
openSession()方法中对DOM中的environment元素节点中的信息成分利用.并最终实例化了一个装配有事务管理器SQL语句执行器的SQL会话(数据源->事务->执行器=sql会话)

Mybatis如何执行Sql语句源码

方式一:SqlSession中的通用模块方法
//核心代码3
		//方式一:mybatis封装的通用模板方法,类似原生jdbc的preparedStatement.execute()
Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 1);

注意上面代码没有生成Dao接口实现类的代理对象,执行过程类似于原生JDBC的preparedStatement.execut()

image-20201031192210425

image-20201031193245394

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u3WOCN53-1604422499008)(C:\Users\80685\AppData\Roaming\Typora\typora-user-images\image-20201031193603213.png)]

image-20201031193754573

image-20201031194034173image-20201031194604738

image-20201031195113505

image-20201031200236577

三、通过SqlSession中的通用模板方法执行sql的过程

org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession()
   》org.apache.ibatis.session.Configuration.newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
    》org.apache.ibatis.executor.SimpleExecutor
     》org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(java.lang.String, java.lang.Object)
      》org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(java.lang.String, java.lang.Object)
        》org.apache.ibatis.executor.CachingExecutor.query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
          》org.apache.ibatis.executor.CachingExecutor.query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
           》org.apache.ibatis.executor.BaseExecutor.queryFromDatabase
             》org.apache.ibatis.executor.SimpleExecutor.doQuery
               》org.apache.ibatis.executor.statement.PreparedStatementHandler.query
                 》org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets

preparestatement.execute() 最终"关闭"资源,sqlsession.selectOne()底层就是远程jdbc.

以上步骤流程图

image-20201030142107727

方式二:Dao接口实现类的代理对象
		//发送二:利用Dao接口实现类的代理对象
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
Blog blog = blogMapper.selectBlog(1);

image-20201104003546314

image-20201104003849270

详细查看一下这个mapperProxy的信息:

image-20201103184013606

image-20201103184155513

先总结一下:

JDK动态代理 Proxy.newInstance(classLoader,interfacesType,invocationHandler)

  • interfaceTypes:BlogMapper.class

  • invocationHandler:mapperProxy

但是目标类是谁?目标了的目标方法肯定是参与执行sql语句的.

哪一个对象有这个功能?没错,就是sqlsession,它就是目标类对象

但是目标对象和代理对象要实现相同的接口,sqlsession实现了BlogMapper接口?

image-20201103190011004

来看看JDK动态代理的步骤

image-20201102232226192

一样的顺序!只不过人家通过了InvocationHandler实现类就是MapperProxy!

Blog blog = blogMapper.selectBlog(1);

image-20201103191456402

image-20201103191819110

总结:

首先方式二与方式一没有很大区别,底层一样!

底层?

其实劳了一圈经过JDK动态代理,最后还是调用sqlSesion.selectOne(sql,param)方法!

org.apache.ibatis.session.defaults.DefaultSqlSession.getMapper

  • org.apache.ibatis.session.Configuration.getMapper

    • org.apache.ibatis.binding.MapperRegistry.getMapper

      • org.apache.ibatis.binding.MapperProxyFactory.newInstance(SqlSession)

        • Proxy.newProxyInstance(mp.getClassLoader(),new Class[]{ mapperInterface },mapperProxy);

          [Spring AOP cglib java动态代理]

          • org.apache.ibatis.binding.MapperProxy.invoke

Mybatis底层是JDK动态代理,设计到JDK动态代理就有如下问题:

  1. 接口? Dao接口
  2. 目标类? SqlSession
  3. InvocationHandler实现类? MapperProxy
  4. 代理类对象(JDK动态代理帮我们在内存中生成)

手写简易版Mybatis

根据以上源码总结,手写如下简化版Mybatis.

package xzq;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Select {

    String[] value();
}
package xzq;


//1.接口
public interface UserMapper {

    @Select({"select id,name from user where id = #{id}"})
    public User getUserById(int id);
}

package xzq;



import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// invocationHandler实现类
public class MapperProxy implements InvocationHandler {

    SqlSession targetObject;

    public MapperProxy(SqlSession targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Select annotation = method.getAnnotation(Select.class);
        String sql = annotation.value()[0];

        System.out.println("sql语句是:"+sql+"----参数是:"+args[0]);

        Object user = method.invoke(targetObject, args[0]);

        return user;
    }
}

实体类:对应数据库表中一条记录

package xzq;

//实体类
public class User {
    public int id;
    public String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

测试类:

package xzq;

public class HandWriteMybatisTest {
    public static void main(String[] args) {
        //1.创建目标类对象
        SqlSession sqlSession = new SqlSession();

        //2.创建InvocationHandler实现类(同时传入目标类对象)  这个步骤在mybatis中是内部实现的,我们这里也在sqlsession内部实现

        //3.获取代理对象
        UserMapper UserMapper = (xzq.UserMapper) sqlSession.getMapperProxy(UserMapper.class);

        //执行操作
        User user = UserMapper.getUserById(1);
        System.out.println(user);
    }
}

image-20201103205300891

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值