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

文章目录

  • 一、目标:数据源的解析、创建和使用
  • 二、设计:数据源的解析、创建和使用
  • 三、实现:数据源的解析、创建和使用
    • 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;}@Overridepublic Connection getConnection() throws SQLException {connection = dataSource.getConnection();connection.setTransactionIsolation(level.getLevel());connection.setAutoCommit(autoCommit);return connection;}@Overridepublic void commit() throws SQLException {if (connection != null && !connection.getAutoCommit()) {connection.commit();}}@Overridepublic void rollback() throws SQLException {if (connection != null && !connection.getAutoCommit()) {connection.rollback();}}@Overridepublic 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 {@Overridepublic Transaction newTransaction(Connection conn) {return new JdbcTransaction(conn);}@Overridepublic 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;@Overridepublic void setProperties(Properties props) {this.props = props;}@Overridepublic 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.调用父类初始化Configurationsuper(new Configuration());// 2.dom4j 处理xmlSAXReader 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");// SELECTList<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();// 添加解析SQLconfiguration.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;}@Overridepublic <T> T selectOne(String statement) {return (T) ("你被代理了!" + statement);}@Overridepublic <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;}@Overridepublic <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}@Overridepublic Configuration getConfiguration() {return configuration;}
}
  • 修改 selectOne,引入配置环境和数据源,将之前打印改为调用 JDBC 连接数据查询 SQL
  • 添加 resultSet2Obj 返回结果处理方法
  • selectOne 方法中获取 Connection 数据源连接,并简单的执行 SQL 语句,并对执行的结果进行封装处理。

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

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

CREATE TABLEUSER(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, userNameFROM userWHERE id = #{id}</select>
</mapper>
  • 去除 createTime 查询字段, 添加 userName 查询字段。

4.4 单元测试

ApiTest

/*** 测试映射器注册机*/
@Test
public void test_SqlSessionFactory() throws IOException {// 1.从SqlSessionFactory中获取SqlSessionSqlSessionFactory 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 {// 解析XMLReader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader);Configuration configuration = xmlConfigBuilder.parse();// 获取 DefaultSqlSessionSqlSession 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.mzph.cn/news/64787.shtml

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

相关文章

配置环境变量的作用

配置环境变量的作用 一般运行过程&#xff1a;寻找QQ.exe所在的目录&#xff0c;输入QQ.exe配置环境变量&#xff1a;把QQ所在的路径配给操作系统Path&#xff0c; 在任何路径下都能运行QQ.exe 举例&#xff1a; 定义变量&#xff1a;SCALA_HOME SCALA_HOME、JAVA_HOME 等这…

【论文精读】Learning Transferable Visual Models From Natural Language Supervision

Learning Transferable Visual Models From Natural Language Supervision 前言Abstract1. Introduction and Motivating Work2. Approach2.1. Creating a Sufficiently Large Dataset2.2. Selecting an Efficient Pre-Training Method2.3. Choosing and Scaling a Model2.4. P…

基于STM32的ADC采样及各式滤波实现(HAL库,含VOFA+教程)

前言&#xff1a;本文为手把手教学ADC采样及各式滤波算法的教程&#xff0c;本教程的MCU采用STM32F103ZET6。以HAL库的ADC采样函数为基础进行教学&#xff0c;通过各式常见滤波的实验结果进行分析对比&#xff0c;搭配VOFA工具直观的展示滤波效果。ADC与滤波算法都是嵌入式较为…

【FreeRTOS】信号量的相关函数使用及示例解释

FreeRTOS中的信号量是一种用于任务间同步的机制。它可以用来实现任务之间的互斥访问共享资源或者等待某个事件发生。 文章目录 信号量类型1. 二进制信号量&#xff08;Binary Semaphore&#xff09;&#xff1a;2. 计数信号量&#xff08;Counting Semaphore&#xff09;&…

python in excel 如何尝鲜 有手就行

众所周知&#xff0c;微软在8月下旬放出消息python已入驻excel&#xff0c;可到底怎么实现呢。 今天我就将发布python in excel的保姆级教程&#xff0c;开始吧&#xff01; 获取office 365 账号 首先我们要有微软office365 这时候需要再万能的某宝去找一个账号&#xff0c;…

Android JNI系列详解之ndk-build工具的使用

一、Android项目中使用ndk-build工具编译库文件 之前介绍过CMake编译工具的使用&#xff0c;今天介绍一种ndk自带的编译工具ndk-build的使用。 ndk-build目前主要有两种配置使用方式&#xff1a; 如上图所示&#xff0c;第一种方式是Android.mkApplication.mkgradle的方式生成…

5.Redis-string

string 字符串 字符串类型是 Redis 最基础的数据类型&#xff0c;关于字符串需要特别注意&#xff1a; 1.⾸先Redis中所有 key 的类型都是字符串类型&#xff0c;⽽且其他⼏种数据结构也都是在字符串类似基础上构建的&#xff0c;例如 list 和 set 的元素类型是字符串类型。 2…

Web_单一视频文件mp4转换为m3u分段ts文件实现边下边播

一、下载ffmpeg: Builds - CODEX FFMPEG @ gyan.dev 二、转换视频文件: 先解压缩,会看到如下结构: 进入bin目录,把需要转换的视频文件复制过来,同时新建一个文件夹用来存放转换后的文件,然后按住Shift键同时单击鼠标右键,选择打开Powershell窗口: 输入以下命令(根据…

【管理运筹学】第 7 章 | 图与网络分析(1,图论背景以及基本概念、术语)

文章目录 引言一、图与网络的基本知识1.1 图与网络的基本概念1.1.1 图的定义1.1.2 图中相关术语1.1.3 一些特殊图类1.1.4 图的运算 写在最后 引言 按照正常进度应该学习动态规划了&#xff0c;但我想换换口味&#xff0c;而且动态规划听说也有一定难度&#xff0c;还不一定会考…

UE5- c++ websocket客户端写法

# 实现目标 ue5 c 实现socket客户端&#xff0c;读取服务端数据&#xff0c;并进行解析 #实现步骤 {projectName}.Build.cs里增加 "WebSockets","JsonUtilities", "Json"配置信息&#xff0c;最终输出如下&#xff1a; using UnrealBuildTool;…

基于科大讯飞AIGC创作平台,构建数字人虚拟主播

笔者为体验目前数字人虚拟主播创作视频的质量&#xff0c;特意制作了一段测试视频。 基于讯飞智作创建 总体感受&#xff0c;数字人虚拟主播具有成本低、可定制性强等优点&#xff0c;但是也存在缺乏人情味、技术限制和法律问题等缺点。因此&#xff0c;在使用数字人虚拟主播时…

opencv 提取选中区域内指定hsv颜色的水印

基于《QT 插件化图像算法研究平台》做的功能插件。提取选中区域内指定hsv颜色的水印。 《QT 插件化图像算法研究平台》有个HSV COLOR PICK功能&#xff0c;可以很直观、方便地分析出水印 的hsv颜色&#xff0c;比如, 蓝色&#xff1a;100,180,0,255,100,255。 然后利用 opencv …

QtConcurrent和QFuture的使用

在Qt中&#xff0c;有时候我们会遇到这样一种情况&#xff0c;需要执行一个很长时间的操作&#xff0c;这时候我们的主界面就会卡住。我们的通常做法就是把这个很长时间的操作扔到线程里去处理&#xff0c;可以使用标准库中的线程也可以使用QThread。 如果我们要在这个很长时间…

C# 跨线程访问窗体控件

在不加任何修饰的情况下&#xff0c;C# 默认不允许跨线程访问控件&#xff0c;实际在项目开发过程中&#xff0c;经常使用跨线程操作控件属性&#xff0c;需要设置相关属性才能正确使用&#xff0c;两种方法设置如下&#xff1a; 方法1&#xff1a;告诉编译器取消跨线程访问检…

ModaHub魔搭社区——未来向量数据库会不像传统数据库那样,在国内涌现 200 多家出来?

I. 引言:数据库市场的持续扩张与向量数据库的崛起 随着技术的迭代速度越来越快,技术门槛也在逐渐降低,数据库市场的持续扩张是不可避免的。当前存在着大量的需求,这将吸引越来越多的数据库甚至向量数据库加入竞争。然而,从业界角度看,这种市场扩张是有利的。它可以促使更…

实现公网远程访问:Windows本地快速搭建SFTP文件服务器并配置端口映射

文章目录 1. 搭建SFTP服务器1.1 下载 freesshd服务器软件1.3 启动SFTP服务1.4 添加用户1.5 保存所有配置 2 安装SFTP客户端FileZilla测试2.1 配置一个本地SFTP站点2.2 内网连接测试成功 3 使用cpolar内网穿透3.1 创建SFTP隧道3.2 查看在线隧道列表 4. 使用SFTP客户端&#xff0…

Redis之主从复制解读

目录 基本概述 作用 如何配置主从复制 命令配置&#xff08;Slaveof &#xff09; 配置文件配置 主从复制缺点 主从复制原理 主从复制常见问题解答 命令补充&#xff08;info replication&#xff09; 基本概述 主从复制,是指将一台Redis服务器的数据,复制到其他的R…

Python3 条件控制

Python3 条件控制 Python 条件语句是通过一条或多条语句的执行结果&#xff08;True 或者 False&#xff09;来决定执行的代码块。 可以通过下图来简单了解条件语句的执行过程: 代码执行过程&#xff1a; if 语句 Python中if语句的一般形式如下所示&#xff1a; if conditi…

python后端,一个账户,多设备登录管理

一个账号&#xff0c;多台设备同时登陆的问题&#xff0c;设计以及实现 参考这篇文章&#xff1a; https://www.alibabacloud.com/help/zh/tair/use-cases/manage-multi-device-logon-from-a-single-user-by-using-tairhash1.0 设计思路 利用的是Redis&#xff0c;主设备的保…

【Java 动态数据统计图】动态数据统计思路案例(动态,排序,动态数组(重点推荐))七(129)

需求&#xff1a;前端根据后端的返回数据&#xff1a;画统计图&#xff1b; 说明&#xff1a; 1.X轴为地域&#xff0c;Y轴为地域出现的次数&#xff1b; 2. 动态展示&#xff08;有地域展示&#xff0c;没有不展示&#xff0c;且高低排序&#xff09; Demo案例&#xff1a; …