对于入门程序的流程分析

使用过程

  1. 读配置文件

    读取配置文件时绝对路径和相对路径(web工程部署后没有src路径)都有一定问题,实际开发中一般有两种方法

    • 使用类加载器,它只能读取类路径的配置文件
    • 使用SerbletContext对象的getRealPath()
  2. 创建SqlSessionFactory工厂,使用了建造者模式(Builder Pattern)

  3. 使用工厂生产SqlSession对象,使用了工厂模式(Factory Pattern)

  4. 使用SqlSession创建Dao接口的代理对象,使用了代理模式(Proxy Pattern)

  5. 使用代理对象执行方法

  6. 释放资源

底层调用jdbc的流程,即自定义dao中selectList()方法的执行流程,也是代理对象增强的逻辑

  1. 注册驱动,获取Connection对象(需要数据库信息)

    • 通过SqlMapConfig.xml的数据库信息,解析xml文件用到的是dom4j技术
  2. 获取预处理对象PreparedStatement(需要sql语句)
    • 通过SqlMapConfig.xml中的mapper标签定位UserDao.xml,映射配置文件中有sql语句
  3. 执行查询,得到结果集ResultSet
  4. 遍历结果集用于封装
    • 根据UserDao.xml中的resultType反射获得User对象,User对象的属性名和表中列名一致,可以一一封装进user对象中,再把user对象封装进list中
  • 所以,要想让selectList()执行,需要提供两个信息,连接信息和映射信息,映射信息包括sql语句和封装结果的实体类的全限定类名,所以映射信息可以用map存储

创建代理对象流程

  • 根据dao接口的字节码创建dao的代理对象

    public <T> T getMapper(Class<T> daoInterfaceClass){
    /*
    类加载器,使用和被代理对象相同的类加载器
    接口数组,和被代理对象实现相同的接口
    增强逻辑,自己提供,此处是一个InvocationHandler接口,需要写一个该接口的实现类,在其中调用selectList()方法
    */
    Proxy.newProxyInstance(类加载器, 接口数组,增强逻辑);
    }

原理分析:模拟Mybatis各组件实现入门程序

  • 分析用到的类

    public void test(){
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(in);
    SqlSession session = factory.openSession();
    UserDao userDao = session.getMapper(UserDao.class);
    List<User> users = userDao.findAll();
    for(User user: users) System.out.println(user);
    session.close();
    in.close();
    }
  • 解决读取配置信息的问题

    读取xml文件具体实现可以使用dom4j和xpath技术,坐标名dom4j和jaxen

    //模拟Mybatis的配置类
    public class Configuration{
    private String driver;
    private String url;
    private String username;
    private String password;
    /*
    mappers中的数据结构形如
    -------------------------------------------------------------------------
    key | value
    -----------------------------------|-------------------------------------
    "com.whteway.dao.UserDao.findAll()"| queryString:"select * from user"
    | resultType:"com.whteway.domain.User"
    -----------------------------------|-------------------------------------
    "com.whteway.dao.StuDao.findAll()" | queryString:"select * from stu"
    | resultType:"com.whteway.domain.Stu"
    -----------------------------------|-------------------------------------
    "package.daoInterface.method()" | queryString: SQL语句
    | resultType: 实体类的全限定类名
    */
    private Map<String Mapper> mappers;
    //to generate getters and setters exclude setMappers;
    public void setMappers(Map<String, Mapper> mappers){
    this.mappers.putAll(mappers); //追加而不是覆盖
    }
    }
    //用于封装映射配置文件的信息,SQL语句和封装实体类的全限定类名
    //SqlMapConfig.xml的mappers标签中的每一个mapper标签对应一个Mapper对象,
    //也就是说第一个映射配置文件对应一个Mapper对象
    public class Mapper{
    private String queryString;
    private String resultType;
    //to generate getters and setters;
    }
    //读取xml文件的工具类
    public class XMLConfigBuilder{
    //读取SqlMapConfig.xml文件并调用方法读取映射配置文件或加注解的类,将配置信息存入Configuration中并返回
    public static Configuration loadConfiguration(InputStream is){
    Configuration config = new Configuration();
    //1.读取SqlMapConfig文件,将数据库连接信息存入config
    /*2.根据SqlMapConfig文件中的mappers,属性循环读取每一个映射配置文件
    如果用的是resource属性
    mappers = loadMapperResource(resource.value);
    如果用的是class属性
    mappers = loadMapperAnnotation(class.value);
    使用追加的方式将mappers存入config.mappers中
    config.setMappers(mappers);
    */
    return config;
    }
    //读取映射配置文件
    public static Map<String, Mapper> laodMapperResource(String daoClassPath) throws Exception{
    Map<String, Mapper> mappers = new HashMap<String, Mapper>();
    /*
    1 循环读取每一个映射配置文件,如UserDao.xml
    2 读取mappers标签的namespace
    3 循环mappers标签中的每一个子标签(对应一个方法)
    4 读取id,组成namespace.id形式的字符串,作为key
    5 读取sql语句存入Mapper中的queryString
    6 读取resultType存入Mapper中的resultType
    7 将Mapper作为value,与key组成键值对,存入mappers中
    */
    return mappers;
    }
    //读取加注解的类
    public static Map<String, Mapper> laodMapperAnnotation(String daoClassPath) throws Exception{
    Map<String, Mapper> mappers = new HashMap<String, Mapper>();
    /*
    1.得到dao接口的字节码对象
    2.循环每一个方法(对应一个Mapper)
    3. 判断是否有select注解,如果有
    4. 获取各种数据封装到一个Mapper中
    包名,类名,@Select的value值,返回类型的泛型的实际类型,方法名
    5. 把Mapper存入mappers中
    */
    return mappers;
    }
    }
  • Resources

    //将文件名转换为输入流
    public class Resources{
    //根据传入的参数,返回一个输入流
    //1.Resources.class 获取当前类的字节码
    //2.getClassLoader() 获取这个字节码的类加载器
    //3.getResourceAsStream(filePath) 根据类加载器读取文件配置
    public static InputStream getResourceAsStream(String filePath){
    return Resources.class.getClassLoader().getResourceAsStream(filePath);
    }
    }
  • SqlSessionFactoryBuilder 读取配置文件将配置信息存入配置类中的代码在此类中调用

    public class SqlSessionFactoryBuilder{
    //根据参数的字节输入流来构建一个SqlSessionFactory工厂
    public SqlSessionFactory build(InputStream config){
    Configuration cfg = XmlConfigBuilder.loadConfiguration(config);
    return new DefaultSqlSessionFactory(config);
    }
    }
  • SqlSessionFactory

    //接口
    public interface SqlSessionFactory{
    public SqlSession openSession();
    }
    //实现类
    public class DefaultSqlSessionFactory implements SqlSessionFactory{
    private Configuration cfg;
    //实例化此类必须传入配置信息
    public DefaultSqlSessionFactory(Configuration cfg){
    this.cfg = cfg;
    }
    //创建一个新的操作数据库的对象
    @Override
    public SqlSession openSession(){
    return new DeaultSqlSession(cfg);
    }
    }
  • SqlSession jdbc代码调用全在此类和此类的代理对象中

    //接口,和数据库交互的核心类
    public interface SqlSession{
    //创建代理对象,参数是dao接口的字节码
    public <T> T getMapper(Class<T> daoInterfaceClass);
    //释放资源
    public void close();
    }
    //实现类
    public class DefaultSqlSession implements SqlSession{
    private Configuration cfg;
    private Connection conn;
    //实例化此类必须传入配置信息
    public DefaultSqlSession(Configuration cfg){
    this.cfg = cfg;
    conn = DataSourceUtil.getConnection(cfg);
    //使用传统JDBC方式获取连接,创建数据源,建议封装到一个工具类中,源码中叫executor
    try{
    Class.forName(cfg.getDriver());
    conn = DriverManager.getConnection(cfg.getUrl(), cfg.getUserName(), cfg.getPassword())
    }catch(Exception e){
    throw new RuntimeException(e);
    }
    } @Override
    public <T> T getMapper(Class<T> daoInterfaceClass){
    return Proxy.newProxyInstance(daoInterfaceClass.getClassLoader()), new Class[]{daoInterfaceClass}, new MapperProxy(cfg.getMappers()));
    } //关闭Connection
    public void close(){
    if(conn != null){
    try{
    conn.close();
    }catch(Exception e){
    e.printStackTrace();
    }
    }
    }
    }
    //作为创建代理对象时的第三个参数,用于对方法进行增强,此处的增强只需要调用selectList()方法
    public class MapperProxy implements InvocationHandler{
    //接收参数
    private Map<String, Mappers> mappers;
    private Connection conn;
    public MapperProxy(Map<String, Mapper> mappers, Connection conn){
    this.mappers = mappers;
    this.conn = conn;
    }
    //定义增强逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
    //获取方法名
    String methodName = method().getName();
    //获取方法所在的类的名称
    String className = method.getDeclaringClass().getName();
    //组合key
    String key = className + "." + methodName();
    //获取mappers中的mapper对象
    Mapper mapper = mappers.get(key);
    //判断参数是否合法
    if(mapper == null)
    throw new IllegalArgumentException("传入参数有误");
    //使用传统JDBC方式执行SQL语句并封装到实体类列表中返回,建议作为selectList()方法封装到工具类中
    selectList();
    return null;
    }
    }

session.selectList()源码分析

//UserDaoImpl.java
public List<User> findAll(){
session.selectList("com.whteway.dao.UserDao.findAll");
}
//DefaultSqlSession.java
public <E> List<E> selectList(String statement){
this.selectList(statement, null);
}
public <E> List<E> selectList(String statement, Object parameter){
this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds){
try{
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapColeection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e){ } finally {
ErrorContext.instance().reset();
}
}
//CachingExecutor.java
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException{
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException{
...
return delegate.<E> query(ms, parameterObect, rowBounds, resultHandler, key, boundSql);
}
//BaseExecutor.java...
//经过一系列的调用,会走到PreparedStatementHandler类中的一个query方法
//PreparedStatementHandler.java
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException{
//出现JDBC代码
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
//DefaultResultSetHandler.java
public List<Object> handleResultSets(Statement stmt) throws SQLException{
//封装结果集的代码
}

Mybatis 原理分析的更多相关文章

  1. MyBatis原理分析

    MyBatis原理分析   参考博客: 深入理解mybatis原理: http://blog.csdn.net/luanlouis/article/details/40422941 一 . JDBC的 ...

  2. Mybatis原理分析之一:从JDBC到Mybatis

    1.引言 本文主要讲解JDBC怎么演变到Mybatis的渐变过程,重点讲解了为什么要将JDBC封装成Mybaits这样一个持久层框架.再而论述Mybatis作为一个数据持久层框架本身有待改进之处. 2 ...

  3. Mybatis原理分析一 从JDBC到Mybaits

    1.引言 本文主要讲解JDBC怎么演变到Mybatis的渐变过程,重点讲解了为什么要将JDBC封装成Mybaits这样一个持久层框架.再而论述Mybatis作为一个数据持久层框架本身有待改进之处. 2 ...

  4. MyBatis原理分析之四:一次SQL查询的源码分析

    上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的. 准备工作 Mybatis完成一次SQL查询需要使用的代码如下: ) { ); ) { throw ne ...

  5. MyBatis原理分析之三:初始化(配置文件读取和解析)

    1. 准备工作 编写测试代码(具体请参考<Mybatis入门示例>),设置断点,以Debug模式运行,具体代码如下: )ExecutorType:执行类型,ExecutorType主要有三 ...

  6. Mybatis原理分析之二:框架整体设计

    1.引言 本文主要讲解Mybatis的整体程序设计,理清楚框架的主要脉络.后面文章我们再详细讲解各个组件. 2.整体设计 2.1 总体流程 (1)加载配置并初始化       触发条件:加载配置文件 ...

  7. MyBatis原理系列

    原理分析之一:从JDBC到Mybatis 原理分析之二:框架整体设计 原理分析之三:初始化(配置文件读取和解析) 原理分析之四:一次SQL查询的源码分析

  8. MyBatis原理总结(代码实现流程)

    我们在实际开发中,越简单越好,所以都是采用不写Dao实现类的方式.不管是使用xml还是直接配置. 但是MyBatis是支持写Dao实现类的 注意sqlSession是这里面的一个灵魂,有很多执行api ...

  9. MyBatis框架及原理分析

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架,其主要就完成2件事情: 封装JDBC操作 利用反射打通Java类与SQL语句之间的相互转换 MyBatis的主要设计目的就 ...

随机推荐

  1. [转]【HttpServlet】HttpServletResponse接口 案例:完成文件下载

    创建时间:6.19 & 6.24 1.案例-完成文件下载 1)  什么情况下会文件下载? 浏览器不能解析的文件就下载 *使用a标签直接指向服务器上的资源 2)什么情况下需要在服务端编写文件下载 ...

  2. webuploader大文件分片,多线程总结

    项目的新需求是用webuploader来做一个多文件,多线程,并且可以进行分块上传的要求,这些在前面的一篇文章当中足够使用了,但是现在又来一个新的需求,要求上传失败的文件进行重新的上传……心里默默说句 ...

  3. node-gyp 在此解决方案中一次生成一个项目。若要启用并行生成,请添加“/m”开关。

    在此解决方案中一次生成一个项目.若要启用并行生成,请添加“/m”开关. MSBUILD : error MSB3428: 未能加载 Visual C++ 组件“VCBuild.exe”.要解决此问题, ...

  4. react.js知识汇总

    首先ract的基本结构 var Input = React.createClass({ getInitialState: function() { return {value: 'Hello!'}; ...

  5. 织梦dedecms后台文件media_add.php任意上传漏洞解决办法

    织梦在安装到阿里云服务器后阿里云后台会提示media_add.php后台文件任意上传漏洞,引起的文件是后台管理目录下的media_add.php文件,下面跟大家分享一下这个漏洞的修复方法: 首先找到并 ...

  6. IE float 双边距

    IE 浮动元素的双边距

  7. linux防止恶意采集攻防战

    这两天ytkah开发的一个中大型项目被人盯上了,网站打开非常慢,查看了一下cpu.内存使用情况,30%左右占用不高,网络上下行就比较大了,IO实时流量达到40MB,IO总流量更是7TB,非常大的数据量 ...

  8. 07-C#笔记-运算符

    1. 支持++和-- 含义和C++中相同 2. 条件运算 同C++ 3. 位运算 ^ 异或 ~ 取反 4. 支持?:运算 5. 特殊 is 判断对象是否为某一类型. If( Ford is Car) ...

  9. mave常用指令

    Maven常用命令有以下几种: 清理 maven clean 测试 maven test 编译 maven compile 打包 maven package 部署 maven install 第一个m ...

  10. MongoDB的安装与简单使用

    一.安装MongoDB的步骤 注:本教程全部统一采用hadoop用户名登录Linux系统,用户名:hadoop 密码:hadoop ​ 首先,在Linux系统中打开一个终端,执行如下命令导入公共秘钥到 ...