1.初始化入口java
//Mybatis 经过SqlSessionFactory获取SqlSession, 而后才能经过SqlSession与数据库进行交互 private static SqlSessionFactory getSessionFactory() { SqlSessionFactory sessionFactory = null; String resource = "configuration.xml"; try { sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource)); } catch (IOException e) { e.printStackTrace(); } return sessionFactory; }
那么,咱们就先从SqlSessionFactoryBuilder入手, 我们先看看源码是怎么实现的数据库
SqlSessionFactoryBuildersession
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 读取配置文件 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //解析配置获得Configuration对象,建立DefaultSqlSessionFactory对象 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. } } }
XMLConfigBuildermybatis
XMLConfigBuilder是BaseBuilder的众多子类之一,核心字段以下app
//表示是否已经解析过了 private boolean parsed; //用于解析配置文件的对象 private final XPathParser parser; //配置文件中表示<environment>的名称 默认读取default属性 private String environment; // 负责和建立Reflector对象 private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
XMLConfigBuilder.parse()方法是解析mybatis-config.xml配置文件的如。它调用parseConfiguration()方法实现整个解析过程。具体实现以下:ide
/** * 解析配置文件的入口 * @return */ public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } /** * 对配置文件每一个节点具体的解析过程 * configuration节点为根节点。 * 在configuration节点之下,咱们能够配置11 个子节点, * 分别为:properties、settings、typeAliases、plugins、objectFactory、objectWrapperFactory、reflectorFactory、 * environments、databaseIdProvider、typeHandlers、mappers。 * @param root 根节点 */ private void parseConfiguration(XNode root) { try { // 解析properties节点 propertiesElement(root.evalNode("properties")); //解析settings节点 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings);//设置vfsImpl字段 //解析typeAliases节点 typeAliasesElement(root.evalNode("typeAliases")); //解析plugins节点 pluginElement(root.evalNode("plugins")); //解析objectFactory节点 objectFactoryElement(root.evalNode("objectFactory")); //解析objectWrapperFactory节点 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //解析reflectorFactory节点 reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); //解析environments节点 environmentsElement(root.evalNode("environments")); //解析databaseIdProvider节点 databaseIdProviderElement(root.evalNode("databaseIdProvider")); //解析typeHandlers节点 typeHandlerElement(root.evalNode("typeHandlers")); //解析mappers节点 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
propertiesElement()方法会解析配置文件中的properties节点并造成Java.util.Properties对象,以后将改对象设置到XpathParse和Configguration的variables字段中,占位符就是用Properties中的信息替换的,具体实现以下:ui
/** * 解析properties的具体方法 * @param context * @throws Exception */ private void propertiesElement(XNode context) throws Exception { if (context != null) { // 将子节点的 name 以及value属性set进properties对象 // 这儿能够注意一下顺序,xml配置优先, 外部指定properties配置其次 Properties defaults = context.getChildrenAsProperties(); // 获取properties节点上 resource属性的值 String resource = context.getStringAttribute("resource"); // 获取properties节点上 url属性的值, resource和url不能同时配置 String url = context.getStringAttribute("url"); if (resource != null && url != null) {//url 和resource不能同时配置 throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } // 把解析出的properties文件set进Properties对象 if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } // 将configuration对象中已配置的Properties属性与刚刚解析的融合 // configuration这个对象会装载所解析mybatis配置文件的全部节点元素,之后也会频频提到这个对象 Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } // 把装有解析配置propertis对象set进解析器, 由于后面可能会用到 parser.setVariables(defaults); // set进configuration对象 configuration.setVariables(defaults); } }
settings节点下的配饰是mybatis的全局性配置,修改的是configuration对象的属性,具体说明参考官方文档url
/** * settings标签就是设置configuration对象的各类属性, * 具体属性说明能够参考mybatis官方文档 * @param props * @throws Exception */ private void settingsElement(Properties props) throws Exception { 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.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"))); @SuppressWarnings("unchecked") Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler")); configuration.setDefaultEnumTypeHandler(typeHandler); 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")); @SuppressWarnings("unchecked") Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl")); configuration.setLogImpl(logImpl); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); }
environments元素节点主要配置数据库事物,数据源。能够配置多个environment子节点,假如咱们系统的开发环境和正式环境所用的数据库不同(这是确定的), 那么能够设置两个environment, 两个id分别对应开发环境(development)和正式环境(final),那么经过配置environments的default属性就能选择对应的environment了, 例如,我将environments的deault属性的值配置为development, 那么就会选择dev的environment。具体实现以下插件
/** * 解析enviroments元素节点的方法 * @param context * @throws Exception */ private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { //获取 <environments default="development"> 中的default值 environment = context.getStringAttribute("default"); } // 循环environments的子节点 for (XNode child : context.getChildren()) { // 获取 <environment id="development"> z中的id String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) {//根据由environments的default属性去选择对应的enviroment // 事物 mybatis有两种:JDBC 和 MANAGED, 配置为JDBC则直接使用JDBC的事务,配置为MANAGED则是将事务托管给容器 // <transactionManager type="JDBC"/> TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); //enviroment节点下面就是dataSource节点了,解析dataSource节点 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); // 将dataSource设置进configuration对象 configuration.setEnvironment(environmentBuilder.build()); } } } }
typeAliases节点主要用来设置别名,其实这是挺好用的一个功能, 经过配置别名,咱们不用再指定完整的包名xml
/** * 解析typeAliases 节点 * <typeAliases> * <!--<package name="com.lpf.entity"></package>--> * <typeAlias alias="UserEntity" type="com.lpf.entity.User"/> * </typeAliases> * @param parent */ private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { //若是子节点是package, 那么就获取package节点的name属性, mybatis会扫描指定的package if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); //TypeAliasRegistry 负责管理别名, 这儿就是经过TypeAliasRegistry 进行别名注册 configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { //若是子节点是typeAlias节点,那么就获取alias属性和type的属性值 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); } } } } }
具体的别名注册类
public class TypeAliasRegistry { // 别名经过一个HashMap来实现, key为别名, value就是别名对应的类型(class对象) private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>(); /** * mybatis默认为咱们注册的别名 */ public TypeAliasRegistry() { registerAlias("string", String.class); registerAlias("byte", Byte.class); registerAlias("long", Long.class); registerAlias("short", Short.class); registerAlias("int", Integer.class); registerAlias("integer", Integer.class); registerAlias("double", Double.class); registerAlias("float", Float.class); registerAlias("boolean", Boolean.class); registerAlias("byte[]", Byte[].class); registerAlias("long[]", Long[].class); registerAlias("short[]", Short[].class); registerAlias("int[]", Integer[].class); registerAlias("integer[]", Integer[].class); registerAlias("double[]", Double[].class); registerAlias("float[]", Float[].class); registerAlias("boolean[]", Boolean[].class); registerAlias("_byte", byte.class); registerAlias("_long", long.class); registerAlias("_short", short.class); registerAlias("_int", int.class); registerAlias("_integer", int.class); registerAlias("_double", double.class); registerAlias("_float", float.class); registerAlias("_boolean", boolean.class); registerAlias("_byte[]", byte[].class); registerAlias("_long[]", long[].class); registerAlias("_short[]", short[].class); registerAlias("_int[]", int[].class); registerAlias("_integer[]", int[].class); registerAlias("_double[]", double[].class); registerAlias("_float[]", float[].class); registerAlias("_boolean[]", boolean[].class); registerAlias("date", Date.class); registerAlias("decimal", BigDecimal.class); registerAlias("bigdecimal", BigDecimal.class); registerAlias("biginteger", BigInteger.class); registerAlias("object", Object.class); registerAlias("date[]", Date[].class); registerAlias("decimal[]", BigDecimal[].class); registerAlias("bigdecimal[]", BigDecimal[].class); registerAlias("biginteger[]", BigInteger[].class); registerAlias("object[]", Object[].class); registerAlias("map", Map.class); registerAlias("hashmap", HashMap.class); registerAlias("list", List.class); registerAlias("arraylist", ArrayList.class); registerAlias("collection", Collection.class); registerAlias("iterator", Iterator.class); registerAlias("ResultSet", ResultSet.class); } /** * 处理别名, 直接从保存有别名的hashMap中取出便可 */ @SuppressWarnings("unchecked") // throws class cast exception as well if types cannot be assigned public <T> Class<T> resolveAlias(String string) { try { if (string == null) { return null; } // issue #748 String key = string.toLowerCase(Locale.ENGLISH); Class<T> value; if (TYPE_ALIASES.containsKey(key)) { value = (Class<T>) TYPE_ALIASES.get(key); } else { value = (Class<T>) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); } } /** * 配置文件中配置为package的时候,扫描包下的Javabean ,而后自动注册别名 * 默认会使用 Bean 的首字母小写的非限定类名来做为它的别名 * 也可在javabean 加上注解@Alias 来自定义别名, 例如: @Alias(user) */ public void registerAliases(String packageName){ registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for(Class<?> type : typeSet){ // Ignore inner classes and interfaces (including package-info.java) // Skip also inner classes. See issue #6 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } } public void registerAlias(Class<?> type) { String alias = type.getSimpleName(); Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } registerAlias(alias, type); } //向hashMap中注册别名 public void registerAlias(String alias, Class<?> value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // issue #748 String key = alias.toLowerCase(Locale.ENGLISH); if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'."); } TYPE_ALIASES.put(key, value); } public void registerAlias(String alias, String value) { try { registerAlias(alias, Resources.classForName(value)); } catch (ClassNotFoundException e) { throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e); } } /** * 获取保存别名的HashMap, Configuration对象持有对TypeAliasRegistry的引用, * 所以,若是须要,咱们能够经过Configuration对象获取 */ public Map<String, Class<?>> getTypeAliases() { return Collections.unmodifiableMap(TYPE_ALIASES); } }
typeHandlers节点的解析和typeAlianses节点的解析相似
/** * 解析typeHandlers节点 * 不管是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时, * 仍是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。 * Mybatis默认为咱们实现了许多TypeHandler, 当咱们没有配置指定TypeHandler时, * Mybatis会根据参数或者返回结果的不一样,默认为咱们选择合适的TypeHandler处理。 * @param parent * @throws Exception */ private void typeHandlerElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { //子节点为package时,获取其name属性的值,而后自动扫描package下的自定义typeHandler if ("package".equals(child.getName())) { String typeHandlerPackage = child.getStringAttribute("name"); typeHandlerRegistry.register(typeHandlerPackage); } else { //子节点为typeHandler时, 能够指定javaType属性, 也能够指定jdbcType, 也可二者都指定 //javaType 是指定java类型 //jdbcType 是指定jdbc类型(数据库类型: 如varchar) String javaTypeName = child.getStringAttribute("javaType"); String jdbcTypeName = child.getStringAttribute("jdbcType"); //handler就是咱们配置的typeHandler String handlerTypeName = child.getStringAttribute("handler"); Class<?> javaTypeClass = resolveClass(javaTypeName); //JdbcType是一个枚举类型,resolveJdbcType方法是在获取枚举类型的值 JdbcType jdbcType = resolveJdbcType(jdbcTypeName); Class<?> typeHandlerClass = resolveClass(handlerTypeName); //注册typeHandler, typeHandler经过TypeHandlerRegistry这个类管理 if (javaTypeClass != null) { if (jdbcType == null) { typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } else { typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); } } else { typeHandlerRegistry.register(typeHandlerClass); } } } } }
插件是mybatis提供的扩展机制,用户能够经过添加自定义插件在SQL语句执行的过程当中某一环节进行拦截,mybatis中的自定义插件只需实现Interceptor接口,并经过注解指定拦截的方法签名,这个后面具体介绍。
/** * 解析plugins标签 * mybatis中的plugin其实就是个interceptor, * 它能够拦截Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler 的部分方法,处理咱们本身的逻辑。 * @param parent * @throws Exception */ private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); // 咱们在定义一个interceptor的时候,须要去实现Interceptor Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); // 向configuration对象中注册拦截器 configuration.addInterceptor(interceptorInstance); } } }
mybatis初始化时,出了加载mybatis-config.xml的全局配置文件,还会加载所有的映射配置文件,即mappers节点配置的mapper.
/** * 解析mapper文件,mapper能够理解为dao的实现 * @param parent * @throws Exception */ private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { //若是mappers节点的子节点是package, 那么就扫描package下的文件, 注入进configuration 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"); //resource, url, class 三选一 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //mapper映射文件都是经过XMLMapperBuilder解析 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初始化过程当中对mybatis-config.xml配置文件的解析过程到这吧,下一个就叫啥mapper配置文件的解析过程。