作者:小傅哥


博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

管你吃几碗粉,有流量就行!

现在我们每天所接收的信息量越来越多,但很多的个人却没有多少分辨知识的能力。很多知识信息也只是蹭热点的泛知识,但泛知识只是一种空泛、不成系统、甚至可能是错误的信息群,不过就是这样的信息却给内容消费者一种“成功获取了知识”吃饱的幻觉,却丧失了对知识层次的把控。

而作为一个本身就很理科的程序员来说,如果都是被泛知识充斥,花费着自己的精力和时间,没有经过足够的脑力思考所吸收的泛技术内容,长期以往是很难有所成长的。

以为我个人的成长经历来看,我更愿意花很多的实际来解决一个问题,而不是一片问题。当一个问题解决的足够透彻、清晰、明确以后,再结合着这个知识点所需要的内容继续扩展和深挖。很庆幸当年没有那么多的泛知识内容推送,否则可能我也会被弄的很焦虑!

二、目标

在上一章节我们解析 XML 中的 SQL 配置信息,并在代理对象调用 DefaultSqlSession 中进行获取和打印操作,从整个框架结构来看我们解决了对象的代理、Mapper的映射、SQL的初步解析,那么接下来就应该是连库和执行SQL语句并返回结果了。

那么这部分内容就会涉及到解析 XML 中关于 dataSource 数据源信息配置,并建立事务管理和连接池的启动和使用。并将这部分能力在 DefaultSqlSession 执行 SQL 语句时进行调用。但为了不至于在一个章节把整个工程撑大,这里我们会把重点放到解析配置、建立事务框架和引入 DRUID 连接池,以及初步完成 SQL 的执行和结果简单包装上。便于读者先熟悉整个框架结构,在后续章节再陆续迭代和完善框架细节。

三、设计

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

  • 在 parse 中解析 XML DB 链接配置信息,并完成事务工厂和连接池的注册环境到配置类的操作。
  • 与上一章节改造 selectOne 方法的处理,不再是打印 SQL 语句,而是把 SQL 语句放到 DB 连接池中进行执行,以及完成简单的结果封装。

四、实现

1. 工程结构

mybatis-step-04
└── src
├── main
│ └── java
│ └── cn.bugstack.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.bugstack.mybatis.test.dao
│ ├── dao
│ │ └── IUserDao.java
│ ├── po
│ │ └── User.java
│ └── ApiTest.java
└── resources
├── mapper
│ └──User_Mapper.xml
└── mybatis-config-datasource.xml

数据源的解析和使用核心类关系,如图 5-2 所示

  • 以事务接口 Transaction 和事务工厂 TransactionFactory 的实现,包装数据源 DruidDataSourceFactory 的功能。这里的数据源连接池我们采用的是阿里的 Druid,暂时还没有实现 Mybatis 的 JNDI 和 Pooled 连接池,这部分可以后续专门以数据源连接池的专项来开发。
  • 当所有的数据源相关功能准备好后,就是在 XMLConfigBuilder 解析 XML 配置操作中,对数据源的配置进行解析以及创建出相应的服务,存放到 Configuration 的环境配置中。
  • 最后在 DefaultSqlSession#selectOne 方法中完成 SQL 的执行和结果封装,最终就把整个 Mybatis 核心脉络串联出来了。

2. 事务管理

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

2.1 事务接口

详见源码cn.bugstack.mybatis.transaction.Transaction

public interface Transaction {

    Connection getConnection() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;

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

详见源码cn.bugstack.mybatis.transaction.jdbc.JdbcTransaction

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;
} @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();
}
} //... }
  • 在 JDBC 事务实现类中,封装了获取链接、提交事务等操作,其实使用的也就是 JDBC 本身提供的能力。

2.2 事务工厂

详见源码cn.bugstack.mybatis.transaction.TransactionFactory

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. 类型别名注册器

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

3.1 基础注册器

详见源码cn.bugstack.mybatis.type.TypeAliasRegistry

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.2 注册事务

详见源码cn.bugstack.mybatis.session.Configuration

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);
} //...
}
  • 在 Configuration 配置选项类中,添加类型别名注册机,通过构造函数添加 JDBC、DRUID 注册操作。
  • 读者应该注意到,整个 Mybatis 的操作都是使用 Configuration 配置项进行串联流程,所以所有内容都会在 Configuration 中进行链接。

4. 解析数据源配置

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

详见源码cn.bugstack.mybatis.builder.xml.XMLConfigBuilder

public class XMLConfigBuilder extends BaseBuilder {

  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());
}
}
} }
  • 以 XMLConfigBuilder#parse 解析扩展对数据源解析操作,在 environmentsElement 方法中包括事务管理器解析和从类型注册器中读取到事务工程的实现类,同理数据源也是从类型注册器中获取。
  • 最后把事务管理器和数据源的处理,通过环境构建 Environment.Builder 存放到 Configuration 配置项中,也就可以通过 Configuration 存在的地方都可以获取到数据源了。

5. SQL执行和结果封装

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

详见源码cn.bugstack.mybatis.session.defaults.DefaultSqlSession

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
} @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;
}
} // ... }
  • 在 selectOne 方法中获取 Connection 数据源链接,并简单的执行 SQL 语句,并对执行的结果进行封装处理。
  • 因为目前这部分主要是为了大家串联出整个功能结构,所以关于 SQL 的执行、参数传递和结果封装都是写死的,后续我们进行扩展。

六、测试

1. 事先准备

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', '小傅哥');

2. 配置数据源

<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>
  • 通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password
  • 另外这里要注意下,DataSource 配置的是 DRUID,因为我们实现的是这个数据源的处理方式。

3. 配置Mapper

<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.bugstack.mybatis.test.po.User">
SELECT id, userId, userName, userHead
FROM user
where id = #{id}
</select>
  • Mapper 的配置内容在上一章节的解析学习中已经做了配置,本章节做了简单的调整。

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));
}
  • 单元测试没有什么改变,仍是通过 SqlSessionFactory 中获取 SqlSession 并获得映射对象和执行方法调用。

测试结果

22:34:18.676 [main] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
22:34:19.286 [main] INFO cn.bugstack.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"} Process finished with exit code 0
  • 从现在的测试结果已经可以看出,通过我们对数据源的解析、包装和使用,已经可以对 SQL 语句进行执行和包装返回的结果信息了。
  • 读者在学习的过程中可以调试下代码,看看每一步都是如何完成执行步骤的,也在这个过程中进行学习 Mybatis 框架的设计技巧。

七、总结

  • 以解析 XML 配置解析为入口,添加数据源的整合和包装,引出事务工厂对 JDBC 事务的处理,并加载到环境配置中进行使用。
  • 那么通过数据源的引入就可以在 DefaultSqlSession 中从 Configuration 配置引入环境信息,把对应的 SQL 语句提交给 JDBC 进行处理并简单封装结果数据。
  • 结合本章节建立起来的框架结构,数据源、事务、简单的SQL调用,下个章节将继续这部分内容的扩展处理,让整个功能模块逐渐完善。

《手写Mybatis》第5章:数据源的解析、创建和使用的更多相关文章

  1. 要想精通Mybatis?从手写Mybatis框架开始吧!

    1.Mybatis组成 动态SQL Config配置 Mapper配置 2.核心源码分析 Configuration源码解析 SqlSessionFactory源码解析 SqlSession源码解析 ...

  2. 手写MyBatis ORM框架实践

    一.实现手写Mybatis三个难点 1.接口既然不能被实例化?那么我们是怎么实现能够调用的? 2.参数如何和sql绑定 3.返回结果 下面是Mybatis接口 二.Demo实现 1.创建Maven工程 ...

  3. 手写mybatis框架笔记

    MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...

  4. 手写MyBatis流程

    MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...

  5. 手写mybatis框架-增加缓存&事务功能

    前言 在学习mybatis源码之余,自己完成了一个简单的ORM框架.已完成基本SQL的执行和对象关系映射.本周在此基础上,又加入了缓存和事务功能.所有代码都没有copy,如果也对此感兴趣,请赏个Sta ...

  6. 带码农《手写Mybatis》进度3:实现映射器的注册和使用

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获!

  7. 《手写Mybatis》第4章:Mapper XML的解析和注册使用

    作者:小傅哥 系列:https://bugstack.cn/md/spring/develop-mybatis/2022-03-20-%E7%AC%AC1%E7%AB%A0%EF%BC%9A%E5%B ...

  8. 手写mybatis框架

    前言 很久没有更新mybatis的源码解析了,因为最近在将自己所理解的mybatis思想转为实践. 在学习mybatis的源码过程中,根据mybatis的思想自己构建了一个ORM框架 .整个代码都是自 ...

  9. Mybatis执行流程学习之手写mybatis雏形

    Mybatis是目前开发中最常用的一款基于ORM思想的半自动持久层框架,平时我们都仅仅停留在使用阶段,对mybatis是怎样运行的并不清楚,今天抽空找到一些资料自学了一波,自己写了一个mybatis的 ...

随机推荐

  1. 还在担心CC攻击? 让我们来了解它, 并尽可能将其拒之服务之外.

    还在担心CC攻击? 让我们来了解它, 并尽可能将其拒之服务之外. CC攻击是什么? 基本原理 CC原名为ChallengeCollapsar, 这种攻击通常是攻击者通过大量的代理机或者肉鸡给目标服务器 ...

  2. IDEA导入第三方jar包

    IDEA导入第三方jar包 在Module下新建一个Directory,命名为lib或者libs,然后直接将目标jar包文件复制到这个新建的Directory中. 右键选中导入的jar包,选择Add ...

  3. Mac安装和配置Maven 及其第二次启动报错问题解决

      1.下载安装 下载地址: https://maven.apache.org/download.cgi 下载后解压下来重名名为ApacheMaven,并放入到/usr/local/下 2.配置环境变 ...

  4. 什么是Java序列化,如何实现Java序列化?或者请解释Serializable接口的作用?

    象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象,对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上,通过网络将这种二进制流传输到另 ...

  5. Java并发机制(7)--线程池ThreadPoolExecutor的使用

    Java并发编程:线程池的使用整理自:博客园-海子-http://www.cnblogs.com/dolphin0520/p/3932921.html 1.什么是线程池,为什么要使用线程池: 1.1. ...

  6. 启动Tomcat,Idea控制台输出乱码 淇℃伅

      解决:修改 tomcat 下的conf目录下 logging.properties这个文件ava.util.logging.ConsoleHandler.encoding修改为 为 GBK 就好了 ...

  7. elasticsearch 的倒排索引是什么 ?

    面试官:想了解你对基础概念的认知. 解答:通俗解释一下就可以. 传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置. 而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表 即 ...

  8. flash的TotalFrames显示undefined

    通过js来操作flash的时候,获取到总帧数的是属性.TotalFrames,而不是属性TotalFrames().在asp.net中,js放在最后可以在一定程度上避免当前flash没有加载完,导致获 ...

  9. 转Gerber和拼版

    1 Gerber 这个网上有现成的教程:(不要写网上能找到的资料-敏捷开发) AD 导出Gerber :https://jingyan.baidu.com/article/3c48dd3494181c ...

  10. 【SpringBoot实战】核心配置和注解

    前言 SpringBoot核心配置在springboot中有非常重要的作用,我们可是使用核心配置文件进行一些基础功能的定义,属性值的注入等.springboot支持两种格式的核心配置文件,一种是pro ...