创建项目
为了看清楚MyBatis的整个初始化过程,先创建一个简单的Java项目,目录结构如下图所示:
以下代码均有代码生成器生成
Student实体类
package com.softtool.mybatis.dao;
import java.io.Serializable;
public class Student implements Serializable {
private Integer id;
private String name;
private Integer age;
private String sex;
private static final long serialVersionUID = 1L;
public Student(Integer id, String name, Integer age, String sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
}
public Student() {
super();
}
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
StudentMapper 类
package com.softtool.mybatis.dao;
public interface StudentMapper {
int deleteByPrimaryKey(Integer id);
int insert(Student record);
int insertSelective(Student record);
Student selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Student record);
int updateByPrimaryKey(Student record);
}
配置文件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.softtool.mybatis.dao.StudentMapper">
<resultMap id="BaseResultMap" type="com.softtool.mybatis.dao.Student">
<constructor>
<idArg column="id" javaType="java.lang.Integer" jdbcType="INTEGER" />
<arg column="name" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="age" javaType="java.lang.Integer" jdbcType="INTEGER" />
<arg column="sex" javaType="java.lang.String" jdbcType="VARCHAR" />
</constructor>
</resultMap>
<sql id="Base_Column_List">
id, name, age, sex
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from student
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from student
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="com.softtool.mybatis.dao.Student">
insert into student (id, name, age,
sex)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER},
#{sex,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="com.softtool.mybatis.dao.Student">
insert into student
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="name != null">
name,
</if>
<if test="age != null">
age,
</if>
<if test="sex != null">
sex,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
</if>
<if test="name != null">
#{name,jdbcType=VARCHAR},
</if>
<if test="age != null">
#{age,jdbcType=INTEGER},
</if>
<if test="sex != null">
#{sex,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.softtool.mybatis.dao.Student">
update student
<set>
<if test="name != null">
name = #{name,jdbcType=VARCHAR},
</if>
<if test="age != null">
age = #{age,jdbcType=INTEGER},
</if>
<if test="sex != null">
sex = #{sex,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.softtool.mybatis.dao.Student">
update student
set name = #{name,jdbcType=VARCHAR},
age = #{age,jdbcType=INTEGER},
sex = #{sex,jdbcType=VARCHAR}
where id = #{id,jdbcType=INTEGER}
</update>
</mapper>
mybatis.xml MyBatis的配置文件
<?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://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/softtool/mybatis/dao/StudentMapper.xml"/>
</mappers>
</configuration>
pom.xml
项目是maven项目,配置的pom如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test002</groupId>
<artifactId>test002</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
Main 主函数
package com.softtool.mybatis;
import com.softtool.mybatis.dao.Student;
import com.softtool.mybatis.dao.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.reflection.Reflector;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Constructor;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args ) throws IOException
{
Reader resourceAsReader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsReader);
SqlSession session = sqlSessionFactory.openSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
Student student = mapper.selectByPrimaryKey(1);
}
}
创建的sql
-- ----------------------------
-- Table structure for `student`
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`name` varchar(255) NOT NULL COMMENT '姓名',
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`sex` varchar(255) DEFAULT NULL COMMENT '性别',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=latin1;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('ss', '1', '1', 'mm');
INSERT INTO `student` VALUES ('ddd', '2', '2', 'women');
INSERT INTO `student` VALUES ('name1', '3', '2', 'nan');
INSERT INTO `student` VALUES ('222', '4', '31', 'man');
运行结果
Student [id=1, name=ss, age=1, sex=mm]
MyBatis初始化过程
获取配置文件
当系统初始化时,首先会读取配置文件,
Reader resourceAsReader = Resources.getResourceAsReader("mybatis.xml");
创建SqlSessionFactoryBuilder对象
从SqlSessionFactoryBuilder的名字中可以看出,SqlSessionFactoryBuilder是用来创建SqlSessionFactory对象的。 来看一下SqlSessionFactoryBuilder源码:,SqlSessionFactoryBuilder中只有一些重载的build函数,这些build函数的入参都是MyBatis配置文件的输入流,返回值都是SqlSessionFactory;由此可见,SqlSessionFactoryBuilder的作用很纯粹,就是用来通过配置文件创建SqlSessionFactory对象的。
SqlSessionFactory 创建过程
下面具体来看一下,build函数是如何创建SqlSessionFactory对象的。
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
构造XMLConfigBuilder对象
build函数首先会构造一个XMLConfigBuilder对象,从名字上大致可以猜到,该对象是用来解析XML配置文件的。下面来看一下XMLConfigBuilder的体系结构。
-
XMLxxxBuilder是用来解析XML配置文件的,不同类型XMLxxxBuilder用来解析MyBatis配置文件的不同部位。比如:XMLConfigBuilder用来解析MyBatis的配置文件,XMLMapperBuilder用来解析MyBatis中的映射文件(如上文提到的ProductMapper.xml),XMLStatementBuilder用来解析映射文件中的SQL语句。
-
这些XMLxxxBuilder都有一个共同的父类——BaseBuilder。这个父类维护了一个全局的Configuration对象,MyBatis的配置文件解析后就以Configuration对象的形式存储。
-
当创建XMLConfigBuilder对象时,就会初始化Configuration对象,并且在初始化Configuration对象的时候,一些别名会被注册到Configuration的typeAliasRegistry容器中。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
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);
}
解析配置文件
当有了XMLConfigBuilder对象之后,接下来就可以用它来解析配置文件了
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
从上述代码中可以看到,XMLConfigBuilder会依次解析配置文件中的<properties>、< settings >、< environments>、< typeAliases >、< plugins >、< mappers >等属性。下面介绍下几个重要属性的解析过程。
节点的解析过程
节点:
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value=""/>
- 节点的解析过程:
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);
}
}
- 首先读取<resources>节点下的所有<resource>节点,并将每个节点的name和value属性存入Properties中。
- 然后读取<resources>节点上的resource、url属性,并获取指定配置文件中的name和value,也存入Properties中。(PS:由此可知,如果resource节点上定义的属性和properties文件中的属性重名,那么properties文件中的属性值会覆盖resource节点上定义的属性值。)
- 最终,携带所有属性的Properties对象会被存储在Configuration对象中。
settings节点的解析过程
settings 节点的解析过程: <settings>属性的解析过程和 <properties>属性的解析过程极为类似,这里不再赘述。最终,所有的setting属性都被存储在Configuration对象中。
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
</settings>
mappers节点的解析过程
<mappers>节点的定义方式有如下四种: 方式1:
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
方式2:
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
方式3:
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
方式4:
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<mappers>节点的解析过程如下:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
MyBatis会遍历<mappers>下所有的子节点,如果当前遍历到的节点是<package>,则MyBatis会将该包下的所有Mapper Class注册到configuration的mapperRegistry容器中。 如果当前节点为<mapper>,则会依次获取resource、url、class属性,解析映射文件,并将映射文件对应的Mapper Class注册到configuration的mapperRegistry容器中。
其中,<mapper>节点的解析过程如下:
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse()
在解析前,首先需要创建XMLMapperBuilder,创建过程如下:
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
// 将configuration赋给BaseBuilder
super(configuration);
// 创建MapperBuilderAssistant对象(该对象为MapperBuilder的协助者)
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
首先会初始化父类BaseBuilder,并将configuration赋给BaseBuilder; 然后创建MapperBuilderAssistant对象,该对象为XMLMapperBuilder的协助者,用来协助XMLMapperBuilder完成一些解析映射文件的动作。
当有了XMLMapperBuilder后,便可进入解析<mapper>的过程
public void parse() {
// 若当前的Mapper.xml尚未被解析,则开始解析
// PS:若<mappers>节点下有相同的<mapper>节点,那么就无需再次解析了
if (!configuration.isResourceLoaded(resource)) {
// 解析<mapper>节点
configurationElement(parser.evalNode("/mapper"));
// 将该Mapper.xml添加至configuration的LoadedResource容器中,下回无需再解析
configuration.addLoadedResource(resource);
// 将该Mapper.xml对应的Mapper Class注册进configuration的mapperRegistry容器中
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// 获取<mapper>节点上的namespace属性,该属性必须存在,表示当前映射文件对应的Mapper Class是谁
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 将namespace属性值赋给builderAssistant
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析<cache>节点
cacheElement(context.evalNode("cache"));
// 解析<parameterMap>节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析<resultMap>节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析<sql>节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析sql语句
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
resultMapElements函数
该函数用于解析映射文件中所有的<resultMap>节点,这些节点会被解析成ResultMap对象,存储在Configuration对象的resultMaps容器中。
<resultMap>节点定义如下:
<resultMap id="BaseResultMap" type="com.softtool.mybatis.dao.Student">
<constructor>
<idArg column="id" javaType="java.lang.Integer" jdbcType="INTEGER" />
<arg column="name" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="age" javaType="java.lang.Integer" jdbcType="INTEGER" />
<arg column="sex" javaType="java.lang.String" jdbcType="VARCHAR" />
</constructor>
</resultMap>
<resultMap>节点的解析过程:
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
ResultMapResolver这个类很纯粹,有且仅有一个函数resolve,用于构造ResultMap对象,并将其存入Configuration对象的resultMaps容器中;而这个过程是借助于MapperBuilderAssistant.addResultMap完成的。
sqlElement函数 该函数用于解析映射文件中所有的<sql>节点,并将这些节点存储在当前映射文件所对应的XMLMapperBuilder对象的sqlFragments容器中,供解析sql语句时使用。- buildStatementFromContext函数 该函数会将映射文件中的sql语句解析成MappedStatement对象,并存在configuration的mappedStatements。
来源:oschina
链接:https://my.oschina.net/u/4006362/blog/2252784