关于java:手写Mybatis第5章数据源的解析创建和使用

29次阅读

共计 10872 个字符,预计需要花费 28 分钟才能阅读完成。

作者:小傅哥
<br/> 博客: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

源码地址:https://t.zsxq.com/bmqNFQ7

数据源的解析和应用外围类关系,如图 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 调用,下个章节将持续这部分内容的扩大解决,让整个功能模块逐步欠缺。

正文完
 0