前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一分钟进入mybatis的世界

一分钟进入mybatis的世界

作者头像
一个架构师
发布2022-06-20 20:13:25
1720
发布2022-06-20 20:13:25
举报
文章被收录于专栏:从码农的全世界路过

mybatis是一款优秀的持久层框架, 通过配置mybatis-config.xml和mapper.xml就可以轻松完成ORM工作. 在当前流行JavaCodeConfig的情况下, 这些配置项又是如何应用配置的呢? 它的执行过程又是怎样的呢? 带着这两个问题, 一起看下mybatis的真面目.

一. mybatis的配置与使用

1.1

字符集

首先我们看下这个全局配置文件, 它是mybatis的核心配置,包括数据源, setting, 自定义类型转化, 以及mapper文件等等.

这个配置文件命名是不限定的, 一般会被命名为mybatis-config.xml

代码语言:javascript
复制
<configuration>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
     <typeAliases>
        <package name="com.coderworld968.model"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="UNPOOLED">
                <property name="driver" value="org.h2.Driver"/>
                <property name="url" value="jdbc:h2:mem:voice;INIT=RUNSCRIPT FROM 'classpath:/h2/schema-h2.sql'"/>
                <property name="username" value="sa"/>
                <property name="password" value="sa"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/CountryMapper.xml"/>
    </mappers>
</configuration>

全局配置有了, 再看下mapper.xml中的SQL.

代码语言:javascript
复制
<mapper namespace="com.coderworld968.mapper">
 <select id="selectAll" resultType="Country">
  select id,countryname,countrycode from country
 </select>
</mapper>

mybatis的本质是简化SQL处理, 那有了配置文件, 再看如何使用? 使用步骤非常简单, 创建sqlSessionFactory和根据mapper.xml中的sql ID执行对应SQL.

(1) 读取全局配置文件,创建sqlSessionFactory

代码语言:javascript
复制
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

(2) 执行SQL

sqlSessionFactory会创建SqlSession对象, 并调用mapper.xml中的SQL ID执行对应SQL.

代码语言:javascript
复制
SqlSession sqlSession = sqlSessionFactory.openSession();
List<Country> countryList = sqlSession.selectList("selectAll");

二. mybatis的配置解析

在上述创建sqlSessionFactory时, 跟进builder()方法就会发现逻辑很简单,就是解析mybatis-config.xml配置文件, 并最终映射成一个Configuration对象.

代码语言:javascript
复制
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
// ...
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
// ...
}

那我们完全可以抛开XML配置文件, 直接创建Configuration对象.

代码语言:javascript
复制
public static void init(){
try {
    UnpooledDataSource dataSource = new UnpooledDataSource(
            "org.h2.Driver",
            "jdbc:h2:mem:voice;INIT=RUNSCRIPT FROM 'classpath:/h2/schema-h2.sql'",
            "sa",
            "sa");
    TransactionFactory transactionFactory = new JdbcTransactionFactory();
    Environment environment = new Environment("Java", transactionFactory, dataSource);

    Configuration configuration = new Configuration(environment);
    configuration.getTypeAliasRegistry().registerAliases("com.coderworld968.model");
    configuration.setLogImpl(Log4jImpl.class);

    InputStream inputStream = Resources.getResourceAsStream("mapper/CountryMapper.xml");
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, "mapper/CountryMapper.xml", configuration.getSqlFragments());
                mapperParser.parse();

    sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
} catch (Exception ignore) {
    ignore.printStackTrace();
}
}

Mapper的注册方式除了示例中解析xml配置文件外,还有包扫描, 指定类等方式, 非常利于mybatis的扩展使用.

代码语言:javascript
复制
public void addMappers(String packageName, Class<?> superType)
public void addMappers(String packageName)
public <T> void addMapper(Class<T> type)

通过这种无配置文件, 纯代码配置的方式也就能明白spring或者spring boot是如何封装mybatis的了.

与示例代码中有TypeAliasRegistry处理类型别名(TypeAlias)类似的,也会有MapperRegistry,TypeHandlerRegistry,LanguageDriverRegistry,InterceptorChain对相应的功能做处理.

稍稍扩展下mybatis中对常见数据类型的别名(TypeAlias)处理方式是在类的构造方法中完成的. 类似的, TypeHandler处理也是在构造方法中处理的.

代码语言:javascript
复制
public TypeAliasRegistry() {
  registerAlias("string", String.class);
  registerAlias("byte", Byte.class);
  registerAlias("long", Long.class);
  // ...
}

三. mybatis的SQL执行

3.1

MappedStatement对象

在解析配置文件(或JavaCodeConfig)中的mapper部分时, 不仅会记录SQL本身, 还会结合Configuration配置项生成对应的MappedStatement对象. 创建的MappedStatement对象会存储到Configuration.mappedStatements集合中.

代码语言:javascript
复制
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")

mappedStatement在存储时, KEY会根据mapper配置分为两种:namespace+SQLID 和 SQLID. 这也是示例中执行查询时, 没有指定namespace也可以找到对应sql的原因了.

代码语言:javascript
复制
sqlSession.selectList("selectAll")

3.2

SqlSession

SqlSession是从sqlSessionFactory中创建的,主要用来处理SQL执行, 事务等功能. SqlSession中主要包含四个对象:

(1)Executor: 调度执行StatementHandler、ParameterHandler、ResultHandler执行相应的SQL语句;

(2)StatementHandler: 使用数据库中Statement(PrepareStatement)执行操作,即底层是封装好了的prepareStatement;

(3)ParameterHandler: 处理SQL参数;

(4)ResultHandler: 结果集ResultSet封装处理返回;

3.3

SQL执行

下面结合源码一起看下sqlSession.selectList("selectAll")的执行流程

(1) Configuration创建SqlSession, 其中包含封装了拦截器(interceptorChain)的Executor. 拦截器的实现原理可以点这里. 如果是开启了缓存, 还会再封装成CachingExecutor.

代码语言:javascript
复制
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// ...
  executor = new SimpleExecutor(this, transaction);
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

(2) 从Configuration中获得MappedStatement对象,并运行执行器(CachingExecutor)执行query()操作

代码语言:javascript
复制
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
// ...
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
// ...
}

(3) 解析SQL和参数, 继续调用query()方法

代码语言:javascript
复制
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

(4) 经过多级调用, 会执行SimpleExecutor.doQuery(), 生成StatementHandler对象, 并封装java.sql.Statement对象

代码语言:javascript
复制
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// ...
  Configuration configuration = ms.getConfiguration();
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  stmt = prepareStatement(handler, ms.getStatementLog());
  return handler.query(stmt, resultHandler);
// ...
}

(5) StatementHandler对象是通过Configuration生成的RoutingStatementHandler类型, 内部根据MappedStatement.getStatementType()会选择具体不同的handler. 本例中用到的是PreparedStatementHandler.

代码语言:javascript
复制
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  switch (ms.getStatementType()) {
    case STATEMENT:
      delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case PREPARED:
      delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case CALLABLE:
      delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    default:
      throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
  }
}

(6) PreparedStatementHandler执行query()操作, 并根据resultSetHandler封装SQL执行结果集.

代码语言:javascript
复制
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.handleResultSets(ps);
}

以上就是mybatis的针对一个SQL的主要处理流程.

总结

通过上述流程,大家对mybatis也有了一定的了解, 由于篇幅原因, 文中并没有提到mapper接口相关的动态代理处理, 后续会完善动态代理的详细介绍及使用.

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 从码农的全世界路过 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档