mybatis的认识(二)

上篇: mybatis的认识(一)
先前说了一下mybatis初始化过程都干了些什么,初始化的时候都加载什么。今天就再深入的看一下mybatis初始化加载过程中Resource加载和sqlsessionFactory到底build的是什么。

目录

  • Resource调用getResourceAsStream(resource)加载配置文件
  • 一系列的创建
    如,SqlSessionFactoryBuilder调用build加载流创建 sqlsessionFactory,sqlsessionFactory调用openSession创建sqlsession,sqlsession调用insert,update等方法
  • 对执行结果进行二次封装
    如,SqlSession调用insert时内部Executor与数据库操作执行sql语句等
  • 提交与事务
    sqlSession.commit();
Resource调用

准备工作:列出实体类,配置文件等

实体类:

/**
 * 用户实体
 */
public class User{

    private long userId;// 用户ID

    private String name;// 名称

    private int age;// 年龄

        getter and setter 方法略过
}

配置文件(mybatis-config.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="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://xxx.xxx:3306/ssm" />
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
        <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

当然,还有很多可以在XML 文件中进行配置,只不过现在主要讲的是加载问题所以上面指出的是最关键的部分。environment 元素体中包含了事务管理和连接池的配置。mappers 则是包含一组 mapper 映射器 。

UserMapper.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="Book">
    <!-- 目sql语句-->
    <insert id="insert" >
        insert into user (name,age) values (#{name},#{age})
    </insert>
</mapper>

接下来就是初始化加载的过程了

public class Main {
    public static void main(String[] args) throws IOException {
        // 创建一个user对象
        User user= new User ();
        user.setName("禾木人生");
        user.setAge(2);
        // 加载配置文件 并构建SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        // 从SqlSessionFactory对象中获取 SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 执行操作
        sqlSession.insert("insert", user);
        // 提交操作
        sqlSession.commit();
        // 关闭SqlSession
        sqlSession.close();
    }
}

这段代码其实是常见的手动加载,也可以从官方文档里面找。里面有个东西要注意的是Resources.getResourceAsStream(resource);加载的这个resource不要有局限,这个只要有路径都可以去加载的,也可以是file:// 的 URL 形式的文件路径来配置。

Resources.getResourceAsStream(resource);源码分析

看getResourceAsStream方法: 由于整篇代码过多所以只展示重要部分

public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream((ClassLoader)null, resource);
}

//其中getResourceAsStream如下: 
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in == null) {
        throw new IOException("Could not find resource " + resource);
    } else {
        return in;
    }
}

接下了的是通多获取到自身的ClassLoader对象,然后交给ClassLoader(lang包下的)来加载,这点有点不太理解所以先看一下。不过可以看到。它返回了一个InputStream对象。:

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    ClassLoader[] arr$ = classLoader;
    int len$ = classLoader.length;

    for(int i$ = 0; i$ < len$; ++i$) {
        ClassLoader cl = arr$[i$];
        if (null != cl) {
            InputStream returnValue = cl.getResourceAsStream(resource);
            if (null == returnValue) {
                returnValue = cl.getResourceAsStream("/" + resource);
            }

            if (null != returnValue) {
                return returnValue;
            }
        }
    }
new SqlSessionFactoryBuilder().build(inputStream);

这个底层其实很好理解因为:

public SqlSessionFactoryBuilder() {
}

所以new SqlSessionFactoryBuilder()只是创建一个对象实例,而没有对象返回(建造者模式),对象的返回交给build()方法。这里要传入一个inputStream对象,就是将我们上一步获取到的InputStream对象传入。通过Document对象来解析,然后返回InputStream对象,然后交给XMLConfigBuilder构造成org.apache.ibatis.session.Configuration对象,然后交给build()方法构造程SqlSessionFactory:

public SqlSessionFactory build(InputStream inputStream) {
    return this.build((InputStream)inputStream, (String)null, (Properties)null);
}

同理看一下build方法的实现过程
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
        // 进行XML配置文件的解析
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            inputStream.close();
        } catch (IOException var13) {
            ;
        }

    }

    return var5;
}
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
}
创建SqlSession

SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。通过调用自身的openSessionFromDataSource方法:
注意:

  • getDefaultExecutorType()默认是SIMPLE。
  • 注意TX等级是 Null, autoCommit是false。

其实整体的构建过程可以是这样的:
构建步骤:
Environment>>TransactionFactory+autoCommit+tx-level>>Transaction+ExecType>>Executor+Configuration+autoCommit>>SqlSession

其中,Environment是Configuration中的属性。

看代码:

public SqlSession openSession() {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}

而openSessionFromDataSource实现方法是:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        Environment environment = this.configuration.getEnvironment();
        // 根据Configuration的Environment属性来创建事务工厂
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        // 从事务工厂中创建事务,默认等级为null,autoCommit=false
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 创建执行器
        Executor executor = this.configuration.newExecutor(tx, execType);
        // 根据执行器创建返回对象 SqlSession
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }
    return var8;
}

调用Executor执行数据库操作&&生成具体SQL指令

在拿到SqlSession对象后,调用它的insert方法。在这里理解一下的是,其实insert的操作相当于另类的update,我是这么理解的,源代码里面也是这么实现的,它调用了自身的update(statement, parameter)方法:

public int insert(String statement, Object parameter) {
    return this.update(statement, parameter);
}

底层update的实现过程
public int update(String statement, Object parameter) {
    int var4;
    try {
        this.dirty = true;
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        // wrapCollection(parameter)判断 param对象是否是集合
        var4 = this.executor.update(ms, this.wrapCollection(parameter));
    } catch (Exception var8) {
        throw ExceptionFactory.wrapException("Error updating database.  Cause: " + var8, var8);
    } finally {
        ErrorContext.instance().reset();
    }

    return var4;
}

mappedStatements就是我们平时说的sql映射对象.

源码如下:
protected final Map<String, MappedStatement> mappedStatements;

可见它是一个Map集合,在我们加载xml配置的时候,mapping.xml的namespace和id信息就会存放为mappedStatements的key,对应的,sql语句就是对应的value.

然后调用BaseExecutor中的update方法:

public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        this.clearLocalCache();
        // 真正做执行操作的方法
        return this.doUpdate(ms, parameter);
    }
}

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;

    int var6;
    try {
        Configuration configuration = ms.getConfiguration();
        // 创建StatementHandler对象,从而创建Statement对象
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
        // 将sql语句和参数绑定并生成SQL指令
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        var6 = handler.update(stmt);
    } finally {
        this.closeStatement(stmt);
    }

    return var6;
}

先来看看prepareStatement方法,看看mybatis是如何将sql拼接合成的:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Connection connection = this.getConnection(statementLog);
    // 准备Statement
    Statement stmt = handler.prepare(connection);
    // 设置SQL查询中的参数值
    handler.parameterize(stmt);
    return stmt;
}
这里parameterize方法:
public void parameterize(Statement statement) throws SQLException {
    this.parameterHandler.setParameters((PreparedStatement)statement);
}

这里把statement转换程PreparedStatement对象,它比Statement更快更安全。
这都是我们在JDBC中熟用的对象,就不做介绍了,所以也能看出来Mybatis是对JDBC的封装。

从ParameterMapping中读取参数值和类型,然后设置到SQL语句中:

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
    if (parameterMappings != null) {
        for(int i = 0; i < parameterMappings.size(); ++i) {
            ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                String propertyName = parameterMapping.getProperty();
                Object value;
                if (this.boundSql.hasAdditionalParameter(propertyName)) {
                    value = this.boundSql.getAdditionalParameter(propertyName);
                } else if (this.parameterObject == null) {
                    value = null;
                } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                    value = this.parameterObject;
                } else {
                    MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                    value = metaObject.getValue(propertyName);
                }

                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = this.configuration.getJdbcTypeForNull();
                }

                try {
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch (TypeException var10) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                } catch (SQLException var11) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var11, var11);
                }
            }
        }
    }

}

针对于上述的实现思路,以思路的处理方式学习为主,不要担心代码怎么写,关键是思路,可以用简单代码实现。

对查询结果二次封装

在doUpdate方法中,解析生成完新的SQL后,然后执行var6 = handler.update(stmt);我们来看看它的源码。因为我们是插入操作,返回的是一个int类型的值,所以这里mybatis给我们直接返回int。

如果是query操作,返回的是一个ResultSet,mybatis将查询结果包装程ResultSetWrapper类型,然后一步步对应java类型赋值等…有兴趣的可以自己去看看。

public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
     // 执行sql
    ps.execute();
    // 获取返回值
    int rows = ps.getUpdateCount();
    Object parameterObject = this.boundSql.getParameterObject();
    KeyGenerator keyGenerator = this.mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(this.executor, this.mappedStatement, ps, parameterObject);
    return rows;
}
提交与事务

最后,来看看commit()方法的源码。他是调用其对象本身的commit()方法,其实在经过上面的代码的熏陶之后,下面的代码理解起来也不会那么困难了:

public void commit() {
    this.commit(false);
}

调用本身已实现的commit方法
public void commit(boolean force) {
    try {
        // 是否提交(判断是提交还是回滚)
        this.executor.commit(this.isCommitOrRollbackRequired(force));
        this.dirty = false;
    } catch (Exception var6) {
        throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + var6, var6);
    } finally {
        ErrorContext.instance().reset();
    }
}


如果dirty是false,则进行回滚;如果是true,则正常提交。

private boolean isCommitOrRollbackRequired(boolean force) {
    return !this.autoCommit && this.dirty || force;
}

调用CachingExecutor的commit方法:

public void commit(boolean required) throws SQLException {
    this.delegate.commit(required);
    this.tcm.commit();
}

调用BaseExecutor的commit方法:

public void commit(boolean required) throws SQLException {
    if (this.closed) {
        throw new ExecutorException("Cannot commit, transaction is already closed");
    } else {
        this.clearLocalCache();
        this.flushStatements();
        if (required) {
            this.transaction.commit();
        }

    }
}

最后调用JDBCTransaction的commit方法:

public void commit() throws SQLException {
    if (this.connection != null && !this.connection.getAutoCommit()) {
        if (log.isDebugEnabled()) {
            log.debug("Committing JDBC Connection [" + this.connection + "]");
        }
        // 提交连接
        this.connection.commit();
    }
}

经过上过程,大概的知道了mybatis初始化内部的加载机制 。其实自己是中间有些部分是不太理解的,所以以后有时间要再重新过一遍思路,最重要的是思路,其实是实现方式。最后,就是如果想真正的理解这些实现的思路方式,最好要在自己理解的前提下,用简单的例子,和代码实现。比如可以举例子,找一个简单的事务,用简单的代码去实现,这样不仅方便自己理解,也方便给看到这篇博客的人理解,在mybatis三的博客会用简单代码和载体来实现这段思想过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值