public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
//方式一:
UserMapper mapper = session.getMapper(UserMapper.class);
User user1 = mapper.selectBlog("1");
//方式二:
User user2 = session.selectOne(
"test.UserMapper.selectBlog", 1);
}
}
1、构建sqlSessionFactory
读取并解析MyBatis的配置文件mybatis-config.xml,并将解析到的各节点数据保存至Configuration 对象。
通过Configuration对象构建sqlSessionFactory
//读取配置文件
InputStream inputStream =Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream)
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
/**
* 1.1 读取mybatis-config.xml文件文件,xml文件到 Document 对象的转换
*/
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
/**
* 1.2 方法parser.parse(),解析Document,并将数据保存到Configuration,并返回Configuration
*
* 1.3 通过build(configuration)构建SqlSessionFactory
*/
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.
}
}
}
1.1 xml文件到 Document 对象的转换
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
/**
* 1.1.1 创建XMLConfigBuilder中的配置信息保存类configuration
*/
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
1.2 方法parser.parse(),解析Document,并将数据保存到Configuration,并返回Configuration
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 从根节点 <configuration>开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
/**
* 对照mybatis-configuration.xml,分别解析相应的节点标签
*/
// 1.2.1 解析properties节点
propertiesElement(root.evalNode("properties"));
//1.2.2 解析setting节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 1.2.3 解析typeAliases 别名
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//1.2.2.1 将setting节点保存到configuration
settingsElement(settings);
//1.2.4 environments节点下包含对数据源解析,事务配置解析
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//1.2.5 解析typeHandlers 类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
// 1.2.6 解析Mapper 映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
1.2.1 解析properties节点
<!-- resource外部文件属性会覆盖子节点property信息 -->
<properties resource="jdbc.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
<!-- url外部文件属性会覆盖子节点property信息 -->
<properties url="jdbc.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
/**
* 1.2.1 解析properties节点
* @param context
* @throws Exception
*/
private void propertiesElement2(XNode context) throws Exception {
if (context != null) {
//Properties是继承Hashtable的。先加载property子节点name value属性。放入Hashtable
Properties defaults = context.getChildrenAsProperties();
//读取properties节点中的属性resource和url
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
//url和resource都存在,则抛出异常
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) {
//resource属性引入的外部文件的name value会覆盖Properties的节点的信息
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
//url属性引入的外部文件的name value会覆盖Properties的节点的信息
defaults.putAll(Resources.getUrlAsProperties(url));
}
//读取Configuration对象中variables属性信息,如果有,则将其添加到properties对象中
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
//将Properties中数据设置到XPathParser的variables
parser.setVariables(defaults);
//将Properties中数据设置到configuration的variables
configuration.setVariables(defaults);
}
}
1.2.2 解析setting节点
<settings>
<!--开启缓存-->
<setting name="cacheEnabled" value="true"/>
<!--延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--单一语句返回多结果集-->
<setting name="multipleResultSetsEnabled" value="true"/>
<!--列标签代替列名-->
<setting name="useColumnLabel" value="true"/>
<!--不允许 JDBC 支持自动生成主键-->
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<!--配置默认的执行器。
SIMPLE 就是普通的执行器;
REUSE 执行器会重用预处理语句(prepared statements);
BATCH 执行器将重用语句并执行批量更新-->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!--超时时间,它决定驱动等待数据库响应的秒数。-->
<setting name="defaultStatementTimeout" value="25"/>
<!--为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。-->
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
/**
* 1.2.2 解析setting节点
* @param context
* @return
*/
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
/**
* 解析setting节点的子节点属性:name value并保存到Properties(Hashtable)
*/
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
/**
* 1.2.1.1 将setting节点保存到configuration
* @param props
*/
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
1.2.3 解析typeAliases 别名
别名存在的意义仅在于用来减少类完全限定名的冗余
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
<!--这两个标签可以共存。但是<typeAliases />标签一定要在 <package />标签的前面。
因为一个类可以有多个别名,所以这时候两个标签设置的名称都有效。-->
<package name="domain.blog"/>
</typeAliases>
/**
* 1.2.3 解析typeAliases 别名
* @param parent
*/
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//这两个标签可以共存。但是<typeAliases />
// 标签一定要在 <package />标签的前面。
// 因为一个类可以有多个别名,所以这时候两个标签设置的名称都有效。
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
MyBatis提供了常见的 Java 类型内建的相应的类型别名。它们都是不区分大小写的。如_byte,byte _long,long
1.2.4 数据源和事务的解析放到下一节
1.2.5 解析typeHandlers 类型处理器(完成Java数据类型和数据库数据类型之间的转换)
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandle" javaType="VARCHAR" jdbcType="String"></typeHandler>
</typeHandlers>
public class ExampleTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
1.2.6 解析Mapper 映射器
<!-- 4种方式配置xml映射文件 -->
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
/**
* 解析package节点
*/
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
/**
* resource,url,class三个标签有且仅有一个有值,其余两个都为null,才能正常执行。
*/
//通过通过resource 找到xxDao.xml映射文件
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());
//解析xxDao.xml映射文件
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//通过通过url 找到xxDao.xml映射文件
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
//解析xxDao.xml映射文件
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.");
}
}
}
}
}
通过resource,url 找到xxDao.xml映射文件,继续解析其节点,并解析的结果存入configuration对象
/**
* 解析xxxDao.xml文件
* @param context
*/
private void configurationElement2(XNode context) {
try {
//读取命名空间
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref标签
cacheRefElement(context.evalNode("cache-ref"));
//解析cache标签
cacheElement(context.evalNode("cache"));
//解析/mapper/parameterMap标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析/mapper/resultMap标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析/mapper/sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//解析select|insert|update|delete标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
1.3 通过build(configuration)构建SqlSessionFactory
前面将mybatis-config.xml文件中的节点解析,并将解析后的数据保存至configuration,返回configuration
/**
* 1.3 通过build(configuration)构建SqlSessionFactory
* @param config
* @return
*/
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
小结:
读取并解析MyBatis的配置文件mybatis-config.xml,并将解析到的各节点数据保存至Configuration 对象。
通过Configuration对象构建sqlSessionFactory