手写Mybatis

(一)Mybatis总体流程

(1)加载配置并初始化触发条件:加载配置文件

配置来源于两个地方,一个是配置文件(主配置文件conf. xml, mapper文件*.xml),一个是java代码中的注解,将主配置文件内容解析封装到Configuration,将sql的配置信息加载成为一个mappedstatement对象,存储在内存之中

(2)接收调用请求

触发条件:调用Mybatis提供的API传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。

(3)处理操作请求

触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象处理过程:

  1. 根据SQL的ID查找对应的MappedStatement对象。
  2. 根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
  3. 获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
  4. 根据MappedStatement对象中的结果映射配置对得到的执行结果进入转换处理,并得到最终的处理结果。
  5. 释放连接资源。

(4)返回处理结果

将最终的处理结果返回。

(二)Mybatis核心构建

核心构建

描述

SqlSession

作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能

Executor

MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护

StatementHandler

封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换成List集合

ParameterHandler

负责对用户传递的参数转换成JDBC Statement所需要的参数

ResultSetHandler

负责将JDBC返回的ResultSet结果集对象转换成List类型的集合

TypeHandler

负责java数据类型和jdbc数据类型之间的映射和转换

MappedStatement

MappedStatement维护了一条<select | update | delete | insert>节点的封装

sqlSource

负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回

BoundSql

表示动态生成的SQL语句以及相应的参数信息

(三)Mybatis层次结构 

(四)源码解析

public class Entry {
    public static void main(String[] args) {
        MtConfiguration configuration = new MtConfiguration("mybatis-config.properties");
        MtSqlSessionFactoryBuilder sqlSessionFactoryBuilder = new MtSqlSessionFactoryBuilder(configuration);
        MtSqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build();
        MtSqlSession sqlSession = sqlSessionFactory.openSession();
        PersonDao personDao = sqlSession.getMapper(PersonDao.class);
        Person person = personDao.queryPersonById(1l);
        System.out.println(person);
    }
}

下面是模拟Mybatis框架的代码目录。
在这里插入图片描述

从上面的图中可以看到涵盖了executor、plugin、typehandler等基础组件。然后再看下是如何使用的。

clipboard.png

解析


怎么才能写出这样的框架呢?其实只需要对Mybatis的工作原理有大致的了解,知道那些模块是做什么的又是怎么实现的,然后画个流程图,然后定义需要哪些类和接口去实现,最后只需要将这些定义好的类和接口填空就可以了。


那么现在我们来看看这个Mybatis是怎么工作的

1.configuration模块


顾名思义,就是框架配置类,用于解析配置文件加载相关环境。这里代码里对配置文件的初始化主要有以下这么一个流程。

public MtConfiguration(String configLocation){
    this.configLocation = configLocation;
    init();
}

private void init(){
    try {
        //记载配置文件,这里使用properties代替xml解析
        loadConfigProperties();
        //初始化数据源信息
        initDataSource();
        //解析并加载mapper文件
        loadMapperRegistory();
        //解析加载plugin
        initPluginChain();
        //解析加载typeHandler
        initTypeHandler();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    }
}

可以看到实例类的时候调用init方法进行初始化,分别加载配置信息、初始化数据源信息、解析并加载mapper文件、解析加载plugin、解析加载typeHandler这几个步骤。具体如何进行小伙伴们可以看下代码实现,代码写得简单,相信你们一看就懂。

2.session模块


这个模块用于用户与框架交互的入口。包括常用的sqlSession、sqlSessionFactory以及sqlSessionFactoryBuilder。

clipboard.png

3.Mapper映射模块


这个模块主要用来注册我们的mapper映射文件的。

clipboard.png

这里需要说明下MapperData这个类,这个类主要记录sql语句和sql语句返回类型信息的。然后MapperRegistory用来保存方法与MapperData的映射关系,这样我们执行某个方法是就能找到对应的Sql语句和对应的返回类型了。这个过程可以结合Mybatis解析mapper.xml的过程去理解。

public class MapperData {
    private String sql;
    private Class type;
public MapperData(String sql, Class type) {
    this.sql = sql;
    this.type = type;
}

public String getSql() {
    return sql;
}

public void setSql(String sql) {
    this.sql = sql;
}

public Class getType() {
    return type;
}

public void setType(Class type) {
    this.type = type;
}

@Override
public String toString() {
    return "MapperData{" +
            "sql='" + sql + '\'' +
            ", resultType=" + type +
            '}';
}


4.Executor执行模块


这个模块主要用于执行SQL语句并且结合各种handler处理,然后这里只是写了一简单的执行器类。

clipboard.png

5.handler模块


这里模仿Mybatis执行SQL时的过程,stamentHandler用于处理语句集、parameterHandler用于处理参数、resultHandler用于处理结果映射。

clipboard.png

6.plugin模块


这里模仿Myabtis插件的工作原理实现的,也就是说这里和原生Mybatis的plugin基本一样

clipboard.png

7.typeHandler模块


这里模仿Mybatis类型处理的机制,用于javaType到jdbcType的映射处理以及jdbcType到javaType的映射处理

clipboard.png

8.配置模块


这里的对框架的配置使用了简单的properties

clipboard.png

(五)手写一个简单的mybatis框架,实现查询功能

1、定义表blog 

2、定义实体类、maper

public class Blog implements Serializable{
    Integer bid; // 文章ID
    String name; // 文章标题
    Integer authorId; // 文章作者ID

    public Integer getBid() {
        return bid;
    }

    public void setBid(Integer bid) {
        this.bid = bid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAuthorId() {
        return authorId;
    }

    public void setAuthorId(Integer authorId) {
        this.authorId = authorId;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "bid=" + bid +
                ", name='" + name + '\'' +
                ", authorId='" + authorId + '\'' +
                '}';
    }
}
public interface BlogMapper {
    /**
     * 根据主键查询文章
     * @param bid
     * @return
     */

    public Blog selectBlogById(Integer bid);

}

3、定义SqlSession

public class GPSqlSession {
    private GPConfiguration configuration;

    private GPExecutor executor;

    public GPSqlSession(GPConfiguration configuration, GPExecutor executor){
        this.configuration = configuration;
        this.executor = executor;
    }
    public <T> T selectOne(String statementId, Object paramater){
        // 根据statementId拿到SQL
        String sql = GPConfiguration.sqlMappings.getString(statementId);
        if(null != sql && !"".equals(sql)){
            return executor.query(sql, paramater );
        }
        return null;
    }

    public <T> T getMapper(Class clazz){
        return configuration.getMapper(clazz, this);
    }

}

4、定义Executor

public class GPExecutor {

    public <T> T query(String sql, Object paramater) {
        Connection conn = null;
        Statement stmt = null;
        Blog blog = new Blog();

        try {
            // 注册 JDBC 驱动
            Class.forName("com.mysql.jdbc.Driver");

            // 打开连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true", "root", "1234");

            // 执行查询
            stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(String.format(sql, paramater));

            // 获取结果集
            while (rs.next()) {
                Integer bid = rs.getInt("bid");
                String name = rs.getString("name");
                Integer authorId = rs.getInt("author_id");
                blog.setAuthorId(authorId);
                blog.setBid(bid);
                blog.setName(name);
            }
            System.out.println(blog);

            rs.close();
            stmt.close();
            conn.close();
        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (stmt != null) stmt.close();
            } catch (SQLException se2) {
            }
            try {
                if (conn != null) conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        }
        return (T)blog;
    }
}

5、定义configuration

public class GPConfiguration {
    public static final ResourceBundle sqlMappings;

    static {
        sqlMappings = ResourceBundle.getBundle("v1sql");
    }

    public <T> T getMapper(Class clazz, GPSqlSession sqlSession) {
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{clazz},
                new GPMapperProxy(sqlSession));
    }

}

6、定义MapperProxy

public class GPMapperProxy implements InvocationHandler{
    private GPSqlSession sqlSession;

    public GPMapperProxy(GPSqlSession sqlSession){
        this.sqlSession = sqlSession;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String mapperInterface = method.getDeclaringClass().getName();
        String methodName = method.getName();
        String statementId = mapperInterface + "." + methodName;
        return sqlSession.selectOne(statementId, args[0]);
    }
}

7、定义springboot启动类

public class MyBatisBoot {
    public static void main(String[] args) {
        GPSqlSession sqlSession = new GPSqlSession(new GPConfiguration(), new GPExecutor());
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        blogMapper.selectBlogById(1);
    }
}

8、启动springboot,打印日志,查询第一条成功!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手写MyBatis框架可以遵循以下步骤: 1. 创建一个配置类,用于加载数据库配置信息和Mapper接口配置信息。 2. 创建一个SqlSession类,用于管理数据库连接、执行SQL语句和返回结果。 3. 创建一个Mapper接口,定义数据库操作方法。 4. 创建一个MapperProxy类,实现动态代理,将Mapper接口的方法调用转发给SqlSession执行对应的SQL语句。 5. 创建一个MapperRegistry类,用于管理Mapper接口和对应的MapperProxy对象。 6. 创建一个DefaultSqlSessionFactory类,用于创建SqlSession对象。 7. 创建一个SqlSessionFactoryBuilder类,用于读取配置信息并创建DefaultSqlSessionFactory对象。 8. 创建一个Configuration类,用于保存MyBatis的全局配置信息。 9. 创建一个Executor类,用于执行SQL语句。 10. 创建一个StatementHandler类,用于封装和执行JDBC操作。 11. 创建一个ResultSetHandler类,用于处理查询结果集。 12. 创建一个TypeHandler类,用于处理Java类型和数据库类型之间的转换。 13. 创建一个XMLMapperBuilder类,用于解析Mapper配置文件,并将解析结果注册到Configuration中。 14. 创建一个XMLConfigBuilder类,用于解析MyBatis配置文件,并将解析结果保存到Configuration中。 15. 在主程序中,使用SqlSessionFactoryBuilder读取配置信息创建SqlSessionFactory,并通过SqlSessionFactory创建SqlSession对象,最后使用SqlSession获取Mapper接口的代理对象,从而实现对数据库的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值