天天看點

初識mybatis源碼(1)——配置檔案的裝載與解析

複習了mybatis相關的一些用法,從這篇開始就是開始學習mybatis的源碼,以前也是曾經看過一些mybatis的源碼,但是隻是粗粗的看過一點兒,而且沒有什麼耐心看下去,全都是浮于表面,現在想想真的是悔不當初。

這一次不會了,在寫部落格之前,也是認真的看了一部分mybatis的源碼,現在就是以部落格的方式,記錄下我的學習過程,部落格中有錯誤的地方,希望指出,共同學習。

首先,建立mybatis_sources的maven項目,添加下面的依賴:

<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.6</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.46</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
		<!-- 寫mybatis插件使用的依賴,不用可以不添加 -->
		<dependency>
			<groupId>org.mybatis.generator</groupId>
			<artifactId>mybatis-generator-core</artifactId>
			<version>1.3.5</version>
		</dependency>
	</dependencies>
           

添加mybatis的配置檔案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>
	<properties resource="dbconfig.properties"></properties>
	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true" />
		<setting name="jdbcTypeForNull" value="NULL" />
		<!--顯示的指定每個我們需要更改的配置的值,即使他是預設的。防止版本更新帶來的問題 -->
		<setting name="lazyLoadingEnabled" value="true" />
		<setting name="aggressiveLazyLoading" value="false" />
	</settings>
	<typeAliases>
		<package name="com.yukio.mybatis.bean" />
	</typeAliases>
	<environments default="dev_mysql">
		<environment id="dev_mysql">
			<transactionManager type="JDBC"></transactionManager>
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	<databaseIdProvider type="DB_VENDOR">
		<!-- 為不同的資料庫廠商起别名 -->
		<property name="MySQL" value="mysql" />
	</databaseIdProvider>
	<mappers>
		<!-- 批量注冊: -->
		<package name="com.yukio.mybatis.dao" />
	</mappers>
</configuration>
           

 添加資料庫配置檔案dbconfig.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis_sources?allowMultiQueries=true
jdbc.username=root
jdbc.password=123456
           

添加對應的實體bean,Student.java:

import java.io.Serializable;

public class Student implements Serializable {

	private static final long serialVersionUID = 1L;

	private Integer id;
	private String name;
	private Integer age;
	private String gender;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public Student(Integer id, String name, Integer age, String gender) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
		this.gender = gender;
	}

	public Student() {
		super();
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + ", age=" + age + ", gender=" + gender + "]";
	}

}
           

添加對應的接口StudentMapper.java:

import com.yukio.mybatis.bean.Student;

public interface StudentMapper {

	Student getStudentById(Integer id);

}
           

對應的StudentMapper.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.yukio.mybatis.dao.StudentMapper">

	<resultMap type="com.yukio.mybatis.bean.Student"
		id="studentMap">
		<id column="id" property="id" />
		<result column="name" property="name" />
		<result column="age" property="age" />
		<result column="gender" property="gender" />
	</resultMap>

	<select id="getStudentById" resultMap="studentMap">
		select id,name,age,gender
		from tb_student where id=#{id}
	</select>

</mapper>
           

  所有的前置工作完成之後,咱們接下來看看對應的測試代碼,StudentMapperTest.java:

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import com.yukio.mybatis.bean.Student;
import com.yukio.mybatis.dao.StudentMapper;

public class StudentMapperTest {

	public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		return new SqlSessionFactoryBuilder().build(inputStream);
	}

	@Test
	public void testGetStudentById() throws IOException {

		SqlSessionFactory sessionFactory = getSqlSessionFactory();

		SqlSession sqlSession = sessionFactory.openSession();

		StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

		Student student = mapper.getStudentById(1);

		System.out.println(student);

	}

}
           

通過下面的這段代碼,是将mybatis的配置檔案,轉化成輸入流,裡面的具體實作,咱們就是不深入去看了,隻要記得這個配置檔案得到的輸入流,咱們在下面使用就好了。

InputStream inputStream = Resources.getResourceAsStream(resource);
           

往下看,咱們直接進入SqlSessionFactoryBuilder.build(InputStream inputStream)方法:

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
           

可以看到,在這個方法中,又調用了同類中的另外一個方法SqlSessionFactoryBuilder.build(InputStream inputStream, String  environment, Properties properties),代碼如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      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.
      }
    }
  }
           

從下面的這個代碼中,咱們能夠看到,其作用主要是從輸入流中,擷取到一個XMLConfigBuilder.java,具體的代碼實作,可以進去看看:

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
           

接着,咱們通過XMLConfigBuilder.parse()進入到以下代碼中,可以看到一個擷取對應的mybatis配置的操作:

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
           

通過parseConfiguration(XNode root)進入到XMLConfigBuilder.parseConfiguration(XNode root)方法中,可以看到,這個方法的主要作用就是擷取一個org.apache.ibatis.session.Configuration.java:

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      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);
    }
  }
           

mybatis的官方文檔中,就是曾經指明過mybatis中的properties參數的裝載方式,是以這裡咱們重點看一下,對應的代碼如下:

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }
           

通過這段代碼,我們能夠得到,在 properties 元素體内指定的屬性首先被讀取,然後根據 properties 元素中的 resource 屬性讀取類路徑下屬性檔案或根據 url 屬性指定的路徑讀取屬性檔案,并覆寫已讀取的同名屬性,以及讀取作為方法參數傳遞的屬性,并覆寫已讀取的同名屬性。

好了,現在我們已經擷取到了對應的org.apache.ibatis.session.Configuration.java類,接下來,咱們需要繼續傳回到SqlSessionFactoryBuilder.build(InputStream, String, Properties).java中,代碼如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      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.
      }
    }
  }
           

好了,繼續往下看,通過build(parser.parse()),我們進入到了SqlSessionFactoryBuilder.build(Configuration)方法中,能夠看到我們擷取到的是一個org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.java類:

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

好了,到這裡mybatis的解析過程,就算是結束了,下一篇我們開始看一下,mybatis對于參數的解析。

繼續閱讀