文档章节

MyBatis源码跟踪 - 初始化

_Code_Monkey_
 _Code_Monkey_
发布于 2017/05/09 18:35
字数 1910
阅读 28
收藏 0

Mybatis的初始化过程,其实就是加载配置的过程; mybatis的配置方式有两种:xml和java api config,本文以java api为例,跟踪mybatis的初始化过程;


先来一段使用代码:

    // 数据源
    public static DataSource initDataSource() {
        PooledDataSource dataSource = new PooledDataSource();
        dataSource.setDriver("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://xxx.xxx.xxx.xxx:3306/mybatis?characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    // 构建数据库事务方式
    public static TransactionFactory initTransactionFactory() {
        return new JdbcTransactionFactory();
    }

    // 数据库运行环境
    public static Environment initEnvironment() {
        DataSource dataSource = initDataSource();
        TransactionFactory transactionFactory = initTransactionFactory();
        return new Environment.Builder("development").dataSource(dataSource).transactionFactory(transactionFactory).build();
    }

    public static Configuration initConfiguration() {
        // 配置对象
        Configuration configuration = new Configuration(initEnvironment());

        // 注册别名
        //configuration.getTypeAliasRegistry().registerAlias("map", Map.class);
        configuration.getTypeAliasRegistry().registerAliases("pub.tbc.stu.mybatis.domain");

        // 映射器
        configuration.addMappers("pub.tbc.stu.mybatis.mappers");

        // 添加插件
        configuration.addInterceptor(new TimerInterceptor());

        return configuration;
    }

    // 使用Configuration构建SessionFactory
    public static SqlSessionFactory initAndReturnSqlSessionFactory() {
        Configuration configuration = initConfiguration();
    //  return new SqlSessionFactoryBuilder().build(configuration);
        return new DefaultSqlSessionFactory(configuration);
    }

    @Test
    public void test() {
        // 获取SqlSession,线程不安全
        SqlSession sqlSession = initAndReturnSqlSessionFactory().openSession();
        // 通过SqlSession获取Mapper
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 执行
        userMapper.addUser(User.builder().name("zhangsan").age(18).build());
        List<User> users = userMapper.users();
    }

可以看出,在正式运行我们的逻辑代码(test()方法)之前,这段代码做了几件事情(附对应的XML配置):

  • 定义数据源 => <dataSource type="POOLED"><property name="driver"......</dataSource>
  • 定义TransactionFactory事务工厂 => <transactionManager type="JDBC"/>
  • 以数据源和事务工厂构造Environment => <environment>...</environment>
  • 创建Configuration,传入Environment对象;
  • 注册别名 => <typeAliases><package name="pub.tbc.stu.mybatis.domain" /></typeAliases>
  • 指定映射器扫描目录 => <mappers> <package name="pub.tbc.stu.mybatis.mappers" /> </mappers>
  • 添加插件 => <plugins><plugin interceptor=pub.tbc.stu.mybatis.extend.plugin.TimerInterceptor"></plugin></plugins>
  • 创建SqlSessionFactory接口的默认实现DefaultSqlSessionFactory,传入Configuration对象; 至此,配置已完成,接下来,就可以在方法中获取SqlSessionFactory工厂并获取SqlSession使用了;

注:

  • 实际中一般SqlSessionFactory需要保持全局单例;
  • 实际项目中一般会使用第三方的数据库连接池构建数据源,并使用第三方事务管理器(spring大法好)
  • 附XML方式进行配置时,这样创建SqlSessionFactory:
    // 使用XML配置的方式,创建SqlSessionFactory
    public static SqlSessionFactory xmlInit() throws IOException {
        String resource = "mybatisConfiguration.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        return new SqlSessionFactoryBuilder().build(inputStream);
    }

///////////////////////////////////////////// 分割线 //////////////////////////////////////////////////


下面将结合源码,分析以上代码创建 SqlSessionFactory 的过程中,mybatis做了什么 (其实以上纯java api的方式配置mybatis,已经基本可以看出mybatis的初始化过程了);

我们先来看数据源DataSource和事务管理器TransactionFactory, 这两个都是接口,基于面向接口编程原则,可以很方便的基本成别的实现,DataSource来源于javax.sql包,属于java jdbc的一部分; mybatis中默认的实现是PooledDataSource和JdbcTransactionFactory(实际中通常使用第三方的实现),这两个都是使用new实例然后设置属性的普通方法来创建,并没有什么牛逼的设计和模式,不必多说;

再来看看Environment,由于上面的示例是使用构建器方式创建的,先看一下Environment.Builder的build()方法:

    public Environment build() {
      return new Environment(this.id, this.transactionFactory, this.dataSource);
    }

OK,仍然是new,那么继续看Environment构造方法:

   public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
    if (id == null) {
      throw new IllegalArgumentException("Parameter 'id' must not be null");
    }
    if (transactionFactory == null) {
        throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
    }
    this.id = id;
    if (dataSource == null) {
      throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
    }
    this.transactionFactory = transactionFactory;
    this.dataSource = dataSource;
  }

只是把参数设置为属性,备用而已,什么时候用且不管,继续看大而全的Configuration;

上面示例中使用new Configuration(initEnvironment());创建 Configuration,看构造方法:

public Configuration(Environment environment) {
    this();
    this.environment = environment;
  }

  public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }

可以看出,Configuration内部维护了一个Environment属性,构造时赋值,并且调用无参构造器,在无参构造器中,使用typeAliasRegistry注册了一系列别名,typeAliasRegistry是Configuration内部维护的别名注册器:TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

然后,我们可以使用Configuration对象进行一些配置,如:

  • 注册别名,实际是调用内部维护的别名注册器进行注册,可以注单个类的别名映射,也可以指定包名,由系统自己扫描包下的全部类;
  • 添加映射器,跟别名一样,通过调用内部的映射器注册器mapperRegistry来进行注册,同样可以指定包名的方式由系统自己扫描 ;
  • 添加插件,通过configuration.addInterceptor(new TimerInterceptor());来添加插件,看一下该方法源码:
// from class org.apache.ibatis.session.Configuration
public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

可以看到,实际是调用内部InterceptorChain对象的addInterceptor方法,该方法内部是把拦截器(插件)添加到内部的列表中:

// from class  org.apache.ibatis.plugin.InterceptorChain
public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  • 除以上示例外,我们还可以通过Configuration对象进行其它配置,所以在xml中支持的配置,都可以通过Configuration进行编程;

有必要首重补充下,mybatis中所有的配置都是通过加载到Configuration对象中进行保存以备使用的,如setting标签中的所以配置项,都可以通过configuration.set...sertter方法进行配置,Configuration对象内部维护了大量属性,mybatis初始化加载配置的过程,就是填充这些属性(部分调用属性的对应方法,如别名、映射器等):

  protected Environment environment;

  protected boolean safeRowBoundsEnabled = false;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase = false;
  protected boolean aggressiveLazyLoading = true;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys = false;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls = false;
  protected boolean useActualParamName = true;

  protected String logPrefix;
  protected Class <? extends Log> logImpl;
  protected Class <? extends VFS> vfsImpl;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  protected boolean lazyLoadingEnabled = false;
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  protected String databaseId;

XML方式进行配置,初始化方式也差不多,用XMLConfigBuilder构建Configuration,看代码:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
    // 这一句,创建XMLConfigBuilder 对象,调用parse方法生成Configuration
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

那么,关键应该就在于parse方法了:

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

具体解析不看,猜也猜的到,挨个儿读取配置中的节点,然后设置到configuration对象中,跟通过代码的方式配置没有什么不同;


很简单,初始化就做了这些工作,然后new一个默认的Session工厂,传入Configuration对象,即可用来获取SqlSession了;

注: SqlSession是线程不安全的,而xxxMapper的代理对象又来自于SqlSession,也是线程不安全的,因此要在方法中使用; 但是,实现项目中,通常是使用注入的方式,将mapper对象注入到自己的应用中,纳尼?怎么个情况呢?事实就是注入的SqlSession或者Mapper是定制的,比如spring-mybatis,相当于扩展了 MyBatis,在其中单独提供了线程安全的mapper注入到应用,可以放心使用,跟通常情况下自己单独使用mybatis是不一样的;

后面补充所有属性的意义,未完待续...

© 著作权归作者所有

_Code_Monkey_
粉丝 1
博文 20
码字总数 23642
作品 0
昌平
程序员
私信 提问
Mybatis使用存储过程以及原理

一直在用Mybatis,身处互联网公司,对于存储过程这块一直没怎么使用,数据库设计基本上都是单表+冗余+服务划分来设计,关键部分使用缓存,因此对存储过程一直不怎么感冒。今天偶然看到一篇M...

郁极风
2016/08/11
74
1
Mybatis Foreach Map

在开发使用mybatis foreach根据Map集合动态生产sql时出现参数值为空问题 源代码: 结果: 由上运行结果可得map第二个值为空,实际非空为何无法解析map值,跟踪源码最后发现DynamicSqlSource ...

思悟修
2016/08/12
55
1
早前学习Java记录

Spring 对 iBATIS 的支持】 Spring 通过 DAO 模式,提供了对 iBATIS 的良好支持。 SqlMapClient:是 iBATIS 中的主要接口,通过 xml 配置文件可以让 Spring 容器来管理 SqlMapClient 对象的创...

大风厂蔡成功
2016/07/10
43
0
Activiti 5.18 的Mybatis版本问题

试用了Activiti近期推出的5.18版本,按照User Guide初始化了工程,为简单起见使用了嵌入式的H2数据库。 增加了单元测试: @Testpublic void testDeployAndRun() {repositoryService.createD...

丽天
2015/09/08
1K
2
mybatis的动态代理

正常的动态代理是一个接口和他的一个实现类进行代理,但mybatis里面只定义接口就可以为我们代理出一个对象,我想知道他是通过什么来创建这个代理对象的,又是怎么创建出来的 我是新手 求助大...

popl
2014/05/16
618
0

没有更多内容

加载失败,请刷新页面

加载更多

Android双向绑定原理简述

Android双向绑定原理简述 双向绑定涉及两个部分,即将业务状态的变化传递给UI,以及将用户输入信息传递给业务模型。 首先我们来看业务状态是如何传递给UI的。开启dataBinding后,编译器为布局...

tommwq
今天
4
0
Spring系列教程八: Spring实现事务的两种方式

一、 Spring事务概念: 事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。...

我叫小糖主
今天
8
0
CentOS 的基本使用

1. 使用 sudo 命令, 可以以 root 身份执行命令, 必须要在 /etc/sudoers 中定义普通用户 2. 设置 阿里云 yum 镜像, 参考 https://opsx.alibaba.com/mirror # 备份mv /etc/yum.repos.d/CentO...

北漂的我
昨天
4
0
Proxmox VE技巧 移除PVE “没有有效订阅” 的弹窗提示

登陆的时候提示没有有效的订阅You do not have a valid subscription for this server. Please visit www.proxmox.com to get a list of available options. 用的是免费版的,所以每次都提示......

以谁为师
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部