Mybatis-体系结构

610 阅读3分钟

背景

我们在开发过程中,常常不会直接通过mysql-client 直接去和数据库交互,
而会采用一些第三方的类orm工具包去简化操作流程,mybatis
是一款敏捷度非常高的数据访问层工具。

结构

version:3.5.3

image.png 部分其他功能就没有画出了,比如反射,事务,插件,语言等。

暴露接口

很多时候我们都不会脱离spring去使用mybatis,但是今天我们重点在分析流程上面,所以单独去运行它,要单独运行mybatis需要配置信息mapper接口mapper xml映射 例子如下。

用例

public static void main(String[] args) throws IOException {
    //获取配置资源
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    //构建sqlsessionfactory
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
    //获取会话session 之后调用数据库
    SqlSession sqlSession = build.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<UserInfo> list = mapper.list();
    System.out.println(JSON.toJSONString(list));
}

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://127.0.0.1:3306/db_user?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

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="com.poizon.study.sb.mybatis.UserMapper">
    <select id="list" resultType="com.poizon.study.sb.mybatis.UserInfo">
        SELECT * from user_info;
    </select>
</mapper>

UserMapper.java , UserInfo.java

public interface UserMapper {
    @Select("SELECT * from user_info;")
    List<UserInfo> list();
}
@Data
public class UserInfo {
    private Long id;
    private String username;
    private Date createAt;
}

案例分析

案例中我们涉及的主要对象有SqlSessionFactorySqlSession,主要方法有sqlSessionFactoryBuilder.build()build.openSession()sqlSession.getMapper(),在分析之前我们根据之前分析源码的经验,在框架启动过程中都会进行配置文件加载,没有build()启到的作用便是配置加载,下面给的是一张配置映射图(包含mybatis-config,与mapper.xml)。 image.png 对应关系还是很清晰的,configuration 在其中充当了配置中心的角色,和项目中容器的功能,下图是执行流程。

image.png

应用层通过SqlSessionFactory 获取到SqlSession会话,会话通过Executor执行器去执行具体的调用,调用层和返回层通过StatementHandler 进行参数和返回的包装。

配置文件解析

mybatis 解析配置文件采用的是DOM解析,(一般有4种解析方式dom,sax,dom4j,jdom),如果文件大很多专业框架都采用dom4j,因为dom解析会加载整个文档,如果文档太大会导致内存溢出问题,我们从入口SqlSessionFactoryBuilder#build() 进去。

public SqlSessionFactory build( inputStream,  environment,  properties) {
  XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  return build(parser.parse());//继续点进去
}
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
  //...
  typeAliasesElement(root.evalNode("typeAliases"));
  mapperElement(root.evalNode("mappers"));
}

这里解析的子标签很多,我们找2个具有代表性的分析下,typeAliases,语义是指类型别名,我们在配置文件中可以这样配置<typeAliases><typeAlias type="com.poizon.study.sb.mybatis.UserInfo" alias="myUserType" /></typeAliases>,这样在解析文件的时候便会走到此解析流程中,代表的含义是,类型UserInfo 可以简写成 myUserType,我们经常用在MapperresultType, parameterType 中运用比较多,需要时还可以以包的维度去配置 <typeAliases><package name=""/></typeAliases>

#org.apache.ibatis.builder.xml.XMLConfigBuilder#typeAliasesElement
private void typeAliasesElement(XNode parent) {
  for (XNode child : parent.getChildren()) {
    if ("package".equals(child.getName())) {
      //...
    } else {
      String alias = child.getStringAttribute("alias");
      String type = child.getStringAttribute("type");
      Class<?> clazz = Resources.classForName(type);
      typeAliasRegistry.registerAlias(alias, clazz);
    }
  }
}
#org.apache.ibatis.type.TypeAliasRegistry#registerAlias()
public void registerAlias(String alias, Class<?> value) {
    String key = alias.toLowerCase(Locale.ENGLISH);
    typeAliases.put(key, value);
}

最后将别名存储到TypeAliasRegistry 的map中,大家可以点看这个类看看,里面内置了很多基础数据类型的别名,比如string=>String.classdate=>Date.class,等到要查询时候通过resolveAlias(string) 返回对应类型,可以通过getTypeAliases() 返回所有的别名列表,这里要注意的是返回的map是不可写的。

public Map<String, Class<?>> getTypeAliases() {
   return Collections.unmodifiableMap(typeAliases);
}