了解mybatis源码手写mybatis

一:mybatis概述

  MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

二:手写mybatis

实现思路::

1创建SqlSessionFactory实例

2:实例化过程中,加载配置文件创建configuration对象

3:通过factory创建SqlSeesion

---------------------初始化阶段完成--------------------------

4:通过SqlSeesion获取mapper接口动态代理

5:动态代理回调sqlsession中某查询方法

---------------------代理阶段完成--------------------------

6:SqlSession将查询方法转发给Executor

7:Executor基于jdbc访问数据库获取数据

8:Executor通过反射将数据转化成POJP并返回给SqlSession

9:将数据返回给调用者

手写源码展示:

     2.1、初始化阶段

     将db.properties,以及*mapper.xml读取到内存中,主要通过SqlSessionFactory实现  

/**
 * 初始化加载配置信息映射
 * 生成sqlsession
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 12:07
 */
public class SqlSessionFactory {
    public final Configuration  conf = new Configuration();
    public SqlSessionFactory(){
        loadDbInfo();
        loadMappersInfo();//加载
    }
    //mappers.xml文件保存路径
    public static final  String MAPPER_CONFIG_LOCATION="mappers";
    //数据库连接配置文件路径
    public static final String DB_CONFIG_FILE="db.properties";
    private void loadDbInfo(){
       InputStream inputStream =  SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
        Properties p = new Properties();
        try {
            p.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

        conf.setJdbcDriver(p.get("jdbc.driver").toString());
        conf.setJdbcUrl(p.get("jdbc.url").toString());
        conf.setJdbcUsername(p.get("jdbc.username").toString());
        conf.setJdbcPassword(p.get("jdbc.password").toString());
    }
    //读取xml并解析成map
    private void loadMappersInfo(){
         URL resources =null;
         resources =  SqlSessionFactory.class.getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
         File mappers =  new File(resources.getFile());
         if(mappers.isDirectory()){
             File[] listFiles = mappers.listFiles();
             for(File file:listFiles){
                 loadMapperInfo(file);
             }
         }
    }
    //加载指定*.xml文件
    private  void loadMapperInfo(File file){
        SAXReader reader = new SAXReader();
        Document document=null;
        try {
            document = reader.read(file);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        Element root = document.getRootElement();
        //获取命名空间
        String namespace = root.attribute("namespace").getData().toString();
        //select子元素
        List<Element> selects = root.elements("select");
        //遍历所有查询语句
        for( Element element:selects){
            MappedStatement mappedStatement = new MappedStatement();
            String id =  element.attribute("id").getData().toString();
            String resultType =  element.attribute("resultType").getData().toString();
            String sql =  element.getData().toString();
            String sourceId = namespace+"."+id; //map的key是命名空间+id
            mappedStatement.setNamespace(namespace);
            mappedStatement.setSourceId(sourceId);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sql);
            conf.getMappedStatements().put(sourceId,mappedStatement);
        }

    }
    public SqlSession openSession(){
         return new DefuaultSqlSession(this.conf);
    }
}

SqlSessionFactory

/**
 * 数据库配置文件映射
 * *mapper.xml映射map
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 12:10
 */
public class Configuration {
    private String jdbcDriver;//数据库驱动
    private String jdbcUrl;//数据库url地址
    private String jdbcUsername;//数据库用户名
    private String jdbcPassword;//数据库密码
    private Map<String, MappedStatement> mappedStatements = new HashMap<>();//mapper.xml解析成对象
    public String getJdbcDriver() {
        return jdbcDriver;
    }

    public void setJdbcDriver(String jdbcDriver) {
        this.jdbcDriver = jdbcDriver;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }

    public String getJdbcUsername() {
        return jdbcUsername;
    }

    public void setJdbcUsername(String jdbcUsername) {
        this.jdbcUsername = jdbcUsername;
    }

    public String getJdbcPassword() {
        return jdbcPassword;
    }

    public void setJdbcPassword(String jdbcPassword) {
        this.jdbcPassword = jdbcPassword;
    }

    public Map<String, MappedStatement> getMappedStatements() {
        return mappedStatements;
    }

    public void setMappedStatements(Map<String, MappedStatement> mappedStatements) {
        this.mappedStatements = mappedStatements;
    }
}

Configuration

/**
 * mapper.xml映射文件
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 12:13
 */
public class MappedStatement {

    private String namespace;//命名空间
    private String sourceId;//方法id名称
    private String resultType;//返回数据类型
    private String sql;//sql语句

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public String getSourceId() {
        return sourceId;
    }

    public void setSourceId(String sourceId) {
        this.sourceId = sourceId;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getSql() {
        return sql;
    }

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

MappedStatement

 2.2、代理阶段

主要通过DefuaultSqlSession实现

/**
 * 1: 对外提供访问的api
 * 2:对内请求转发给Executor实现
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 14:51
 */
public interface SqlSession {
   <T> T selectOne(String statement,Object parameter) throws Exception;

   <E> List<E> selectList(String statement, Object parameter);

   <T> T getMapper(Class<T> type);
}

SqlSession

/**
 * 1: 对外提供访问的api
 * 2:对内请求转发给Executor实现
 * 3: 通过动态代理,面向接口编程
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 14:51
 */
public class DefuaultSqlSession implements SqlSession {

    private final Configuration conf;

    private Executor executor;

    public DefuaultSqlSession(Configuration conf){
        super();
        this.conf=conf;
        executor = new DefaultExcutor(conf);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) throws Exception {
        MappedStatement  ms = conf.getMappedStatements().get(statement);
        List<Object> selectList =  executor.query(ms,parameter);
        if(null==selectList || selectList.size()==0){
            return null;
        }
        if(selectList.size()==1){
            return (T)selectList.get(0);
        }else{
            throw new Exception();
        }
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        MappedStatement  ms = conf.getMappedStatements().get(statement);
        return executor.query(ms,parameter);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
         return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type},new MapperProxy(this));

    }

    public class MapperProxy implements InvocationHandler{
        private SqlSession session;

        public MapperProxy(SqlSession session){
            super();
            this.session = session;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           Class<?>  returnType = method.getReturnType();
            System.out.println("returnType="+returnType.toString());
           if(Collection.class.isAssignableFrom(returnType)){//返回参数是不是集合
               return session.selectList(method.getDeclaringClass().getName()+"."+method.getName(),null==args?null:args[0]);
           }else{
               return session.selectOne(method.getDeclaringClass().getName()+"."+method.getName(),null==args?null:args[0]);
           }
        }
    }
}

DefuaultSqlSession

  2.3、数据库操作返回数据并做映射处理阶段

     主要通过Executor实现

/**
 * @param: mybatis03
 * @description: 封装对数据库操作,以及对返回数据的封装
 * @author: tangl
 * @create: 2019-05-13 15:25
 */
public interface Executor {
   <E> List<E> query(MappedStatement mappedStatement, Object args);
}

Executor

/**
 * @param: mybatis03
 * @description: 封装对数据库操作,以及对返回数据的封装
 * @author: tangl
 * @create: 2019-05-13 15:25
 */
public class DefaultExcutor implements Executor {

    private Configuration conf;

    public DefaultExcutor(Configuration conf){
        super();
        this.conf=conf;
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter) {
        List<E> ret = new ArrayList<>();
        try {
            Class.forName(conf.getJdbcDriver());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Connection connection=null;
        PreparedStatement preparedStatement =null;
        ResultSet resultSet=null;
        try {
            connection =  DriverManager.getConnection(conf.getJdbcUrl(),conf.getJdbcUsername(),conf.getJdbcPassword());
            preparedStatement = connection.prepareStatement(ms.getSql());
            //处理sql语句中的占位符
            parameterize(preparedStatement,parameter);
            resultSet = preparedStatement.executeQuery();
            //通过发射技术,填充到list
            handlerResultSet(resultSet,ret,ms.getResultType());
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                resultSet.close();
                preparedStatement.close();
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
       return ret;

    }

    public void parameterize(PreparedStatement preparedStatement,Object parameter) throws SQLException {
          if(parameter instanceof String){
              preparedStatement.setString(1,(String)parameter);
          }else if(parameter instanceof Long){
              preparedStatement.setLong(1,(Long)parameter);
          }else if(parameter instanceof Integer){
              preparedStatement.setInt(1,(int)parameter);
          }
    }

    public <E> void handlerResultSet(ResultSet resultSet,List<E> ret,String className){
        Class<E> clazz = null;
        try {
            clazz = (Class<E>)Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            while(resultSet.next()){
                //通过反射实例化对象
                Object entity  =  clazz.newInstance();
                //使用反射工具将resultSet中的数据填充到entity中
                //id,name,sex,age
//                Integer id  =  resultSet.getInt(1);
//                String  name  =  resultSet.getString(2);
//                String  sex  =  resultSet.getString(3);
//                Integer age  =  resultSet.getInt(4);
//
//                Integer id02 =  resultSet.getInt("id");
//                String name02 =  resultSet.getString("name");
//                String sex02 =  resultSet.getString("id");
//                Integer age02  =  resultSet.getInt("age");
              //  ReflectionUtils.
                // 获取实体类的所有属性,返回Field数组
                Field[] fields = clazz.getDeclaredFields();
                for(Field field:fields){
                     field.setAccessible(true);
                     String fname  =  field.getName();
                     Type  type = field.getGenericType();
                     System.out.println("fname="+fname+" type="+type.toString());
                     if(type.toString().equals("class java.lang.String")){
                         String column = resultSet.getString(fname);
                         field.set(entity,column);
                     }else if(type.toString().equals("class java.lang.Integer")){
                         Integer column = resultSet.getInt(fname);
                         field.set(entity,column);
                     }
                }
                ret.add((E)entity);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

DefaultExcutor

三:测试

/**
 * @param: mybatis03
 * @description:手写mybatis测试
 * @author: tangl
 * @create: 2019-05-13 14:57
 */
public class TestMybatis {
    public static void main(String[] args) throws ClassNotFoundException {
       //第一阶段,加载配置信息放入内存
        SqlSessionFactory factory = new SqlSessionFactory();
        SqlSession sqlSession = factory.openSession();
        System.out.println(sqlSession);
        //第二阶段 封装ibatis编程模式,使用mapper接口开发的初始化工作
        TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
        //第三阶段
        TUser tUser = mapper.getUserById(1);
        System.out.println(tUser);
        List<TUser> allList  = mapper.getUserAllList();
        if(null!=allList && allList.size()>0){
            for (TUser tUser02:allList){
                System.out.println(tUser02);
            }
        }
    }
}

四:项目补充

1:pom.xml文件

<dependencies>

  <!-- 解析xml文件-->  <dependency>  <groupId>dom4j</groupId>  <artifactId>dom4j</artifactId>  <version>1.6.1</version></dependency>  <!--mysql-->  <dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <version>8.0.11</version>  </dependency></dependencies>

2:db.properties
jdbc.driver=com.mysql.cj.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&verifyServerCertificate=false&useSSL=false&serverTimezone=UTCjdbc.username=rootjdbc.password=123456

五:项目说明

这仅仅是参考源码完成mybatis的核心功能,mybatis还有一些其它辅助功能,如:数据库连接池等。

原文地址:https://www.cnblogs.com/tanglinsheng/p/10861539.html

时间: 2024-10-13 01:20:31

了解mybatis源码手写mybatis的相关文章

mybatis源码分析(四) mybatis与spring事务管理分析

mybatis源码分析(四) mybatis与spring事务管理分析 一丶从jdbc的角度理解什么是事务 从mysql获取一个连接之后, 默认是自动提交, 即执行完sql之后, 就会提交事务. 这种事务的范围是一条sql语句. 将该连接设置非自动提交, 可以执行多条sql语句, 然后由程序决定是提交事务, 还是回滚事务. 这也是我们常说的事务. Connection connection = dataSource.getConnection(); // connection.setTransa

【mybatis源码学习】mybatis的参数处理

一.mybatis的参数处理以及参数取值 1.单个参数 mybatis不做任何处理 取值方式: ? #{参数名/任意名} <!-- Employee getEmpById(Integer id); --> <select id="getEmpById" resultType="com.mxc.bean.Employee"> select * from employee where id=#{id} </select> 2.多个参数

【mybatis源码学习】mybatis的插件功能

一.mybatis的插件功能可拦截的目标 org.apache.ibatis.executor.parameter.ParameterHandler org.apache.ibatis.executor.resultset.ResultSetHandler org.apache.ibatis.executor.statement.StatementHandler org.apache.ibatis.executor.Executor 二.Mybatis的插件功能接入步骤 1.实现接口:org.a

MyBatis源码浅析

什么是MyBatis MyBatis是支持定制化SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手工设置参数以及抽取结果集.MyBatis 使用简单的 XML 或注解来配置和映射基本体,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录. MyBatis简单示例 虽然在使用MyBatis时一般都会使用XML文件,但是本文为了分析程序的简单性,简单的测试程序将不包含XML配置

MyBatis源码分析-SQL语句执行的完整流程

MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.如何新建MyBatis源码工程请点击MyBatis源码分析-IDEA新建MyBatis源码工程. MyBatis框架主要完成的是以下2件事情: 根据JD

Mybatis源码分析

MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.如何新建MyBatis源码工程请点击MyBatis源码分析-IDEA新建MyBatis源码工程. MyBatis框架主要完成的是以下2件事情: 根据JD

mybatis专题(三)-----mybatis源码学习

源码分析概述 源码包分析 下载地址 MyBatis 源码下载地址:https://github.com/mybatis/mybatis-3 导入过程 1. 下载MyBatis的源码 2. 检查maven的版本,必须是3.25以上,建议使用maven的最新版本 3. mybatis的工程是maven工程,在开发工具中导入,工程必须使用jdk1.8以上版本: 4. 把mybatis源码的pom文件中true,全部改为false,或者直接删除这行: 5. 在工程目录下执行 mvn clean inst

MyBatis源码分析-MyBatis初始化流程

MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.如何新建MyBatis源码工程请点击MyBatis源码分析-IDEA新建MyBatis源码工程. MyBatis初始化的过程也就是创建Configura

MyBatis 源码篇-日志模块2

上一章的案例,配置日志级别为 debug,执行一个简单的查询操作,会将 JDBC 操作打印出来.本章通过 MyBatis 日志部分源码分析它是如何实现日志打印的. 在 MyBatis 的日志模块中有一个 jdbc package,package 中的内容如下图所示: BaseJdbcLogger 是一个抽象类,它是 jdbc package 下其他类的父类,类继承关系如下图所示: BaseJdbcLogger 类中定义了一些公共集合和简单的工具方法,提供给子类使用. BaseJdbcLogger