手写Mybatis:第5章-数据源的解析、创建和使用

news2024/11/24 13:43:03

文章目录

  • 一、目标:数据源的解析、创建和使用
  • 二、设计:数据源的解析、创建和使用
  • 三、实现:数据源的解析、创建和使用
    • 3.1 引入依赖
    • 3.2 工程结构
    • 3.3 数据源解析、创建和使用关系图
    • 3.4 事务管理接口和事务工厂
      • 3.4.1 事务的隔离级别
      • 3.4.2 定义事务接口
      • 3.4.3 事务接口实现类
      • 3.4.4 事务工厂
      • 3.4.5 JDBC事务工厂
    • 3.5 数据源工厂创建
      • 3.5.1 数据源工厂
      • 3.5.2 阿里的druid数据库连接池
    • 3.6 创建配置环境基础类
      • 3.6.1 绑定的SQL对象
      • 3.6.2 参数映射对象
      • 3.6.3 环境配置类
      • 3.6.4 映射器语句类
    • 3.7 类型别名注册器
      • 3.7.1 JDBC枚举类型
      • 3.7.2 类型别名注册机
      • 3.7.3 配置项
    • 3.8 解析数据源配置
      • 3.8.1 构建器基类
      • 3.8.2 XML配置构建器
    • 3.9 SQL执行和结果封装
      • 3.9.1 默认sqlSession实现类
  • 四、测试:数据源的解析、创建和使用
    • 4.1 创建 mybatis 数据库并添加数据库表
    • 4.2 提供 DAO 接口 和 User 实体类
      • 4.2.1 用户持久层
      • 4.2.2 用户类
    • 4.3 配置数据源和配置Mapper
      • 4.3.1 配置文件
      • 4.3.2 用户接口配置文件
    • 4.4 单元测试
    • 4.5 功能验证
  • 五、总结:数据源的解析、创建和使用

一、目标:数据源的解析、创建和使用

💡 解析 XML 中关于 dataSource 数据源信息配置,并建立事务管理和连接池的启动和使用,并将这部分能力在 DefaultSqlSession 执行 SQL 语句时进行调用。

二、设计:数据源的解析、创建和使用

💡 怎么完成对数据源的解析?

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

在这里插入图片描述

  • parse 中解析 XML DB 链接配置信息,并完成事务工厂和连接池的注册环境到配置类的操作。
  • 调用 selectOne 方法的处理,把 SQL 语句放到 DB 连接池中进行执行,以及完成简单的结果封装。

三、实现:数据源的解析、创建和使用

3.1 引入依赖

pom.xml

<dependencies>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.9</version>
    </dependency>
</dependencies>

3.2 工程结构

mybatis-step-04
|-src
    |-main
    |   |-java
    |       |-com.lino.mybatis
    |           |-binding
    |           |   |-MapperMethod.java
    |           |   |-MapperProxy.java
    |           |   |-MapperProxyFactory.java
    |           |   |-MapperRegistry.java
    |           |-builder
    |           |   |-xml
    |           |   |   |-XMLConfigBuilder.java
    |           |   |-BaseBuilder.java
    |           |-datasource
    |           |   |-druid
    |           |   |   |-DruidDataSourceFacroty.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
        |   |-com.lino.mybatis.test
        |   |-dao
        |   |   |-IUserDao.java
        |   |-po
        |   |   |-User.java
        |   |-ApiTest.java
        |-resources
            |-mapper
            |   |-User_Mapper.xml
            |-mybatis-config-datasource.xml

3.3 数据源解析、创建和使用关系图

在这里插入图片描述

  • 以事务接口 Transaction 和事务工厂 TransactionFactory 的实现,包装数据源 DruidDataSourceFactory 的功能。数据源采用阿里的 druid
  • 当所有的数据源相关功能准备好之后,在 XMLConfigBuilder 解析 XML 配置操作中,对数据源的配置进行解析以及创建出相应的服务,存在到 Configuration 的环境配置中。
  • 最后在 DefaultSqlSession#selectOne 方法中完成 SQL 的执行和结果封装,最终就把整个 Mybatis 的核心脉络串联起来。

在这里插入图片描述

3.4 事务管理接口和事务工厂

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

3.4.1 事务的隔离级别

TransactionIsolationLevel.java

package com.lino.mybatis.session;

import java.sql.Connection;

/**
 * @description: 事务的隔离级别
 */
public enum TransactionIsolationLevel {

    //包括JDBC支持的5个级别
    NONE(Connection.TRANSACTION_NONE),
    READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
    READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
    REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
    SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);

    private final int level;

    TransactionIsolationLevel(int level) {
        this.level = level;
    }

    public int getLevel() {
        return level;
    }
}

3.4.2 定义事务接口

Transaction.java

package com.lino.mybatis.transaction;

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

/**
 * @description: 事务接口
 */
public interface Transaction {

    /**
     * 获取数据库连接
     *
     * @return 数据库连接
     * @throws SQLException SQL异常
     */
    Connection getConnection() throws SQLException;

    /**
     * 提交
     *
     * @throws SQLException SQL异常
     */
    void commit() throws SQLException;

    /**
     * 回滚
     *
     * @throws SQLException SQL异常
     */
    void rollback() throws SQLException;

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

3.4.3 事务接口实现类

JdbcTransaction.java

package com.lino.mybatis.transaction.jdbc;

import com.lino.mybatis.session.TransactionIsolationLevel;
import com.lino.mybatis.transaction.Transaction;

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

/**
 * @description: 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 本身提供的能力。

3.4.4 事务工厂

TransactionFactory.java

package com.lino.mybatis.transaction;

import com.lino.mybatis.session.TransactionIsolationLevel;

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

/**
 * @description: 事务工厂
 */
public interface TransactionFactory {

    /**
     * 根据 Connection 创建 事务
     *
     * @param conn 连接
     * @return 事务对象
     */
    Transaction newTransaction(Connection conn);

    /**
     * 根据数据源和事务隔离级别创建事务
     *
     * @param dataSource 数据源
     * @param level      事务隔离级别
     * @param autoCommit 是否自动提交
     * @return 事务
     */
    Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
  • 以工厂方法模式包装 JDBC 事务实现,为每一个事务实现都提供一个对应的工厂。
  • 与简单工厂的接口包装不同。

3.4.5 JDBC事务工厂

JdbcTransactionFactory.java

package com.lino.mybatis.transaction.jdbc;

import com.lino.mybatis.session.TransactionIsolationLevel;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.TransactionFactory;

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

/**
 * @description: JDBC 事务工厂
 */
public class JdbcTransactionFactory implements TransactionFactory {

    @Override
    public Transaction newTransaction(Connection conn) {
        return new JdbcTransaction(conn);
    }

    @Override
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new JdbcTransaction(dataSource, level, autoCommit);
    }
}

3.5 数据源工厂创建

3.5.1 数据源工厂

DataSourceFactory.java

package com.lino.mybatis.datasource;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * @description: 数据源工厂
 */
public interface DataSourceFactory {

    /**
     * 添加数据源
     *
     * @param props 数据源信息
     */
    void setProperties(Properties props);

    /**
     * 获取数据源
     *
     * @return 数据源
     */
    DataSource getDataSource();
}

3.5.2 阿里的druid数据库连接池

DruidDataSourceFactory.java

package com.lino.mybatis.datasource.druid;

import com.alibaba.druid.pool.DruidDataSource;
import com.lino.mybatis.datasource.DataSourceFactory;
import javax.sql.DataSource;
import java.util.Properties;

/**
 * @description: Druid 数据源工厂
 */
public class DruidDataSourceFactory implements DataSourceFactory {

    private Properties props;

    @Override
    public void setProperties(Properties props) {
        this.props = props;
    }

    @Override
    public DataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(props.getProperty("driver"));
        dataSource.setUrl(props.getProperty("url"));
        dataSource.setUsername(props.getProperty("username"));
        dataSource.setPassword(props.getProperty("password"));
        return dataSource;
    }
}

3.6 创建配置环境基础类

3.6.1 绑定的SQL对象

BoundSql.java

package com.lino.mybatis.mapping;

import java.util.Map;

/**
 * @description: 绑定的SQL,是从SqlSource而来,将动态内容都处理完成得到的SQL语句字符串,其中包括?,还有绑定的参数
 */
public class BoundSql {

    private String sql;
    private Map<Integer, String> parameterMappings;
    private String parameterType;
    private String resultType;

    public BoundSql(String sql, Map<Integer, String> parameterMappings, String parameterType, String resultType) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterType = parameterType;
        this.resultType = resultType;
    }

    public String getSql() {
        return sql;
    }

    public Map<Integer, String> getParameterMappings() {
        return parameterMappings;
    }

    public String getParameterType() {
        return parameterType;
    }

    public String getResultType() {
        return resultType;
    }
}

3.6.2 参数映射对象

ParameterMapping.java

package com.lino.mybatis.mapping;

import cn.hutool.db.meta.JdbcType;
import com.lino.mybatis.session.Configuration;

/**
 * @description: 参数映射 #{property,javaType=int,jdbcType=NUMERIC}
 */
public class ParameterMapping {

    private Configuration configuration;
    /**
     * property
     */
    private String property;
    /**
     * javaType = int
     */
    private Class<?> javaType = Object.class;
    /**
     * javaType = NUMERIC
     */
    private JdbcType jdbcType;

    public ParameterMapping() {
    }

    public static class Builder {

        private ParameterMapping parameterMapping = new ParameterMapping();

        private Builder(Configuration configuration, String property) {
            parameterMapping.configuration = configuration;
            parameterMapping.property = property;
        }

        public Builder javaType(Class<?> javaType) {
            parameterMapping.javaType = javaType;
            return this;
        }

        public Builder jdbcType(JdbcType jdbcType) {
            parameterMapping.jdbcType = jdbcType;
            return this;
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public String getProperty() {
        return property;
    }

    public Class<?> getJavaType() {
        return javaType;
    }

    public JdbcType getJdbcType() {
        return jdbcType;
    }
}

3.6.3 环境配置类

Environment.java

package com.lino.mybatis.mapping;

import com.lino.mybatis.transaction.TransactionFactory;
import javax.sql.DataSource;

/**
 * @description: 环境
 */
public final class Environment {

    /**
     * 环境id
     */
    private final String id;
    /**
     * 事务工厂
     */
    private final TransactionFactory transactionFactory;
    /**
     * 数据源
     */
    private final DataSource dataSource;

    public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
        this.id = id;
        this.transactionFactory = transactionFactory;
        this.dataSource = dataSource;
    }

    public static class Builder {

        private String id;
        private TransactionFactory transactionFactory;
        private DataSource dataSource;

        public Builder(String id) {
            this.id = id;
        }

        public Builder transactionFactory(TransactionFactory transactionFactory) {
            this.transactionFactory = transactionFactory;
            return this;
        }

        public Builder dataSource(DataSource dataSource) {
            this.dataSource = dataSource;
            return this;
        }

        public String id() {
            return this.id;
        }

        public Environment build() {
            return new Environment(this.id, this.transactionFactory, this.dataSource);
        }
    }

    public String getId() {
        return id;
    }

    public TransactionFactory getTransactionFactory() {
        return transactionFactory;
    }

    public DataSource getDataSource() {
        return dataSource;
    }
}

3.6.4 映射器语句类

MappedStatement.java

package com.lino.mybatis.mapping;

import com.lino.mybatis.session.Configuration;
import java.util.Map;

/**
 * @description: 映射器语句类
 */
public class MappedStatement {

    private Configuration configuration;
    private String id;
    private SqlCommandType sqlCommandType;
    private BoundSql boundSql;

    public MappedStatement() {
    }

    public static class Builder {

        private MappedStatement mappedStatement = new MappedStatement();

        public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, BoundSql boundSql) {
            mappedStatement.configuration = configuration;
            mappedStatement.id = id;
            mappedStatement.sqlCommandType = sqlCommandType;
            mappedStatement.boundSql = boundSql;
        }

        public MappedStatement build() {
            assert mappedStatement.configuration != null;
            assert mappedStatement.id != null;
            return mappedStatement;
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public String getId() {
        return id;
    }

    public SqlCommandType getSqlCommandType() {
        return sqlCommandType;
    }

    public BoundSql getBoundSql() {
        return boundSql;
    }
}
  • 去除 sql 多个参数字段,添加 BoundSql SQL 对象

3.7 类型别名注册器

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

3.7.1 JDBC枚举类型

JdbcType.java

package com.lino.mybatis.type;

import java.sql.Types;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: JDBC枚举类型
 */
public enum JdbcType {

    // JDBC枚举类型
    INTEGER(Types.INTEGER),
    FLOAT(Types.FLOAT),
    DOUBLE(Types.DOUBLE),
    DECIMAL(Types.DECIMAL),
    VARCHAR(Types.VARCHAR),
    TIMESTAMP(Types.TIMESTAMP);

    public final int TYPE_CODE;
    private static Map<Integer, JdbcType> codeLookup = new HashMap<>();

    static {
        for (JdbcType type : JdbcType.values()) {
            codeLookup.put(type.TYPE_CODE, type);
        }
    }

    JdbcType(int code) {
        this.TYPE_CODE = code;
    }

    public static JdbcType forCode(int code) {
        return codeLookup.get(code);
    }
}

3.7.2 类型别名注册机

TypeAliasRegistry.java

package com.lino.mybatis.type;

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

/**
 * @description: 类型别名注册机
 */
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.7.3 配置项

Configuration.java

package com.lino.mybatis.session;

import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.transaction.jdbc.JdbcTransaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;

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

/**
 * @description: 配置项
 * @author: lingjian
 * @createDate: 2022/11/5 16:27
 */
public class Configuration {

    /**
     * 环境
     */
    protected Environment environment;

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

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

    /**
     * 类型别名注册机
     */
    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;
    }
}
  • 添加 Environment 配置环境
  • 添加 TypeAliasRegistry 类型别名注册机,添加初始化添加 JDBC 事务工厂和 DRUID 数据源工厂
  • Configuration 配置选项中,添加类型别名注册机,通过构造函数添加 JDBCDRUID 注册操作。
  • 整个 Mybatis 的操作都是使用 Configuration 配置项进行串联流程,所有的内容都会在 Configuration 中进行连接。

3.8 解析数据源配置

💡 通过在 XML 解析器 XMLConfigBuilder 中,扩展对环境信息的解析。这里把数据源、事务类内容成为操作 SQL 环境。
解析后把配置信息写入到 Configuration 配置项中,便于后续使用。

3.8.1 构建器基类

BaseBuilder.java

package com.lino.mybatis.builder;

import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeAliasRegistry;

/**
 * @description: 构建器的基类,建造者模式
 */
public class BaseBuilder {

    protected final Configuration configuration;
    protected final TypeAliasRegistry typeAliasRegistry;

    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    }

    public Configuration getConfiguration() {
        return configuration;
    }
}
  • 添加 TypeAliasRegistry 类型别名注册机

3.8.2 XML配置构建器

XMLConfigBuilder.java

package com.lino.mybatis.builder.xml;

import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.datasource.DataSourceFactory;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.transaction.TransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
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;

/**
 * @description: XML配置构建器,建造者模式,集成BaseBuilder
 */
public class XMLConfigBuilder extends BaseBuilder {

    private Element root;
    private static final Pattern pattern = Pattern.compile("(#\\{(.*?)})");

    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;
    }

    private void environmentsElement(Element context) throws Exception {
        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());
            }

        }
    }

    private void mapperElement(Element mappers) throws Exception {
        List<Element> mapperList = mappers.elements("mapper");
        for (Element e : mapperList) {
            String resource = e.attributeValue("resource");
            Reader reader = Resources.getResourceAsReader(resource);
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(new InputSource(reader));
            Element root = document.getRootElement();
            // 命名空间
            String namespace = root.attributeValue("namespace");

            // 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");
                String sql = node.getText();

                // ? 匹配
                Map<Integer, String> parameter = new HashMap<>(16);
                Matcher matcher = pattern.matcher(sql);
                for (int i = 1; matcher.find(); i++) {
                    String g1 = matcher.group(1);
                    String g2 = matcher.group(2);
                    parameter.put(i, g2);
                    sql = sql.replace(g1, "?");
                }

                String msId = namespace + "." + id;
                String nodeName = node.getName();
                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));
        }

    }
}
  • 添加 environmentsElement 配置环境处理
  • 修改 mapperElement 中创建 MappedStatement 映射器语句类的初始化
  • XMLConfigBuilder#parse 解析扩展对数据源解析操作, 在 environmentsElement 方法中包括事务管理器解析和从注册器中读取到事务工程的实现类,同理数据源也是从类型注册器中获取。
  • 最后把事务管理器和数据源的处理,通过环境构建 Environment.Builder 存放到 Configuration 配置项中,也就可以通过 Configuration 存在的地方都可以获取到数据源了。

3.9 SQL执行和结果封装

3.9.1 默认sqlSession实现类

DefaultSqlSession.java

package com.lino.mybatis.session.defaults;

import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import java.lang.reflect.Method;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * @description: 默认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, LocalDateTime.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,引入配置环境和数据源,将之前打印改为调用 JDBC 连接数据查询 SQL
  • 添加 resultSet2Obj 返回结果处理方法
  • selectOne 方法中获取 Connection 数据源连接,并简单的执行 SQL 语句,并对执行的结果进行封装处理。

四、测试:数据源的解析、创建和使用

4.1 创建 mybatis 数据库并添加数据库表

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-11-07 00:00:00', '2022-11-07 00:00:00', '小零');
  • 创建一个数据库名为 mybatis 的数据库
  • 在数据库 mybatis 中创建表 user,并添加测试数据

4.2 提供 DAO 接口 和 User 实体类

4.2.1 用户持久层

IUserDao.java

package com.lino.mybatis.test.dao;

import com.lino.mybatis.test.po.User;

/**
 * @Description: 用户持久层
 */
public interface IUserDao {

    /**
     * 根据ID查询用户信息
     *
     * @param uId ID
     * @return String 名称
     */
    User queryUserInfoById(Long uId);
}
  • 返回结果改为 User 实体类

4.2.2 用户类

User.java

package com.lino.mybatis.test.po;

import cn.hutool.core.date.DateTime;
import java.time.LocalDateTime;
import java.util.Date;

/**
 * @description: 用户实例类
 */
public class User {

    private Long id;
    /**
     * 用户ID
     */
    private String userId;
    /**
     * 头像
     */
    private String userHead;
    /**
     * 用户名称
     */
    private String userName;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserHead() {
        return userHead;
    }

    public void setUserHead(String userHead) {
        this.userHead = userHead;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}
  • 添加 userName 属性

4.3 配置数据源和配置Mapper

4.3.1 配置文件

mybatis-config-datasource.xml

<?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://127.0.0.1:3306/mybatis?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/User_Mapper.xml"/>
    </mappers>
</configuration>
  • 添加 environments 数据库配置信息。
  • 通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password
  • DataSource 配置的是 DRUID,目前只实现了这个数据源。

4.3.2 用户接口配置文件

User_Mapper.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.lino.mybatis.test.dao.IUserDao">
    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.lino.mybatis.test.po.User">
        SELECT id, userId, userHead, userName
        FROM user
        WHERE id = #{id}
    </select>
</mapper>
  • 去除 createTime 查询字段, 添加 userName 查询字段。

4.4 单元测试

ApiTest

/**
 * 测试映射器注册机
 */
@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));
}

测试结果

08:32:18.875 [main] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
08:32:19.623 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小零哥"}
  • 从测试结果看,通过对数据源的解析、包装和使用,已经可以对 SQL 语句进行执行和包装返回的结果信息了。

4.5 功能验证

ApiTest

@Test
public void test_selectOne() throws IOException {
    // 解析XML
    Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");
    XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader);
    Configuration configuration = xmlConfigBuilder.parse();

    // 获取 DefaultSqlSession
    SqlSession sqlSession = new DefaultSqlSession(configuration);

    // 执行查询:默认是一个集合参数
    Object[] req = {1L};
    Object result = sqlSession.selectOne("com.lino.mybatis.test.dao.IUserDao.queryUserInfoById", req);
    logger.info("测试结果:{}", JSON.toJSONString(result));
}
  • 对本章节新增的内容进行提取,进行测试验证。
  • 新增内容:解析内容的添加、处理 XML 配置中的数据源信息,以及解析后可以在 DefaultSqlSession 中调用数据源执行 SQL 语句并返回结果。

测试结果

10:19:43.519 [main] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
10:19:44.306 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
  • 测试结果是通过的。
  • XML 数据源元素配置的解析,到 Configuration 资源的注册以及写入相关配置到配置项,并在 DefaultSqlSession 中进行使用。
  • 同时这里跳过代理方式获取 Mapper 而是直接拿到 SqlSession 执行 selectOne 方法的方式进行处理,这样更容易观察整个功能的迭代开发。

请添加图片描述

五、总结:数据源的解析、创建和使用

  • 以解析 XML 配置解析为入口,添加数据源的整合和包装,引出事务工厂对 JDBC 事务的处理,并加载到环境配置中进行运用。
  • 通过数据源的引入就可以在 DefaultSqlSession 中从 Configuration 配置引入环境信息,把对应的 SQL 语句提交给 JDBC 进行处理并简单封装结果数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/959962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重新理解百度智能云:写在大模型开放后的24小时

在这些回答背后共同折射出的一个现实是——大模型不再是一个单选题&#xff0c;而更是一个综合题。在这个新的时代帆船上&#xff0c;产品、服务、安全、开放等全部都需要成为必需品&#xff0c;甚至是从企业的落地层面来看&#xff0c;这些更是刚需品。 作者| 皮爷 出品|产…

大数据课程K12——Spark的MLlib概述

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Spark的MLlib概念; ⚪ 掌握Spark的MLlib基本数据模型; ⚪ 掌握Spark的MLlib统计量基础; 一、Spark MLlib介绍 1. 概述 MLlib是Apache Spark的可迭代机器学习库。 2. 易于使用 …

改革企业治理结构,建立国有企业全面预算管理制度

随着我国市场经济的推广&#xff0c;国有企业进入到改革发展的必经之路上&#xff0c;企业应当结合自身实际情况加强成本管控&#xff0c;提高管理效率&#xff0c;为企业的发展提供有力保障。近年来&#xff0c;全面预算管理的理念在国有企业实施范围内不断扩大&#xff0c;加…

联发科MTK6762/MT6762核心板_安卓主板小尺寸低功耗4G智能模块

MT6762安卓核心板是一款基于MTK平台的高性能智能模块&#xff0c;是一款工业级的产品。该芯片也被称为Helio P22。这款芯片内置了Arm Cortex-A53 CPU&#xff0c;最高可运行于2.0GHz。同时&#xff0c;它还提供灵活的LPDDR3/LPDDR4x内存控制器&#xff0c;此外&#xff0c;Medi…

5年前我们摸爬滚打进入测试行业,如今的你后悔吗?

记得在求职的时候&#xff0c;面试官经常问我&#xff1a;“为什么要选择软件测试工作?”而我也会经常说一堆自己有的没的优势去应付。 工作这么久了&#xff0c;也不再浮躁&#xff0c;静下心来回忆当初选择软件测试工作的历程&#xff0c;也是对自己职业生涯的一次回顾。 一…

GreenPlum的gpfdist使用与原理流程分析

一、简介 GreenPlum 的数据导入功能作为对数据源的一种扩充&#xff0c;数据导入的方式有&#xff1a; 1、insert 该方式通过 sql 语句&#xff0c;把数据一条一条插入至表中。这种方式&#xff0c;不仅读取数据慢&#xff08;一条一条读取&#xff09;&#xff0c;且数据需要…

我们学到的关于减少客户流失的 4 个经验教训

客户流失 – 这两个词会让任何企业主的脊背不寒而栗。用最简单的术语来说&#xff0c;它是在特定时间内停止使用您的服务的客户百分比。这很糟糕&#xff0c;但这并不全是厄运和阴霾。我们已经通过四个简单的步骤研究了如何减少客户流失&#xff0c;并与您分享这些秘密。请继续…

SP1545L肖特基二极管厂家

目前&#xff0c;市面上供应肖特基二极管的厂家、供应商特别地多&#xff0c;更多选择的背后&#xff0c;带来的却是更多的迷茫和不知所措。采购肖特基二极管&#xff0c;哪家好呢&#xff1f;提及“东沃电子DOWOSEMI”这个国产二极管品牌&#xff0c;很多客户可能第一想到他家…

部署Django报错-requires SQLite 3.8.3 or higher

记一次CentOS7部署Django项目时的报错 问题出现 在部署测试环境时&#xff0c;有需要用到一个python的后端服务&#xff0c;要部署到测试环境中去 心想这不是so easy吗&#xff0c;把本地调试时使用的python版本及Django版本在服务器上对应下载好&#xff0c;然后直接执行命…

AWS-数据库迁移工具DMS-场景:单账号跨区域迁移RDS for Mysql

参考文档&#xff1a; 分为几个环节&#xff1a; 要使用 AWS DMS 迁移至 Amazon RDS 数据库实例&#xff1a; 1.创建复制实例 有坑内存必须8g或者以上&#xff0c;我测试空库 都提示内存不足 2.创建目标和源终端节点 目标空库也得自己创建哈 3.刷新源终端节点架构 4.创建迁…

FrameWork的概述与启动过程

FrameWork框架 Framework定义了客户端组件和服务端组件功能及接口。以下阐述中&#xff0c;“应用程序”一般是指“.apk”程序。 框架中包含三个主要部分&#xff0c;分别为服务端、客户端和Linux驱动。 服务端 服务端主要包含两个重要类&#xff0c;分别是WindowManagerSe…

jsch网页版ssh

使用依赖 implementation com.jcraft:jsch:0.1.55Server端代码 import com.jcraft.jsch.Channel; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.TimeUnit; import o…

django项目改名字后顺利运行、ModelSerializer使用、模块与包的使用、反序列化校验源码分析、断言、drf之请求、魔法方法之点(.)拦截

一 django项目改名字后顺利运行 1 先改文件夹名 2 改项目名 3 改 项目内的文件夹名 4 替换掉所有文件中的 drf_day04_02 ---》drf_day05 5 命令行中启动&#xff1a;python manage.py runserver 6 setting--->django--->指定项目根路径二 同时创建作者和作者详情表(一对…

Ros noetic 机器人坐标记录运动路径和发布 实战教程(A)

前言: 网上记录Path的写入文件看了一下还挺多的,有用yaml作为载体文件,也有用csv文件的路径信息,也有用txt来记录当前生成的路径信息,载体不重要,反正都是记录的方式,本文主要按yaml的方式写入,后文中将补全其余两种方式。 其中两种方式的主要区别在于,加载yaml所需要…

ASUS华硕VivoBook15笔记本V5200EA_X515EA原装出厂Win11预装OEM系统

华硕11代酷睿笔记本电脑VivoBook_ASUSLaptop X515EA_V5200EA原厂Windows11系统 自带显卡、声卡、网卡、蓝牙等所有驱动、出厂主题壁纸、Office办公软件、华硕电脑管家MyASUS、迈克菲等预装程序 链接&#xff1a;https://pan.baidu.com/s/1yAEdA7aiuHK4CTdGLlSOKw?pwdo45a …

【MySQL】一文带你搞懂MySQL中的各种锁

1.概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资&#xff08; CPU 、 RAM、 I/O &#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致 性、有 效性是所有数据库必须解决的一个…

posexplode函数实战总结

目录 1、建表和准备数据 2、炸裂实践 3、错误炸裂方式 4、当字段类型为string&#xff0c;需要split一下 对单列array类型的字段进行炸裂时&#xff0c;可以使用lateral view explode。 对多列array类型的字段进行炸裂时&#xff0c;可以使用lateral view posexplode。 1…

命令行编译VS工程

先输入以下命令&#xff0c;因为命令出错了&#xff0c;就会弹出帮助&#xff0c;如下&#xff1a; "C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe" /help 反正就是Microsoft Visual Studio 的安装路径。 帮助界面如下&#xff1a…

新风机为什么会出现?

新风机之所以会出现&#xff0c;是因为人们对于室内空气质量的重视与需求。随着社会的进步和人们生活水平的提高&#xff0c;人们更加注重健康和舒适的居住环境&#xff0c;而室内空气质量是其中一个重要的方面。 空气污染问题&#xff1a;城市化进程加速&#xff0c;工业排放、…

vue3+ts+uniapp小程序端自定义日期选择器基于内置组件picker-view + 扩展组件 Popup 实现自定义日期选择及其他单列选择

vue3ts 基于内置组件picker-view 扩展组件 Popup 实现自定义日期选择及单列选择 vue3tsuniapp小程序端自定义日期选择器 1.先上效果图2.代码展示2.1 组件2.2 公共方法处理日期2.3 使用组件(全局自动导入的情况) 3.注意事项3.1refSelectDialog3.1 backgroundColor"#fff&q…