在上一节中,我们通过对xml的解析,将mapper的映射灵活配置,同时创建了核心配置类configuration,存储至关重要的配置项信息。

思考:从整个框架结构来看我们解决了对象的代理、Mapper的映射、SQL的初步解析,那么接下来应该做什么来完善?

本章重点:连接数据库执行SQL语句并返回结果。我们将继续丰富Configuration配置类的内容。

1、目标

涉及到解析 XML 中关于 dataSource 数据源信息配置,并简历事务管理和连接池的启动和使用。并将这部分能力在 DefaultSqlSession 执行 SQL 语句时进行调用。重点放到解析配置、简历事务框架和引入 DRUID 连接池,以及初步完成 SQL 的执行和结果简单包装上。后续陆续迭代和完善框架细节。

2、设计

建立数据源连接池和 JDBC 事务工厂操作,并以 xml 配置数据源信息为入口,在 XMLConfigBuilder 中添加数据源解析和构建操作,向配置类configuration添加 JDBC 操作环境信息。以便在 DefaultSqlSession 完成对 JDBC 执行 SQL 的操作。

  • 在 parse 中解析 XML DB 链接配置信息,并完成事务工厂和连接池的注册环境到配置类的操作。

  • 与上一章节改造 selectOne 方法的处理,不再是打印 SQL 语句,而是把 SQL 语句放到 DB 连接池中进行执行,以及完成简单的结果封装。

3、实现

3.1 工程结构

mybatis-04
└── src
    ├── main
    │   └── java
    │       └── cn.sunnyy.mybatis
    │           ├── binding
    │           │   ├── MapperMethod.java
    │           │   ├── MapperProxy.java
    │           │   ├── MapperProxyFactory.java
    │           │   └── MapperRegistry.java
    │           ├── builder
    │           │   ├── xml
    │           │   │   └── XMLConfigBuilder.java
    │           │   └── BaseBuilder.java
    │           ├── datasource
    │           │   ├── druid
    │           │   │   └── DruidDataSourceFactory.java
    │           │   └── DataSourceFactory.java
    │           ├── io
    │           │   └── Resources.java
    │           ├── mapping
    │           │   ├── BoundSql.java
    │           │   ├── Environment.java
    │           │   ├── MappedStatement.java
    │           │   ├── ParameterMapping.java
    │           │   └── SqlCommandType.java
    │           ├── session
    │           │   ├── defaults
    │           │   │   ├── DefaultSqlSession.java
    │           │   │   └── DefaultSqlSessionFactory.java
    │           │   ├── Configuration.java
    │           │   ├── SqlSession.java
    │           │   ├── SqlSessionFactory.java
    │           │   ├── SqlSessionFactoryBuilder.java
    │           │   └── TransactionIsolationLevel.java
    │           ├── transaction
    │           │   ├── jdbc
    │           │   │   ├── JdbcTransaction.java
    │           │   │   └── JdbcTransactionFactory.java
    │           │   ├── Transaction.java
    │           │   └── TransactionFactory.java
    │           └── type
    │               ├── JdbcType.java
    │               └── TypeAliasRegistry.java
    └── test
        ├── java
        │   └── cn.sunnyy.mybatis.test.dao
        │       ├── dao
        │       │   └── IUserDao.java
        │       ├── po
        │       │   └── User.java
        │       └── ApiTest.java
        └── resources
            ├── mapper
            │   └──User_Mapper.xml
            └── mybatis-config-datasource.xml

数据源的解析和使用核心类关系

  • 以事务接口 Transaction 和事务工厂 TransactionFactory 的实现,包装数据源 DruidDataSourceFactory 的功能。这里的数据源连接池我们采用的是阿里的 Druid,暂时还没有实现 Mybatis 的 JNDI 和 Pooled 连接池,这部分可以后续专门以数据源连接池的专项来开发。

  • 当所有的数据源相关功能准备好后,就是在 XMLConfigBuilder 解析 XML 配置操作中,对数据源的配置进行解析以及创建出相应的服务,存放到 Configuration 的环境配置中。

  • 最后在 DefaultSqlSession#selectOne 方法中完成 SQL 的执行和结果封装,最终就把整个 Mybatis 核心脉络串联出来了。

3.2 事务管理

一次数据库的操作应该具有事务管理能力,而不是通过 JDBC 获取链接后直接执行即可。还应该把控链接、提交、回滚和关闭的操作处理。所以这里我们结合 JDBC 的能力封装事务管理。

3.2.1 事务接口

package cn.sunnyy.mybatis.transaction;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author sunzhen
 * @version 1.0
 * @date 2024/9/9 20:31
 * 事务接口 事务的基本功能
 */
public interface Transaction {

    Connection getConnection() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;
}
  • 定义标准的事务接口,链接、提交、回滚、关闭,具体可以由不同的事务方式进行实现,包括:JDBC和托管事务,托管事务是交给 Spring 这样的容器来管理。

实现类

package cn.sunnyy.mybatis.transaction.jdbc;

import cn.sunnyy.mybatis.session.TransactionIsolationLevel;
import cn.sunnyy.mybatis.transaction.Transaction;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author sunzhen
 * @version 1.0
 * @date 2024/9/9 20:33
 * JDBC 事务,直接利用 JDBC 的 commit、rollback。依赖于数据源获得的连接来管理事务范围。
 */
public class JdbcTransaction implements Transaction {

    protected Connection connection;
    protected DataSource dataSource;
    protected TransactionIsolationLevel level = TransactionIsolationLevel.NONE;
    protected boolean autoCommit;

    public JdbcTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        this.dataSource = dataSource;
        this.level = level;
        this.autoCommit = autoCommit;
    }

    public JdbcTransaction(Connection connection) {
        this.connection = connection;
    }

    @Override
    public Connection getConnection() throws SQLException {
        connection = dataSource.getConnection();
        connection.setTransactionIsolation(level.getLevel());
        connection.setAutoCommit(autoCommit);
        return connection;
    }

    @Override
    public void commit() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            connection.commit();
        }
    }

    @Override
    public void rollback() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            connection.rollback();
        }
    }

    @Override
    public void close() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            connection.close();
        }
    }
}
  • 在 JDBC 事务实现类中,封装了获取链接、提交事务等操作,其实使用的也就是 JDBC 本身提供的能力。最终都是通过Connection来实现的。

3.2.2 事务工厂

package cn.sunnyy.mybatis.transaction;

import cn.sunnyy.mybatis.session.TransactionIsolationLevel;

import javax.sql.DataSource;
import java.sql.Connection;

/**
 * @author sunzhen
 * @version 1.0
 * @date 2024/9/9 20:37
 * 事务 工厂
 */
public interface TransactionFactory {

    /**
     * 根据 Connection 创建 Transaction
     *
     * @param conn Existing database connection
     * @return Transaction
     */
    Transaction newTransaction(Connection conn);

    /**
     * 根据数据源和事务隔离级别创建 Transaction
     *
     * @param dataSource DataSource to take the connection from
     * @param level      Desired isolation level
     * @param autoCommit Desired autocommit
     * @return Transaction
     */
    Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
  • 以工厂方法模式包装 JDBC 事务实现,为每一个事务实现都提供一个对应的工厂。

3.3 类型别名注册器

在 Mybatis 框架中我们所需要的基本类型、数组类型以及自己定义的事务实现和事务工厂都需要注册到类型别名的注册器中进行管理,在我们需要使用的时候可以从注册器中获取到具体的对象类型,之后在进行实例化的方式进行使用。

3.3.1 基础注册器

package cn.sunnyy.mybatis.type;

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

/**
 * @author sunzhen
 * @version 1.0
 * @date 2024/9/9 20:41
 * 类型别名注册机
 * 在 Mybatis 框架中我们所需要的基本类型、数组类型以及自己定义的事务实现和事务工厂都需要注册到类型别名的注册器中进行管理,
 * 在我们需要使用的时候可以从注册器中获取到具体的对象类型,之后在进行实例化的方式进行使用。
 */
public class TypeAliasRegistry {

    private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<>();

    public TypeAliasRegistry() {
        // 构造函数里注册系统内置的类型别名
        registerAlias("string", String.class);

        // 基本包装类型
        registerAlias("byte", Byte.class);
        registerAlias("long", Long.class);
        registerAlias("short", Short.class);
        registerAlias("int", Integer.class);
        registerAlias("integer", Integer.class);
        registerAlias("double", Double.class);
        registerAlias("float", Float.class);
        registerAlias("boolean", Boolean.class);
    }

    public void registerAlias(String alias, Class<?> value) {
        String key = alias.toLowerCase(Locale.ENGLISH);
        TYPE_ALIASES.put(key, value);
    }

    public <T> Class<T> resolveAlias(String string) {
        String key = string.toLowerCase(Locale.ENGLISH);
        return (Class<T>) TYPE_ALIASES.get(key);
    }
}
  • 在 TypeAliasRegistry 类型别名注册器中先做了一些基本的类型注册,以及提供 registerAlias 注册方法和 resolveAlias 获取方法。

3.3.2 注册事务

package cn.sunnyy.mybatis.session;

import cn.sunnyy.mybatis.binding.MapperRegistry;
import cn.sunnyy.mybatis.datasource.druid.DruidDataSourceFactory;
import cn.sunnyy.mybatis.mapping.Environment;
import cn.sunnyy.mybatis.mapping.MappedStatement;
import cn.sunnyy.mybatis.transaction.jdbc.JdbcTransactionFactory;
import cn.sunnyy.mybatis.type.TypeAliasRegistry;

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

/**
 * @author sunzhen
 * @version 1.0
 * @date 2024/8/28 20:35
 * 配置项 会贯穿mybatis全局
 * 目前涵盖 映射注册机 和 映射语句
 */
public class Configuration {

    //环境
    protected Environment environment;

    /**
     * 映射注册机
     */
    protected MapperRegistry mapperRegistry = new MapperRegistry(this);

    /**
     * 映射的语句,存在Map里
     */
    protected final Map<String, MappedStatement> mappedStatements = new HashMap<>();

    // 类型别名注册机
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

    public Configuration() {
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);
    }

    public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
    }

    public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }

    public boolean hasMapper(Class<?> type) {
        return mapperRegistry.hasMapper(type);
    }

    public void addMappedStatement(MappedStatement ms) {
        mappedStatements.put(ms.getId(), ms);
    }

    public MappedStatement getMappedStatement(String id) {
        return mappedStatements.get(id);
    }

    public TypeAliasRegistry getTypeAliasRegistry() {
        return typeAliasRegistry;
    }

    public Environment getEnvironment() {
        return environment;
    }

    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}
  • 在 Configuration 配置选项类中,添加类型别名注册机,通过构造函数添加 JDBC、DRUID 注册操作

  • 我们应该注意到,整个 Mybatis 的操作都是使用 Configuration 配置项进行串联流程,所以所有内容都会在 Configuration 中进行链接。

3.4 解析数据源配置

package cn.sunnyy.mybatis.builder.xml;

import cn.sunnyy.mybatis.builder.BaseBuilder;
import cn.sunnyy.mybatis.datasource.DataSourceFactory;
import cn.sunnyy.mybatis.io.Resources;
import cn.sunnyy.mybatis.mapping.BoundSql;
import cn.sunnyy.mybatis.mapping.Environment;
import cn.sunnyy.mybatis.mapping.MappedStatement;
import cn.sunnyy.mybatis.mapping.SqlCommandType;
import cn.sunnyy.mybatis.session.Configuration;
import cn.sunnyy.mybatis.transaction.TransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;

import javax.sql.DataSource;
import java.io.Reader;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author sunzhen
 * @version 1.0
 * @date 2024/8/28 20:50
 * XML配置构建器,建造者模式,继承BaseBuilder
 */
public class XMLConfigBuilder extends BaseBuilder {

    private Element root;

    public XMLConfigBuilder(Reader reader) {
        // 1. 调用父类初始化Configuration
        super(new Configuration());
        // 2. dom4j 处理 xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new InputSource(reader));
            root = document.getRootElement();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析配置;类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器
     *
     * @return Configuration
     */
    public Configuration parse() {
        try {
            // 环境
            environmentsElement(root.element("environments"));
            // 解析映射器
            mapperElement(root.element("mappers"));
        } catch (Exception e) {
            throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
        return configuration;
    }

    /**
     * 对传入的数据源xml进行解析
     * @param context
     * @throws Exception
     */
    private void environmentsElement(Element context) throws Exception {

        /**
         * xml中的格式
         *     <environments default="development">
         *         <environment id="development">
         *             <transactionManager type="JDBC"/>
         *             <dataSource type="DRUID">
         *                 <property name="driver" value="com.mysql.jdbc.Driver"/>
         *                 <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
         *                 <property name="username" value="root"/>
         *                 <property name="password" value="123456"/>
         *             </dataSource>
         *         </environment>
         *     </environments>
         */

        // 默认数据源
        String environment = context.attributeValue("default");

        // 遍历数据源配置
        List<Element> environmentList = context.elements("environment");
        for (Element e : environmentList) {
            String id = e.attributeValue("id");
            // 只对启用的数据源进行解析
            if (environment.equals(id)) {
                // 事务管理器 -- 获取配置的事务管理
                TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry
                        .resolveAlias(e.element("transactionManager")
                                .attributeValue("type"))
                        .newInstance();

                // 数据源 -- 获取数据源项信息
                Element dataSourceElement = e.element("dataSource");
                DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry
                        .resolveAlias(dataSourceElement.attributeValue("type")).newInstance();
                // 数据源连接信息
                List<Element> propertyList = dataSourceElement.elements("property");
                Properties props = new Properties();
                for (Element property : propertyList) {
                    props.setProperty(property.attributeValue("name"), property.attributeValue("value"));
                }

                // 创建数据源
                dataSourceFactory.setProperties(props);
                DataSource dataSource = dataSourceFactory.getDataSource();

                // 构建环境 -- 根据 事务和数据源 构建环境
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                        .transactionFactory(txFactory)
                        .dataSource(dataSource);

                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }

    /**
     * 对传入的mybatis主xml文件解析,把多条mapper全部读取到configuration中去
     * @param mappers mybatis主xml文件的Element
     * @throws Exception Exception
     */
    private void mapperElement(Element mappers) throws Exception {
        /**
         * Element mappers 中的文件格式
         *      <mappers>
         *         <mapper resource="mapper/User_Mapper.xml"/>
         *     </mappers>
         */
        // 获取到mapper标签内容
        List<Element> mapperList = mappers.elements("mapper");
        // 遍历mapper标签
        for (Element e : mapperList) {
            // 获取到 resource 中的mapper路径
            String resource = e.attributeValue("resource");
            // 再次读取Reader--这次是子mapper的
            Reader reader = Resources.getResourceAsReader(resource);
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(new InputSource(reader));
            /**
             * 子mapper的格式
             *  <mapper namespace="cn.sunnyy.mybatis.test.dao.IUserDao">
             *
             *     <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.sunnyy.mybatis.test.po.User">
             *         SELECT id, userId, userHead, createTime
             *         FROM user
             *         where id = #{id}
             *     </select>
             *
             * </mapper>
             */
            Element root = document.getRootElement();
            //命名空间 mapper的全路径
            String namespace = root.attributeValue("namespace");

            // SELECT 标签(目前先讨论只有select的情况)
            List<Element> selectNodes = root.elements("select");
            for (Element node : selectNodes) {
                // 方法名
                String id = node.attributeValue("id");
                // 入参类型
                String parameterType = node.attributeValue("parameterType");
                // 返回参数类型
                String resultType = node.attributeValue("resultType");
                // sql语句
                String sql = node.getText();

                // ? 匹配
                Map<Integer, String> parameter = new HashMap<>();
                // 正则表达式 匹配形如 #{something} 的字符串
                Pattern pattern = Pattern.compile("(#\\{(.*?)})");
                Matcher matcher = pattern.matcher(sql);
                for (int i = 1; matcher.find(); i++) {
                    // g1 将获取匹配的整个字符串(例如 #{something})
                    String g1 = matcher.group(1);
                    // g2 将获取大括号内的内容(例如 something)
                    String g2 = matcher.group(2);
                    // 参数占位列表
                    parameter.put(i, g2);
                    //  SQL 字符串中的 #{something} 替换为 ?  这通常用于准备 SQL 语句,以防止 SQL 注入攻击
                    sql = sql.replace(g1, "?");
                }

                String msId = namespace + "." + id;
                String nodeName = node.getName();
                // 匹配sql语句类型
                SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

                // sql语句优化存储,默认绑定
                BoundSql boundSql = new BoundSql(sql, parameter, parameterType, resultType);
                MappedStatement mappedStatement = new MappedStatement.Builder(
                        configuration,
                        msId,
                        sqlCommandType,
                        boundSql).build();

                // 添加解析 SQL
                configuration.addMappedStatement(mappedStatement);
            }

            // 注册Mapper映射器
            configuration.addMapper(Resources.classForName(namespace));
        }
    }
}
  • 以 XMLConfigBuilder#parse 解析扩展对数据源解析操作,在 environmentsElement 方法中包括事务管理器解析和从类型注册器中读取到事务工程的实现类,同理数据源也是从类型注册器中获取。(重点关注environmentsElement 部分,其余基本是上一节内容)

  • 最后把事务管理器和数据源的处理,通过环境构建 Environment.Builder 存放到 Configuration 配置项中,也就可以通过 Configuration 存在的地方都可以获取到数据源了。

3.5 SQL执行和结果封装

在上一章节中在 DefaultSqlSession#selectOne 只是打印了 XML 中配置的 SQL 语句,现在把数据源的配置加载进来以后,就可以把 SQL 语句放到数据源中进行执行以及结果封装。

package cn.sunnyy.mybatis.session.impl;

import cn.sunnyy.mybatis.mapping.BoundSql;
import cn.sunnyy.mybatis.mapping.Environment;
import cn.sunnyy.mybatis.mapping.MappedStatement;
import cn.sunnyy.mybatis.session.Configuration;
import cn.sunnyy.mybatis.session.SqlSession;
import com.alibaba.fastjson.JSON;

import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author sunzhen
 * @version 1.0
 * @date 2024/8/26 22:14
 * 默认SqlSession实现类
 */
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }
    @Override
    public <T> T selectOne(String statement) {
        return (T) ("你的操作被代理了!" + statement);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        try {
            MappedStatement mappedStatement = configuration.getMappedStatement(statement);
            Environment environment = configuration.getEnvironment();

            Connection connection = environment.getDataSource().getConnection();
            BoundSql boundSql = mappedStatement.getBoundSql();
            PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
            preparedStatement.setLong(1, Long.parseLong(((Object[]) parameter)[0].toString()));
            ResultSet resultSet = preparedStatement.executeQuery();

            List<T> objList = resultSet2Obj(resultSet, Class.forName(boundSql.getResultType()));
            return objList.get(0);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
        List<T> list = new ArrayList<>();
        try {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            // 每次遍历行值
            while (resultSet.next()) {
                T obj = (T) clazz.newInstance();
                for (int i = 1; i <= columnCount; i++) {
                    Object value = resultSet.getObject(i);
                    String columnName = metaData.getColumnName(i);
                    String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
                    Method method;
                    if (value instanceof Timestamp) {
                        method = clazz.getMethod(setMethod, Date.class);
                    } else {
                        method = clazz.getMethod(setMethod, value.getClass());
                    }
                    method.invoke(obj, value);
                }
                list.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

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

    @Override
    public Configuration getConfiguration() {
        return configuration;
    }
}
  • 在 selectOne 方法中获取 Connection 数据源链接,并简单的执行 SQL 语句,并对执行的结果进行封装处理。

  • 因为目前这部分主要是为了大家串联出整个功能结构,所以关于 SQL 的执行、参数传递和结果封装都是写死的,后续我们进行扩展。

4、测试

4.1 准备

4.1.1 创建表

创建一个数据库名称为 mybatis 并在库中创建表 user 以及添加测试数据,如下:

CREATE TABLE
    user
    (
        id bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',
        userId VARCHAR(9) COMMENT '用户ID',
        userHead VARCHAR(16) COMMENT '用户头像',
        createTime TIMESTAMP NULL COMMENT '创建时间',
        updateTime TIMESTAMP NULL COMMENT '更新时间',
        userName VARCHAR(64),
        PRIMARY KEY (id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
insert into user (id, userId, userHead, createTime, updateTime, userName) values (1, '10001', '1_04', '2022-04-13 00:00:00', '2022-04-13 00:00:00', '孙哥');    

4.1.2 配置数据源

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="DRUID">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.3.60:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/User_Mapper.xml"/>
    </mappers>

</configuration>
  • 通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password

  • 另外这里要注意下,DataSource 配置的是 DRUID,因为我们实现的是这个数据源的处理方式。

4.1.3 配置Mapper

<?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="cn.sunnyy.mybatis.test.dao.IUserDao">

    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.sunnyy.mybatis.test.po.User">
        SELECT id, userId, userName, userHead
        FROM user
        where id = #{id}
    </select>

</mapper>

4.2 单元测试

    @Test
    public void test_SqlSessionFactory() throws IOException {
        // 1. 从SqlSessionFactory中获取SqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(
                Resources.getResourceAsReader("mybatis-config-datasource.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 2. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 3. 测试验证
        User user = userDao.queryUserInfoById(1L);
        logger.info("测试结果:{}", JSON.toJSONString(user));
    }
  • 本章节的主要操作都是在解析内容的添加,处理 XML 配置中的数据源信息,以及解析后可以在 DefaultSqlSession 中调用数据源执行 SQL 语句并返回结果。

  • 所以我们这里单独把这部分提取出来进行测试验证,通过基于这样的测试,可以更好的在 Sequence Diagram 中生成对应的 UML 方便读者更加容易理解本章节新增的内容和流程。

测试结果

22:14:14.357 [main] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
22:14:14.581 [main] INFO  cn.sunnyy.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"孙哥"}

Process finished with exit code 0
  • 测试结果是通过的,这里更多的是为了体现出整个调用关系链路。从 XML 数据源元素配置的解析,到 Configuration 资源的注册以及写入相关配置到配置项,并在 DefaultSqlSession 中进行使用。同时这里跳过代理方式获取 Mapper 而是直接拿到 SqlSession 执行 selectOne 方法的方式进行处理,因为这样更加容易观察整个功能的迭代开发。

5、总结

  • 以解析 XML 配置解析为入口,添加数据源的整合和包装,引出事务工厂对 JDBC 事务的处理,并加载到环境配置中进行使用。

  • 那么通过数据源的引入就可以在 DefaultSqlSession 中从 Configuration 配置引入环境信息,把对应的 SQL 语句提交给 JDBC 进行处理并简单封装结果数据。

  • 结合本章节建立起来的框架结构,数据源、事务、简单的SQL调用,下个章节将继续这部分内容的扩展处理,让整个功能模块逐渐完善。