手写mybatis - 2 源码解析

手写mybatis - 2 源码解析

0 缘起

本文接上文 手写mybatis - 1 概述&代码 , 目的是解释下代码的基本组成, 和设计思想,本文代码基于自己手写的 mybatis , 包结构和类名都和 mybatis 高度相似, 相当于一个mybatis 简化版吧。

1 mybatis 核心流程

mybatis 核心业务流程其实也蛮简单

1 解析 mybatis 系统配置文件,封装成 Configuration
2 解析 mapper.xml 封装成 MapperStatement 供后面使用
3 具体的业务代码,调用 dao 的代理类
4 参数的映射和处理
5 根据入参 和 MapperStatement 动态生成 执行的 sql 语句
6 通过 resulthandle 处理结果集合映射, 并且返回结果集

2 源码分析

 //读取mybatis主配置文件
InputStream is = Resources.getResourceAsStream("MybatisConfig.xml");
//创建SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//通过构建者设计模式创建工厂
SqlSessionFactory factory = builder.build(is);
//通过工厂模式创建sqlSession
SqlSession sqlSession = factory.openSession();
//通过动态代理获取对应mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//调用方法
List<User> userList = mapper.getUserList();
//遍历打印结果集
for (User user : userList) {
    System.out.println(user);
}

2.1 SqlSessionFactoryBuilder

负责加载并且解析配置文件 返回 SqlSessionFactory

public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream inputStream) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream);
            return build(parser.parse());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
}

2.2 XMLConfigBuilder

具体的解析逻辑 将输入流解析成 Configuration 供后面使用

public class XMLConfigBuilder {

    private InputStream inputStream;

    public XMLConfigBuilder(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public Configuration parse() {
        return loadXmlConfiguration(inputStream);
    }

    /**
     * 读取主配置文件
     *
     * @param inputStream
     * @return
     */
    public static Configuration loadXmlConfiguration(InputStream inputStream) {
        //使用dom4j和xpath解析主配置文件
        SAXReader reader = new SAXReader();
        Configuration config = new Configuration();
        try {
            //将主配置文件二进制流传入,得到document对象
            Document document = reader.read(inputStream);
            //获取property节点集合(用于生成连接对象的四大参数)
            List<Element> list = document.selectNodes("//property");
            for (Element element : list) {
                String name = element.attributeValue("name");
                String value = element.attributeValue("value");
                if (name.equals("username")) config.setUsername(value);
                if (name.equals("driver")) config.setDriver(value);
                if (name.equals("password")) config.setPassword(value);
                if (name.equals("url")) config.setUrl(value);
            }
            //读取主配置文件下的所有mapper映射文件
            List<Element> listMapper = document.selectNodes("//mapper");
            for (Element element : listMapper) {
                //遍历每个mapper标签,获取resource属性(mapper映射文件的全限类名)
                String resource = element.attributeValue("resource");
                //使用dom4j和xpath解析mapper映射文件
                Map<String, MappedStatement> loadMapper = loadMapper(resource, config);
                config.setMappedStatements(loadMapper);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
        return config;
    }

    /**
     * 使用dom4j和xpath解析mapper映射文件
     *
     * @param resource
     * @return
     */
    private static Map<String, MappedStatement> loadMapper(String resource, Configuration config) {
        Map<String, MappedStatement> mappers = new HashMap<String, MappedStatement>();
        //利用类加载器将配置文件转换为二进制流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(inputStream);
            //获取根节点对象
            Element rootElement = document.getRootElement();
            //获取根节点的namespace属性值
            String namespace = rootElement.attributeValue("namespace");
            //获取根节点下所有select节点
            List<Element> selectNodes = rootElement.selectNodes("//select");
            //遍历,获取每个select节点的id、resultType以及sql语句等值
            for (Element selectNode : selectNodes) {
                String id = selectNode.attributeValue("id");
                String resultType = selectNode.attributeValue("resultType");
                SqlSource sqlSource = parseDynamicSqlNode(selectNode);
                //用一个mapper对象存储以上的值
                MappedStatement mapper = new MappedStatement();
                mapper.setConfiguration(config);
                mapper.setId(id);
                mapper.setResultType(Class.forName(resultType));
                mapper.setSqlSource(sqlSource);
                //一个mapper映射文件可以有多个select(statment),不同映射文件里,select的id可以相同
                //所以将namespace+"."+id作为唯一标志,用于区分,并存入map集合里
                mappers.put(namespace + "." + id, mapper);
            }
            return mappers;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    private static SqlSource parseDynamicSqlNode(Element selectNode) {
        String sql = selectNode.getTextTrim();
        StaticSqlSource staticSqlSource = new StaticSqlSource(sql);
        return staticSqlSource;
    }
}

2.3 Configuration

具体的配置对象, 主要保存了连接信息 和 解析 mapper.xml 生成 的 mappedStatements 映射

private String username;

    private String password;

    private String url;

    private String driver;

    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

    protected final InterceptorChain interceptorChain = new InterceptorChain();

    protected Map<String, MappedStatement> mappedStatements = new HashMap();

    public Executor newExecutor(ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;

        Executor executor = null;
        if (ExecutorType.BATCH == executorType) {
        } else if (ExecutorType.REUSE == executorType) {
        } else {
            executor = new SimpleExecutor(this);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }

2.4 SqlSessionFactory

完成配置解析后要根据 Configuration 对象生成会话工厂, 会话工厂有什么用呢, 说白了 就是不断的开启会话

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public void setConfiguration(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType());
    }

    private SqlSession openSessionFromDataSource(ExecutorType execType) {
        try {
            final Executor executor = configuration.newExecutor(execType);
            return new DefaultSqlSession(configuration, executor);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

// 会话工厂的接口
public interface SqlSessionFactory {

    SqlSession openSession();
}

2.5 SqlSession

通过 上面生成的会话工厂 可以开启一个会话

SqlSession sqlSession = factory.openSession();

DefaultSqlSessionFactory

...

 @Override
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType());
    }

    private SqlSession openSessionFromDataSource(ExecutorType execType) {
        try {
            final Executor executor = configuration.newExecutor(execType);
            return new DefaultSqlSession(configuration, executor);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

2.6 Executor & SimpleExecutor

执行器,根据上面的 configuration 对象可以生成对应的执行器, 执行器就是具体干活的,负责根据参数和上面解析 MappedStatement,还有ResultSetHandler,解析sql,处理结果并且返回

public interface Executor {

    ResultSetHandler NO_RESULT_HANDLER = null;

    <E> List<E> query(MappedStatement ms, Object parameter, ResultSetHandler resultSetHandler) throws SQLException;
    
    ...

}

SimpleExecutor 就是 Executor 的简单实现

public class SimpleExecutor implements Executor {

    protected Configuration configuration;

    public SimpleExecutor(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> query(MappedStatement mappedStatement, Object parameter, ResultSetHandler resultSetHandler) throws SQLException {
        Statement stmt = null;
        Configuration configuration = mappedStatement.getConfiguration();
        StatementHandler handler = new SimpleStatementHandler(mappedStatement, parameter, resultSetHandler);
        return handler.query(stmt, resultSetHandler);
    }
}

2.7 DefaultSqlSession

有了 执行器和默认的配置对象 我们可以生成默认的会话了, 会话 其实就是负责 增删该查的, 执行器 其实也是调用的 SqlSession 去执行对应的逻辑

public class DefaultSqlSession implements SqlSession {

    private final Configuration configuration;
    private final Executor executor;


    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override
    public <T> T selectOne(String statement) throws RuntimeException {
        return this.<T>selectOne(statement, null);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) throws RuntimeException {
        // Popular vote was to return null on 0 results and throw exception on too many.
        List<T> list = this.<T>selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter) throws RuntimeException {
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms, parameter, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            throw new RuntimeException("Error querying database.  Cause: " + e);
        }
    }

    @Override
    public Configuration getConfiguration() {
        return configuration;
    }

    @Override
    public <T> T getMapper(Class<T> clazz){
        try {
            T proxyObj = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MapperProxy(this, clazz));

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

        return null;
    }
}

2.8 MapperProxy

MapperProxy 就是 mapper 对象的一个代理, 说白了 就是你定义 dao 的接口,用 jdk 动态代理生成的一个代理对象

> //通过动态代理获取对应mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

...

 public <T> T getMapper(Class<T> clazz){
        try {
            T proxyObj = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MapperProxy(this, clazz));

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

        return null;
    }

MapperProxy 其实zu

public class MapperProxy implements InvocationHandler {
    private SqlSession sqlSession;
    private Class mapperInterface;

    public MapperProxy(SqlSession sqlSession, Class mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //如果返回值是一个List集合才执行如下代码
        Class<?> returnType = method.getReturnType();
        if (returnType == List.class) {
            //代理对象调用任何方法,都会在这执行
            //真正执行Sql语句
            //执行Sql语句的工作:sqlSession对象的selectList(),也就是说咱在这要调用那个方法
            //key是"Mapper接口的全限定名"+"."+方法名
            String methodName = method.getName();//方法名
            Class<?> clazz = method.getDeclaringClass();
            String clazzName = clazz.getName();//获取接口的全限定名
            String key = clazzName + "." + methodName;
            List<Object> list = sqlSession.selectList(key, args);
            return list;
        }else {
            return null;
        }
    }

2.9 dao 的执行

dao方法调用的时候 其实执行的是代理对象的 invoke 方法

//调用方法
List<User> userList = mapper.getUserList();

--> MapperProxy --> invoke

--> sqlSession-->selectList

DefaultSqlSession

...

@Override
    public <E> List<E> selectList(String statement, Object parameter) throws RuntimeException {
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms, parameter, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            throw new RuntimeException("Error querying database.  Cause: " + e);
        }
    }
    
我们看看 executor 的 query

SimpleExecutor

...

@Override
    public <E> List<E> query(MappedStatement mappedStatement, Object parameter, ResultSetHandler resultSetHandler) throws SQLException {
        Statement stmt = null;
        Configuration configuration = mappedStatement.getConfiguration();
        StatementHandler handler = new SimpleStatementHandler(mappedStatement, parameter, resultSetHandler);
        return handler.query(stmt, resultSetHandler);
    }

StatementHandler

看了这么久终于看到原生 jdbc 的东西了。

...

public class SimpleStatementHandler implements StatementHandler {

    private MappedStatement mappedStatement;

    private ResultSetHandler resultHandler;

    private Object parameter;

    public SimpleStatementHandler(MappedStatement mappedStatement, Object parameter, ResultSetHandler resultHandler) {
        this.mappedStatement = mappedStatement;
        this.resultHandler = resultHandler;
        this.parameter = parameter;
    }

    @Override
    public <E> List<E> query(Statement statement, ResultSetHandler resultHandler) throws SQLException {
        Connection conn = null;
        PreparedStatement preparedStatement = null;
        try {
            conn = getConnection();
            preparedStatement = conn.prepareStatement(mappedStatement.getBoundSql(parameter).getSql());
            ParameterHandler parameterHandler = new DefaultParameterHandler(parameter);
            parameterHandler.setParameters(preparedStatement);
            preparedStatement.execute();
            resultHandler = new DefaultResultSetHandler(mappedStatement.getResultType(), preparedStatement.getResultSet());
            return resultHandler.handleResultSets();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private Connection getConnection() throws SQLException, ClassNotFoundException {
        Configuration configuration = mappedStatement.getConfiguration();
        Class.forName(configuration.getDriver());
        return DriverManager.getConnection(configuration.getUrl(), configuration.getUsername(), configuration.getPassword());
    }
}

2.10 SimpleStatementHandler

就是和 jdbc 打交道的,通过 ParameterHandler 处理参数,ResultSetHandler 处理返回结果并且返回

DefaultParameterHandler

public class DefaultParameterHandler implements ParameterHandler {

    private Object parameter;

    public DefaultParameterHandler(Object parameter) {
        this.parameter = parameter;
    }

    @Override
    public void setParameters(PreparedStatement psmt) throws SQLException {
        try {
            Object[] parameters = (Object[]) parameter;

            if (parameters != null && parameters.length > 0) {
                for (int i = 0; i < parameters.length; i++) {
                    if (parameters[i] instanceof Integer) {
                        psmt.setInt(i + 1, (Integer) parameters[i]);
                    } else if (parameters[i] instanceof Long) {
                        psmt.setLong(i + 1, (Long) parameters[i]);
                    } else if (parameters[i] instanceof String) {
                        psmt.setString(i + 1, String.valueOf(parameters[i]));
                    } else if (parameters[i] instanceof Boolean) {
                        psmt.setBoolean(i + 1, (Boolean) parameters[i]);
                    } else {
                        psmt.setString(i + 1, String.valueOf(parameters[i]));
                    }
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}


DefaultResultSetHandler

...

public class DefaultResultSetHandler<E> implements ResultSetHandler {

    private Class type;
    private ResultSet resultSet;

    public DefaultResultSetHandler(Class type, ResultSet resultSet) {
        this.type = type;
        this.resultSet = resultSet;
    }

    @Override
    public <E> List<E> handleResultSets() {
        try {
            ArrayList<E> resultList = new ArrayList<>();
            Object resultObject = null;
            while (resultSet.next()) {
                resultObject = new DefaultObjectFactory().create(type);
                for (Field field : resultObject.getClass().getDeclaredFields()) {
                    setValue(resultObject, field, resultSet);
                }

                resultList.add( (E) resultObject);
            }

            return resultList;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public void setValue(Object resultObject, Field field, ResultSet resultSet) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, SQLException {
        Method method = type.getMethod("set" + upperCapital(field.getName()), field.getType());
        method.invoke(resultObject, getResult(field, resultSet));
    }

    public Object getResult(Field field, ResultSet rs) throws SQLException {
        Class<?> type = field.getType();
        if (Integer.class == type) {
            return rs.getInt(field.getName());
        } else if (String.class == type) {
            return rs.getString(field.getName());
        } else if (Long.class == type) {
            return rs.getLong(field.getName());
        } else {
            return rs.getString(field.getName());
        }
    }

    private String upperCapital(String name) {
        String first = name.substring(0, 1);
        String tail = name.substring(1);
        return first.toUpperCase() + tail;
    }
}


3 拓展

mybatis 的核心逻辑到这里就告一段落了,但是mybatis 远不止这些东西,比如 sql 动态生成, 插件机制,缓存机制,事务机制, 这些东西博主以后会专门写文章介绍的。尽情期待。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值