首页 > 其他分享 >手写mybatis之数据源的解析、创建和使用

手写mybatis之数据源的解析、创建和使用

时间:2024-10-08 16:49:04浏览次数:3  
标签:registerAlias 数据源 class dataSource SQL mybatis 手写 public

前言


在上一章节我们解析 XML 中的 SQL 配置信息,并在代理对象调用 DefaultSqlSession 中进行获取和打印操作,从整个框架结构来看我们解决了对象的代理、Mapper的映射、SQL的初步解析,那么接下来就应该是连库和执行SQL语句并返回结果了。
那么这部分内容就会涉及到解析 XML 中关于 dataSource 数据源信息配置,并简历事务管理和连接池的启动和使用。并将这部分能力在 DefaultSqlSession 执行 SQL 语句时进行调用。
核心类图如下
在这里插入图片描述

事务管理


一次数据库的操作应该具有事务管理能力,而不是通过 JDBC 获取链接后直接执行即可。还应该把控链接、提交、回滚和关闭的操作处理。所以这里我们结合 JDBC 的能力封装事务管理。
事务接口定义 Transaction
定义标准的事务接口,链接、提交、回滚、关闭,具体可以由不同的事务方式进行实现,包括:JDBC和托管事务,托管事务是交给 Spring 这样的容器来管理


    Connection getConnection() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;
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();
        }
    }
}

事务工厂

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

}

类型别名注册器
在 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);
    }

}

注册事务

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

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

}

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

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 的执行、参数传递和结果封装都是写死的,后续我们进行扩展。
测试
数据库表创建

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', '码农明哥'); 
配置数据源

```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>

通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password。
配置Mapper

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

流程验证

@Test
public void test01() 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));
}

功能验证

@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 res = sqlSession.selectOne("com.lm.mybatis.test.dao.IUserDao.queryUserInfoById", req);
    logger.info("测试结果:{}", JSON.toJSONString(res));
}

除了整体的功能流程测试外,我们还可以只对本章节新增的内容进行单元测试。由于本章节的主要操作都是在解析内容的添加,处理 XML 配置中的数据源信息,以及解析后可以在 DefaultSqlSession 中调用数据源执行 SQL 语句并返回结果。
所以我们这里单独把这部分提取出来进行测试验证,通过基于这样的测试,可以更好的在 Sequence Diagram 中生成对应的 UML 方便读者更加容易理解本章节新增的内容和流
在这里插入图片描述
总结
1:以解析 XML 配置解析为入口,添加数据源的整合和包装,引出事务工厂对 JDBC 事务的处理,并加载到环境配置中进行使用。
2:那么通过数据源的引入就可以在 DefaultSqlSession 中从 Configuration 配置引入环境信息,把对应的 SQL 语句提交给 JDBC 进行处理并简单封装结果数据。
最后:结合本章节建立起来的框架结构,数据源、事务、简单的SQL调用,下个章节将继续这部分内容的扩展处理,让整个功能模块逐渐完善。

好了到这里就结束了手写mybatis之数据源的解析、创建和使用的学习,大家一定要跟着动手操作起来。需要的源码的 可si我获取;

标签:registerAlias,数据源,class,dataSource,SQL,mybatis,手写,public
From: https://blog.csdn.net/CSDN_LiMingfly/article/details/142763492

相关文章

  • 第 1 章 MyBatis快速入门
    1.1ORM简介ORM(ObjectRelationalMapping,对象——关系映射)框架的主要功能是根据映射配置文件,完成数据在对象模型与关系模型之间的映射,同时出屏蔽了连接数据库、创建Statement对象、执行SQL、读取ResultSet、转换JavaBean、关闭ResultSet、Statement对象以及数据库......
  • Mybatis——SqlSessionFactoryBuilder工厂模式
    Mybatis——SqlSessionFactoryBuilder工厂模式工厂模式题外话合集总览:Mybatis框架梳理  说一下我的理解:设计模式是对项目工程中代码结构的设计和抽象,有了这种设计和抽象,系统才有了扩展性。记住了模式的角色、组成、UML类图,只是记住了模式的形,类似武术中的拳法套......
  • MyBatis 批量插入方案
    MyBatis批量插入MyBatis插入数据的方法有几种:for循环,每次都重新连接一次数据库,每次只插入一条数据。在编写sql时用foreach标签,建立一次数据库连接。使用MyBatis的batchInsert方法。下面是方法1和2的对比:在数据量较少的时候,比如几十条,可以使用方......
  • Mybatis-flex代替繁琐的JPA
    1.前言最近在新的SpringBoot项目中采用JPA来作为数据库的持久层。刚开始得益于Spring框架自带,IDEA也有丰富的支持;可以自行匹配数据库字段,接口中方法可以直接提示,支持JPQL,原生SQL等方式。写起来也是非常顺手。但是当业务中有一些复杂一点的需求,在JPA中实现就非常麻烦,且不直观。......
  • torch--minst手写体识别
    utils.pyimporttorchimportmatplotlib.pyplotaspltdefplot_curve(data):fig=plt.figure()plt.plot(range(len(data)),data,color='blue')plt.legend(['value'],loc='upperright')plt.xlabel('step�......
  • 【2024计算机毕业设计】基于jsp+mysql+Spring+mybatis的SSM药品进货销售仓储信息管理
    运行环境:最好是javajdk1.8,我在这个平台上运行的。其他版本理论上也可以。IDE环境:Eclipse,Myeclipse,IDEA或者SpringToolSuite都可以,如果编译器的版本太低,需要升级下编译器,不要弄太低的版本tomcat服务器环境:Tomcat7.x,8.x,9.x版本均可操作系统环境:WindowsXP/7......
  • MyBatis快速入门
    一、简介MyBatis是一个优秀的基于ORM的半自动轻量级持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注SQL本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码ORM(ObjectRelationalMap......
  • mybatis xml里的 resultMap、resultOrdered、resultSets、resultSetType、resultType
    在MyBatis中,映射结果集是一项重要的功能,用于将数据库查询结果映射到Java对象中。为了实现这一功能,MyBatis提供了多个配置选项,如resultMap、resultOrdered、resultSets、resultSetType和resultType。以下是这些配置选项的详细解释及示例:1.resultTyperesultType是最简单的结......
  • 七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用)
    七,MyBatis-Plus扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用)@目录七,MyBatis-Plus扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用)1.乐观锁2.代码生成器3.执行SQL分析打印4.总结:5.最后:1.乐观锁首先我们需要先了解开发中的一个常见场景,叫做并发请求。并......
  • 深入理解call、bind、和apply的使用以及底层代码手写
    call、bind、和apply的使用在JavaScript中,call、apply和bind是三个非常常见的方法,用来显式地指定函数的this绑定,它们都可以用来改变函数的执行上下文(即函数内部的this指向)。尽管它们的功能相似,但在使用时有一些区别。开启usestrictthis的值为undefined而不......