手写Mybatis框架

一、简介

Mybatis是一款优秀的持久层框架,在互联网公司的项目开发中使用非常广泛。通过对MyBatis源码的学习,可以更好的了解Mybatis的使用,同时也可以借鉴其中优秀的编程方式和设计模式。学习是一个抽象归纳然后再运用的过程,通过对Mybatis源码核心部分的抽象,手写一个Mybatis框架,来更好的学习Mybatis。

二、处理流程

Mybatis核心流程分为三步:

1.读取XML配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化工作;

2.封装iBatis的编程模式,使用mapper接口开发的初始化工作;

3.通过SqlSession完成SQL的解析,参数的映射、SQL的执行、结果的反射解析过程;

三、手写Mybatis具体实现

1. 实体相关类

Dept.java:

package com.taoxi.mybatis.mysimple.entity;

public class Dept {
    private Integer deptNo;
    private String dName;
    private String loc;

    public Integer getDeptNo() {
        return deptNo;
    }

    public void setDeptNo(Integer deptNo) {
        this.deptNo = deptNo;
    }

    public String getDname() {
        return dName;
    }

    public void setDname(String dname) {
        this.dName = dname;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

}

DeptMapper.java:

package com.taoxi.mybatis.mysimple.mapper;

import com.taoxi.mybatis.mysimple.entity.Dept;

public interface DeptMapper {

    Dept deptFindById(Integer deptId);

}

DeptMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.taoxi.mybatis.mysimple.mapper.DeptMapper">
 
 <select >
        select * from dept where deptno= ?
 </select>
   
</mapper>

2.配置相关类

Configuration.java:

package com.taoxi.mybatis.mysimple.config;

import java.util.HashMap;
import java.util.Map;

public class Configuration {

    private String jdbcDriver;
    private String jdbcUrl;
    private String jdbcUsername;
    private String jdbcPassword;

    private Map<String, MappedStatement> mappedStatments = new HashMap<String, MappedStatement>();

    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> getMappedStatments() {
        return mappedStatments;
    }

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

MappedStatemant.java:

package com.taoxi.mybatis.mysimple.config;

public class MappedStatement {

    private String namespace;
    private String sourceId;
    private String resultType;
    private String 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;
    }
}

3.SqlSessionFactory工厂类

SqlSessionFactory.java:

package com.taoxi.mybatis.mysimple.session;

import com.taoxi.mybatis.mysimple.config.Configuration;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Properties;

public class SqlSessionFactory {

    private final Configuration configuration = new Configuration();

    public SqlSessionFactory() {
        loadDbInfo();
        loadMappersInfo();
    }

    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }

    //记录mapper xml文件存放的位置
    public static final String MAPPER_CONFIG_LOCATION = "mappers";
    //记录数据库连接信息文件存放位置
    public static final String DB_CONFIG_FILE = "db.properties";

    //加载数据库配置信息
    private void loadDbInfo() {
        //加载数据库信息配置文件
        InputStream dbIn = SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
        Properties properties = new Properties();
        try {
            properties.load(dbIn);
        } catch (IOException e) {
            throw new RuntimeException("load db info error:", e);
        }
        //将数据库配置信息写入configuration对象
        configuration.setJdbcDriver(properties.get("jdbc.driver").toString());
        configuration.setJdbcPassword(properties.get("jdbc.password").toString());
        configuration.setJdbcUrl(properties.get("jdbc.url").toString());
        configuration.setJdbcUsername(properties.get("jdbc.username").toString());
    }


    //加载指定文件夹下的所有mapper.xml
    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();
            //遍历文件夹下所有的mapper.xml,解析信息后,注册到configuration对象中
            for (File file :
                    listFiles) {
                loadMapperInfo(file);
            }
        }

    }

    //加载指定的mapper.xml文件
    private void loadMapperInfo(File file) {
        //创建saxReader对象
        SAXReader reader = new SAXReader();
        //通过read方法读取一个文件,转换成Document对象
        Document document = null;
        try {
            document = reader.read(file);
        } catch (DocumentException e) {
            throw new RuntimeException("load mapper info error:", e);
        }
        //获取根节点元素对象<mapper>
        Element root = document.getRootElement();
        //获取命名空间
        String namespace = root.attribute("namespace").getData().toString();
        //获取select子节点列表
        List<Element> selects = root.elements("select");
        //遍历select节点,将信息记录到MappedStatement对象,并登记到configuration对象中
        for (Element element :
                selects) {
            MappedStatement mappedStatement = new MappedStatement();//实例化mappedStatement
            String id = element.attribute("id").getData().toString();//读取id属性
            String resultType = element.attribute("resultType").getData().toString();//读取resultType属性
            String sql = element.getData().toString();//读取SQL语句信息
            String sourceId = namespace + "." + id;
            //给mappedStatement属性赋值
            mappedStatement.setSourceId(sourceId);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sql);
            mappedStatement.setNamespace(namespace);
            //注册到configuration对象中
            configuration.getMappedStatments().put(sourceId, mappedStatement);
        }

    }

}

4.SqlSession相关类

SqlSession.java接口:

package com.taoxi.mybatis.mysimple.session;

import java.util.List;

public interface SqlSession {

    public <T> T selectOne(String statement, Object parameter);

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

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

}

DefaultSqlSession.java默认实现类:

package com.taoxi.mybatis.mysimple.session;

import com.taoxi.mybatis.mysimple.binding.MapperProxy;
import com.taoxi.mybatis.mysimple.config.Configuration;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import com.taoxi.mybatis.mysimple.executor.DefaultExecutor;
import com.taoxi.mybatis.mysimple.executor.Executor;

import java.lang.reflect.Proxy;
import java.util.List;

public class DefaultSqlSession implements SqlSession{

    private final Configuration configuration;

    private Executor executor;

    public DefaultSqlSession(Configuration configuration) {
        super();
        this.configuration = configuration;
        executor = new DefaultExecutor(configuration);
    }

    public <T> T selectOne(String statement, Object parameter) {
        //执行Sql语句获取查询结果
        List<Object> selectList = this.selectList(statement, parameter);
        //查询结果为空返回null
        if (selectList == null || selectList.size()==0) {
            return null;
        }
        if (selectList.size()==1) {
            return (T)selectList.get(0);
        }else{
            throw new RuntimeException("Too Many Results!");
        }

    }

    public <E> List<E> selectList(String statement, Object parameter) {
        try {
            MappedStatement ms = configuration.getMappedStatments().get(statement);
            return executor.query(ms, parameter);
        } catch (Exception e) {
            throw new RuntimeException("select list error:", e);
        }

    }

    public <T> T getMapper(Class<T> type) {
        MapperProxy mapperProxy = new MapperProxy(this);

        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, mapperProxy);
    }
}

MapperProxy.java代理处理类:

package com.taoxi.mybatis.mysimple.binding;

import com.taoxi.mybatis.mysimple.session.SqlSession;

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

public class MapperProxy implements InvocationHandler {

    private SqlSession session;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Collection.class.isAssignableFrom(method.getReturnType())){
            return session.selectList(method.getDeclaringClass().getName()+"."+method.getName(),
                    args==null?null:args[0]);
        }else {
            return session.selectOne(method.getDeclaringClass().getName()+"."+method.getName(),
                    args==null?null:args[0]);
        }
    }
}

5.Executor执行器相关类

Executor.java执行器接口:

package com.taoxi.mybatis.mysimple.executor;

import com.taoxi.mybatis.mysimple.config.MappedStatement;

import java.util.List;

public interface Executor {
    public <E> List<E> query(MappedStatement ms, Object parameter) throws Exception;
}

DefaultExecutor.java执行器默认实现类:

package com.taoxi.mybatis.mysimple.executor;

import com.taoxi.mybatis.mysimple.config.Configuration;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import com.taoxi.mybatis.mysimple.util.ReflectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class DefaultExecutor implements Executor{

    private static Logger logger = LoggerFactory.getLogger(DefaultExecutor.class);

    private final Configuration configuration;

    public DefaultExecutor(Configuration configuration) {
        super();
        this.configuration = configuration;
    }

    public <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException{
        ArrayList<E> ret = new ArrayList<E>();//定义返回结果集
        try {
            Class.forName(configuration.getJdbcDriver());//加载驱动程序
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            //获取连接,从Configuration中获取数据库信息
            connection = DriverManager.getConnection(configuration.getJdbcUrl(), configuration.getJdbcUsername(),
                    configuration.getJdbcPassword());
            //创建prepareStatement,从MappedStatement中获取sql语句
            preparedStatement = connection.prepareStatement(ms.getSql());
            //处理sql语句中的占位符
            parameterize(preparedStatement, parameter);
            //执行查询操作获取resultSet
            resultSet = preparedStatement.executeQuery();
            //将结果集通过反射技术,填充到list中
            handlerResultSet(resultSet, ret, ms.getResultType());

        } catch (SQLException e) {
            throw e;
        }finally {
            try {
                resultSet.close();
                preparedStatement.close();
                connection.close();
            } catch (SQLException e) {
                throw e;
            }
        }

        return ret;
    }


    //对prepareStatement中的占位符进行处理
    private void parameterize(PreparedStatement preparedStatement, Object parameter) throws SQLException {
        if (parameter instanceof Integer) {
            preparedStatement.setInt(1,((Integer) parameter).intValue());
        } else if (parameter instanceof Long) {
            preparedStatement.setLong(1, ((Long) parameter).longValue());
        } else if (parameter instanceof String) {
            preparedStatement.setString(1, (String)parameter);
        }
    }

    //读取resultset中的数据,并转换成目标对象
    private <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中
                ReflectionUtil.setPropToBeanFromResult(entity, resultSet);
                //对象加入返回集合中
                ret.add((E)entity);

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

ReflectionUti.java反射工具类:

package com.taoxi.mybatis.mysimple.util;


import java.lang.reflect.Field;
import java.sql.ResultSet;

public class ReflectionUtil {

    public static void setPropToBean(Object bean, String propName, Object value) {
        Field field;
        try {
            field = field = bean.getClass().getDeclaredField(propName);
            field.setAccessible(true);
            field.set(bean, value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }

    }

    public static void setPropToBeanFromResult(Object entity, ResultSet resultSet) throws Exception {
        Field[] declaredFields = entity.getClass().getDeclaredFields();
        for (int i = 0;i<declaredFields.length;i++) {
            if (declaredFields[i].getType().getSimpleName().equals("String")){
                setPropToBean(entity, declaredFields[i].getName(), resultSet.getString(declaredFields[i].getName()));
            } else if (declaredFields[i].getType().getSimpleName().equals("Integer")){
                setPropToBean(entity, declaredFields[i].getName(), resultSet.getInt(declaredFields[i].getName()));
            } else if (declaredFields[i].getType().getSimpleName().equals("Long")){
                setPropToBean(entity, declaredFields[i].getName(), resultSet.getLong(declaredFields[i].getName()));
            }
        }
    }


    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class<?> clazz = Class.forName("com.taoxi.mybatis.mysimple.entity.Dept");
        Object dept = clazz.newInstance();
        ReflectionUtil.setPropToBean(dept, "dName", "zhongguo");
        System.out.println(dept);
    }

}

源码实现工程地址:https://github.com/taoxibj/study/tree/master/mysimple

DeptMapper