MyBatis 简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
简单点来说 MyBatis 做的事情就是在Java POJO 和 数据库之间 完成了 Java POJO 和 数据库语言的转换,相当于一个翻译官。
Mybatis 初始化做了哪些事情
解析主配置文件
主配置文件主要包含内容(MyBatis 官网)
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
主节点 <configuration 包含了整个mybatis的配置 对应的配置类实例也叫 Configuration
子节点顺序有严格要求就是按照括号里面的排列
解析主配置文件源码
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
当 new XMLConfigBuilder(inputStream, environment, properties); 的时候mybatis开始填充内部属性
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
1.准备初始化XMLConfigBuilder
2.初始化 XMLConfigBuilder 里面的属性对象 XPathParser
3.初始化 XMLMapperEntityResolver 这个对象可以校验mybatis 和 mapper 的xml配置文件
4.通过配置文件的inputStream 流创建一个 Document 对象
5.开始初始化 XMLConfigBuilder
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) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
调用super 实例化 Configuration 对象 初始化了一些属性
public Configuration() {
//关于事务 默认jdbc
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);
// xml语言驱动 默认 XMLLanguageDriver
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
//mybatis 日志 引入那个包就是哪一种日志策略
// <setting name="logImpl" value="STDOUT_LOGGING"/> 可打印sql 执行信息
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);
//代理 扫描mapper 配置 package 或者 class 的时候使用的代理
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
// 设置默认的 xml语言驱动
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
//注册
languageRegistry.register(RawLanguageDriver.class);
}
再执行super
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
typeAliasRegistry :别名注册器(初始化内部的别名注册器)
比如 Spring 中 beanName的别名,简化写法,一次声明,全局使用。不区分大小写
typeHandlerRegistry :类型注册器(初始化内部的类型注册器)
可以看到有两个属性 javaType 和 jdbcType
官网的解释:MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
主配置文件中的自定义别名注册器和类型注册器配置
<typeAliases>
<typeAlias type="org.apache.ibatis.test.User" alias="u"></typeAlias>
</typeAliases>
<typeHandlers>
<typeHandler handler="org.apache.ibatis.test.XxTypeHandler" javaType="java.lang.String" jdbcType="VARCHAR"></typeHandler>
</typeHandlers>
然后设置了一个属性 parsed = false
至此 XMLConfigBuilder 初始化完成。
使用已经初始化好的 XPathParser 来解析文档对象节点
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
使用boolean 值 来判断 ,如果mybatis 环境还未被初始化完成并且再次初始化就会抛出异常
在 Spring 里面则直接使用的 是 synchronized 关键字控制
解析节点,这个里面的都是configuration的节点信息,解析的顺序也是按照排列的顺序
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
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);
}
}
properties 解析配置的数据库信息,解析完成之后设置到 XPathParser 和 Configuration 的variables 属性中
properties 的用法
两种方式任选一种
<properties resource="jdbc.properties"></properties> <!-- jdbc.properties driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8 username=root password=root --> <properties> <property name="driver" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </properties>
settings 自定义的setting 信息 可见官网详情节点
一个配置完整的 settings 元素的示例如下:
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <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>
如需要打印sql执行信息
loadCustomLogImpl(settings);
<setting name="logImpl" value="STDOUT_LOGGING"/>
源码 的设置 直接给 configuration 的logImpl 属性设值
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
settingsElement(settings); 会设置剩余的settings信息
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")));
}
自定义的别名注册器,别名注册器是一个map 所以直接put进去
typeAliasesElement(root.evalNode("typeAliases"));
插件 pluginElement(root.evalNode("plugins")); 插件的底层是一个拦截器,内部维护了一个拦截器集合 也是直接add进去
插件的拦截对象 即mybatis 责任链的四大对象
解析数据库信息
environmentsElement(root.evalNode("environments"));
typeHandlerElement(root.evalNode("typeHandlers"));
类型注册器 也是一个内置的 map 直接 put 进去
解析 mapper 节点
mapperElement(root.evalNode("mappers"));
解析 mapper 节点 有两种配置 一种解析xml 一种解析接口
解析xml配置的是 resource 和 url 属性
解析接口的是 class 和 package 属性
解析mapper 先看 resource 和 url 同一种 。resource 和 url 和 class三 种是可以同时存在的,但是命名空间必须唯一。而 package 有且仅有一种。
再次实例化上面的对象重新用来解析mapper文件
源码 XNode 对象就是每一个 resource 和 url 指定的mapper 文件
private void configurationElement(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);
//使用第三方二级缓存
cacheRefElement(context.evalNode("cache-ref"));
//该 命名空间的 查询节点 开启二级缓存
cacheElement(context.evalNode("cache"));
//xxx版本后已废弃 官网已说明
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//配置Java POJO 和 数据库字段的映射节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
//自定义的sql 节点
sqlElement(context.evalNodes("/mapper/sql"));
//具体的增删改查节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
最终将每一个select|insert|update|delete 节点生成一个 MappedStatement 对象并add到Configuration 属性中保存起来。
1.如果sql语句中包含了 #{xxx} 这种类型的值会进行预编译。(没有${xxx})
2.只要sql语句中包含了一个${xxx}就不会进行预编译
基于接口的 class 和 package 属性的mapper文件
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
}
..........
else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
}
Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
解析每一个方法parseStatement(method); 最后添加到MappedStatement里面去