自己动手实现一个简单的Mybatis(初级版本1.0)

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 自己动手实现一个简单的Mybatis(初级版本1.0)

手写Mybatis-v1.0

**源码链接(包括v1.0与v2.0): **

从上一个文章 —Mybatis概述中了解到了Mybatis的主要架构与底层原理流程,结尾给出了一个宏观流程图,可以知道,大致我们可以从三个模块入手:

  1. SqlSession:含有属性Configuration、Excutor,含有方法getMapper,selectOne(先实现一个查询方法)
  2. Configuration:含有MapperRegistry(mapper接口、方法、SQL),含有方法getMapper
  3. Executor:含有方法doQuery查询数据库

这样来看,我们的MyBatis1.0的大致脉络已经出来了。接下来就coding吧~

先是自定义的SqlSession (这里忽略SqlSessionFactory解析xml资源过程,1.0版本简化)

12345678910111213141516171819202122232425262728293031323334353637
/** * @description: 自定义的SqlSession * @author: linyh * @create: 2018-10-31 16:31 **/public class CustomSqlSession {     //持有两个关键对象    private CustomConfiguration configuration;    private CustomExecutor executor;     /**     * 用构造器将两个对象形成关系     */    public CustomSqlSession(CustomConfiguration configuration, CustomExecutor executor) {        this.configuration = configuration;        this.executor = executor;    }     public CustomConfiguration getConfiguration() {        return configuration;    }     /**    * 委派configuration获取mapper    */    public T T getMapper(ClassT clazz){        return configuration.getMapper(clazz, this);    }     /**     * 委派executor查询     */    public T T selectOne(String statement, String parameter){        return executor.query(statement, parameter);    }}

/**

  • @description: 自定义的SqlSession
  • @author: linyh
  • @create: 2018-10-31 16:31
  • */
    public class CustomSqlSession { //持有两个关键对象
    private CustomConfiguration configuration;
    private CustomExecutor executor; /**
    • 用构造器将两个对象形成关系
    • /
      public CustomSqlSession(CustomConfiguration configuration, CustomExecutor executor) {
      this.configuration = configuration;
      this.executor = executor;
      }
    public CustomConfiguration getConfiguration() {
    
      return configuration;
    
    } /**
    • 委派configuration获取mapper
    • /
      public T getMapper(Class clazz){
      return configuration.getMapper(clazz, this);
      }
    /**
    • 委派executor查询
    • /
      public T selectOne(String statement, String parameter){
      return executor.query(statement, parameter);
      }
      }

然后是自定义Configuration实现getMapper方法,这里也初始化一个MapperProxyFactory为了存放所有的Mapper。再写一个验证Mapper是否存在的方法和根据Class获取对应mapper的MapperProxyFactory。

123456789101112131415161718192021222324252627282930313233
/** * @description: * @author: linyh * @create: 2018-10-31 16:32 **/public class CustomConfiguration {     public final MapperRegistory mapperRegistory = new MapperRegistory();     public static final MapString, String mappedStatements = new HashMap();     //TODO 改用anontation扫描 (暂时HardCode)    //初始化时Configuration加载所有Mapper方法与Sql语句    public CustomConfiguration() {        mapperRegistory.addMapper(TestMapper.class);        mappedStatements.put("com.test.mybatis.v1.mapper.TestCustomMapper.selectByPrimaryKey"        , "select * from test where id = %d");    }     //MapperProxy根据statementName查找是否有对应SQL    public boolean hasStatement(String statementName) {        return mappedStatements.containsKey(statementName);    }     //MapperProxy根据statementID获取SQL    public String getMappedStatement(String id) {        return mappedStatements.get(id);    }     public T T getMapper(ClassT clazz, CustomSqlSession sqlSession) {        return mapperRegistory.getMapper(clazz, sqlSession);    }}

/**

  • @description:
  • @author: linyh
  • @create: 2018-10-31 16:32
  • */
    public class CustomConfiguration { public final MapperRegistory mapperRegistory = new MapperRegistory(); public static final MapString, String mappedStatements = new HashMap(); //TODO 改用anontation扫描 (暂时HardCode)
    //初始化时Configuration加载所有Mapper方法与Sql语句
    public CustomConfiguration() {
    
      mapperRegistory.addMapper(TestMapper.class);
      mappedStatements.put("com.test.mybatis.v1.mapper.TestCustomMapper.selectByPrimaryKey"
      , "select * from test where id = %d");
    
    } //MapperProxy根据statementName查找是否有对应SQL
    public boolean hasStatement(String statementName) {
    
      return mappedStatements.containsKey(statementName);
    
    } //MapperProxy根据statementID获取SQL
    public String getMappedStatement(String id) {
    
      return mappedStatements.get(id);
    
    } public T getMapper(Class clazz, CustomSqlSession sqlSession) {
    
      return mapperRegistory.getMapper(clazz, sqlSession);
    
    }
    }

这里完善MapperProxy与MapperRegistory(注册所有的Mapper的Map)

12345678910111213141516171819202122232425
/** * @description: Mapper动态代理者 * @author: linyh * @create: 2018-10-31 16:52 **/public class MapperProxy implements InvocationHandler{     private CustomSqlSession sqlSession;     public MapperProxy(CustomSqlSession sqlSession) {        this.sqlSession = sqlSession;    }     /**     * 每一个Mapper的每个方法都将执行invoke方法,此方法判断方法名是否维护在Configuration中,如有则取出SQL    */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if (sqlSession.getConfiguration().hasStatement(method.getDeclaringClass().getName()+"."+method.getName())) {            String sql = sqlSession.getConfiguration().getMappedStatement(method.getDeclaringClass().getName()+"."+method.getName());            return sqlSession.selectOne(sql, args[0].toString());        }        return method.invoke(proxy, args);    }}

/**

  • @description: Mapper动态代理者
  • @author: linyh
  • @create: 2018-10-31 16:52
  • */
    public class MapperProxy implements InvocationHandler{ private CustomSqlSession sqlSession; public MapperProxy(CustomSqlSession sqlSession) {
    
      this.sqlSession = sqlSession;
    
    } /**
    • 每一个Mapper的每个方法都将执行invoke方法,此方法判断方法名是否维护在Configuration中,如有则取出SQL
    • /
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if (sqlSession.getConfiguration().hasStatement(method.getDeclaringClass().getName()+”.”+method.getName())) {
      
         String sql = sqlSession.getConfiguration().getMappedStatement(method.getDeclaringClass().getName()+"."+method.getName());
         return sqlSession.selectOne(sql, args[0].toString());
      
      }
      return method.invoke(proxy, args);
      }
      }
      123456789101112131415161718192021222324252627282930313233343536373839404142
      /** * @description: mapper注册类 * @author: linyh * @create: 2018-10-31 16:51 **/public class MapperRegistory {     //用一个Map维护所有Mapper    private final MapClass?, MapperProxyFactory knownMappers = new HashMap();     //TODO Configuration解析anontation之后调用方法初始化所有mapper    public T void addMapper(ClassT clazz){        knownMappers.put(clazz, new MapperProxyFactory(clazz));    }     /**     * getMapper最底层执行者,获取mapper的MapperProxyFactory对象     */    public T T getMapper(ClassT clazz, CustomSqlSession sqlSession) {        MapperProxyFactory proxyFactory = knownMappers.get(clazz);        if (proxyFactory == null) {            throw new RuntimeException("Type: " + clazz + " can not find");        }        return (T)proxyFactory.newInstance(sqlSession);    }     /**     * 内部类实现一个Factory生成Mapper的代理     */    public class MapperProxyFactoryT{         private ClassT mapperInterface;         public MapperProxyFactory(ClassT mapperInterface) {            this.mapperInterface = mapperInterface;        }         public T newInstance(CustomSqlSession sqlSession) {            return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, new MapperProxy(sqlSession));        }    }}

/**

  • @description: mapper注册类
  • @author: linyh
  • @create: 2018-10-31 16:51
  • */
    public class MapperRegistory { //用一个Map维护所有Mapper
    private final MapClass?, MapperProxyFactory knownMappers = new HashMap(); //TODO Configuration解析anontation之后调用方法初始化所有mapper
    public void addMapper(Class clazz){
    
      knownMappers.put(clazz, new MapperProxyFactory(clazz));
    
    } /**
    • getMapper最底层执行者,获取mapper的MapperProxyFactory对象
    • /
      public T getMapper(Class clazz, CustomSqlSession sqlSession) {
      MapperProxyFactory proxyFactory = knownMappers.get(clazz);
      if (proxyFactory == null) {
      
        throw new RuntimeException("Type: " + clazz + " can not find");
      
      }
      return (T)proxyFactory.newInstance(sqlSession);
      }
    /**
    • 内部类实现一个Factory生成Mapper的代理

    • /
      public class MapperProxyFactory{

      private Class mapperInterface;

      public MapperProxyFactory(Class mapperInterface) {

      
        this.mapperInterface = mapperInterface;
      

      }

      public T newInstance(CustomSqlSession sqlSession) {

      
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, new MapperProxy(sqlSession));
      

      }
      }
      }

到这里getMapper就实现好了,每次getMapper都能生成对应的MapperProxy代理。接下来实现Executor的查询方法。

先定义一个接口(2.0将加入CacheExecutor,面向接口编程便于扩展)。

12345678
/** * @description: * @author: linyh * @create: 2018-10-31 16:32 **/public interface CustomExecutor {    T T query(String statement, String parameter);}

/**

  • @description:
  • @author: linyh
  • @create: 2018-10-31 16:32
  • */
    public interface CustomExecutor {
    T query(String statement, String parameter);
    }

然后是具体的实现类,其中使用JDBC查询。

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
/** * @description: 自定义Executor * @author: linyh * @create: 2018-10-31 17:46 **/public class CustomDefaultExecutor implements CustomExecutor{     @Override    public T T query(String statement, String parameter) {        Connection conn = null;        PreparedStatement preparedStatement = null;        Test test = null;        try {            conn = getConnection();             //TODO ParameterHandler            preparedStatement = conn.prepareStatement(String.format(statement, Integer.parseInt(parameter)));            preparedStatement.execute();            ResultSet rs = preparedStatement.getResultSet();             //TODO ObjectFactory            test = new Test();             //TODO ResultSetHandler            while (rs.next()) {                test.setId(rs.getInt(1));                test.setNums(rs.getInt(2));                test.setName(rs.getString(3));            }         } catch (SQLException e) {            e.printStackTrace();        } finally {            try {                conn.close();            } catch (SQLException e) {                e.printStackTrace();            }        }        return (T)test;    }     public Connection getConnection() throws SQLException {        String driver = "com.mysql.jdbc.Driver";        String url = "jdbc:mysql://127.0.0.1:3306/gp?serverTimezone=UTC";        String username = "root";        String password = "admin";        Connection conn = null;        try {            Class.forName(driver); //classLoader,加载对应驱动            conn = DriverManager.getConnection(url, username, password);        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SQLException e) {            e.printStackTrace();        }        return conn;    }}

/**

  • @description: 自定义Executor
  • @author: linyh
  • @create: 2018-10-31 17:46
  • */
    public class CustomDefaultExecutor implements CustomExecutor{ @Override
    public T query(String statement, String parameter) {
    
      Connection conn = null;
      PreparedStatement preparedStatement = null;
      Test test = null;
      try {
          conn = getConnection();
    
          //TODO ParameterHandler
          preparedStatement = conn.prepareStatement(String.format(statement, Integer.parseInt(parameter)));
          preparedStatement.execute();
          ResultSet rs = preparedStatement.getResultSet();
    
          //TODO ObjectFactory
          test = new Test();
    
          //TODO ResultSetHandler
          while (rs.next()) {
              test.setId(rs.getInt(1));
              test.setNums(rs.getInt(2));
              test.setName(rs.getString(3));
          }
    
      } catch (SQLException e) {
          e.printStackTrace();
      } finally {
          try {
              conn.close();
          } catch (SQLException e) {
              e.printStackTrace();
          }
      }
      return (T)test;
    
    } public Connection getConnection() throws SQLException {
    
      String driver = "com.mysql.jdbc.Driver";
      String url = "jdbc:mysql://127.0.0.1:3306/gp?serverTimezone=UTC";
      String username = "root";
      String password = "admin";
      Connection conn = null;
      try {
          Class.forName(driver); //classLoader,加载对应驱动
          conn = DriverManager.getConnection(url, username, password);
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      } catch (SQLException e) {
          e.printStackTrace();
      }
      return conn;
    
    }
    }

这样查询方法也大致完成了,创建实体与Mapper,然后开始测试一下吧~

1234567891011121314151617181920212223242526272829303132333435363738394041424344
/** * @description: 实体类 * @author: linyh * @create: 2018-10-31 17:03 **/public class Test {     private Integer id;    private Integer nums;    private String name;     public Integer getId() {        return id;    }     public void setId(Integer id) {        this.id = id;    }     public Integer getNums() {        return nums;    }     public void setNums(Integer nums) {        this.nums = nums;    }     public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     @Override    public String toString() {        return "Test{" +                "id=" + id +                ", nums=" + nums +                ", name='" + name + '\'' +                '}';    }}

/**

  • @description: 实体类
  • @author: linyh
  • @create: 2018-10-31 17:03
  • */
    public class Test { private Integer id;
    private Integer nums;
    private String name; public Integer getId() {
    
      return id;
    
    } public void setId(Integer id) {
    
      this.id = id;
    
    } public Integer getNums() {
    
      return nums;
    
    } public void setNums(Integer nums) {
    
      this.nums = nums;
    
    } public String getName() {
    
      return name;
    
    } public void setName(String name) {
    
      this.name = name;
    
    } @Override
    public String toString() {
    
      return "Test{" +
              "id=" + id +
              ", nums=" + nums +
              ", name='" + name + '\'' +
              '}';
    
    }
    }
    12345678
    /** * @Author:linyh * @Date: 2018/10/31 16:56 * @Modified By: */public interface TestCustomMapper {    Test selectByPrimaryKey(int id);}

/**

  • @Author:linyh
  • @Date: 2018/10/31 16:56
  • @Modified By:
  • /
    public interface TestCustomMapper {
    Test selectByPrimaryKey(int id);
    }
    12345678910111213
    /** * @description: * @author: linyh * @create: 2018-10-31 18:05 **/public class TestMybatis {    public static void main(String[] args) {        CustomSqlSession sqlSession = new CustomSqlSession(                new CustomConfiguration(), new CustomDefaultExecutor());        TestCustomMapper testCustomMapper = sqlSession.getMapper(TestCustomMapper.class);        System.out.println(testCustomMapper.selectByPrimaryKey(2));    }}

/**

  • @description:
  • @author: linyh
  • @create: 2018-10-31 18:05
  • */
    public class TestMybatis {
    public static void main(String[] args) {
    
      CustomSqlSession sqlSession = new CustomSqlSession(
              new CustomConfiguration(), new CustomDefaultExecutor());
      TestCustomMapper testCustomMapper = sqlSession.getMapper(TestCustomMapper.class);
      System.out.println(testCustomMapper.selectByPrimaryKey(2));
    
    }
    }

控制台打印:

自己动手实现一个简单的Mybatis(初级版本1.0)

这样就完成了极简版MyBatisv1.0了,其中还有很多不足之处,我用了TODO标记,下面总结几点不足之处,统计2.0需要改进与增加的功能。

不足之处

  1. 解析Mapper信息时用了HardCode,需改用anontation方式去扫描Mapper与对应SQL语句。
  2. Executor不够单一职责,它不仅负责参数装配还负责查询语句加上结果集映射,不合理,需要细分职责。

增加的功能

  1. 新增anontation功能动态扫描Mapper类,去掉HardCode。
  2. 新增StatementHandler设置参数以及负责查询工作。
  3. 新增ObjectFactory动态创建实体类。
  4. 新增ResultSetHandler将结果集映射到实体类中。
  5. 增加一个Plugin功能。
  6. 增加CacheExecutor装饰者来为查询增加一个缓存功能。
本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 自己动手实现一个简单的Mybatis(初级版本1.0)


 上一篇
MyBatis概览(各组件以及底层实现原理等) MyBatis概览(各组件以及底层实现原理等)
一、MyBatis概览 这是Mybatis的整体架构图,可以看出它是由几个主要组件组成,分别为Configuration、Sql映射、Mapper、MappedStatements组成,Configuration包含了所有启动时的配置信息
2021-04-05
下一篇 
手写MyBatis2.0附带Plugin功能(增强版本) 手写MyBatis2.0附带Plugin功能(增强版本)
基于上一篇博客,手写MyBatis1.0末尾提出的几个不足之处与需要新增的地方,这篇博客将完善之前的MyBatis1.0版本,升级为2.0版本~将会新增的功能: 加入Plugin插件功能。 加入缓存功能。 分解Executor指责,分出各
2021-04-05