MyBatis源码跟踪 - 初始化

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是不一样的;

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

转载于:https://my.oschina.net/u/2407208/blog/896018

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值