MyBatis学习 (一) 配置文件解析流程

news2024/11/26 13:43:34

MyBatis源码学习

最近在学习MyBatis的代码。记录下

首先下载下源码:

https://github.com/mybatis/parent
https://github.com/mybatis/mybatis-3

parent为父控依赖。也需要下载。

入口

InputStream inputStream = null;
try {
    // 获取配置文件
    inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    // 解析配置文件,并获取SqlSessionFactory对象
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = factory.openSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    String userName = mapper.getUserNameById("5");
    System.err.println(userName);
} catch (IOException e) {
    throw new RuntimeException(e);
}

一、创建DefaultSqlSessionFactory对象

第一步中是读取资源的,这里就不做分析了。

mybatis的配置介绍:

https://mybatis.org/mybatis-3/zh/configuration.html

第二步构建SqlSessionFactory,这里会根据解析的mybatis-config.xml来构建一个SqlSessionFactory。

经过多次的build方法重载会进入解析xml的方法中。最终new一个DefaultSqlSessionFactory出来。

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 {
            if (inputStream != null) {
                inputStream.close();
            }
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}
  • XMLConfigBuilder

先看看XMLConfigBuilder继承自BaseBuilder.

public class XMLConfigBuilder extends BaseBuilder {
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(Configuration.class, inputStream, environment, props);
    }
    
    public XMLConfigBuilder(Class<? extends Configuration> configClass, Reader reader, String environment,
                            Properties props) {
        this(configClass, new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
    }
    private XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment,
                             Properties props) {
        super(newConfig(configClass));
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
    
    // 上边传入的就是Configuration.class,这里反射去创建一个Configuration对象。
    private static Configuration newConfig(Class<? extends Configuration> configClass) {
        try {
            return configClass.getDeclaredConstructor().newInstance();
        } catch (Exception ex) {
            throw new BuilderException("Failed to create a new Configuration instance.", ex);
        }
    }
}
public abstract class BaseBuilder {
    // 配置文件
    protected final Configuration configuration;
    // 别名处理器注册
    protected final TypeAliasRegistry typeAliasRegistry;
    // 类型处理器注册
    protected final TypeHandlerRegistry typeHandlerRegistry;
}
  • Configuration

再看看Configuration对象的构造函数。这里全都是类型注册的别名映射。

public Configuration(Environment environment) {
    this();
    this.environment = environment;
}
// 别名初始配置
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.parse

propertiesElement方法:解析properties配置项

settingsAsProperties方法:解析settings标签

loadCustomVfs方法:读取vfs文件

loadCustomLogImpl方法:解析日志设置

typeAliasesElement方法:解析类型别名配置项

pluginElement方法:解析插件配置

objectFactoryElement方法:创建对象

objectWrapperFactoryElement方法:对对象进行包装

reflectorFactoryElement方法:

public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
private void parseConfiguration(XNode root) {
    try {
        // issue #117 read properties first
        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);
        // 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);
    }
}

propertiesElement

加载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);
    }
}

settingsAsProperties

把settings配置也解析成properties文件

private Properties settingsAsProperties(XNode context) {
    if (context == null) {
        return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    // 获取到Configuration的属性和方法后,检查是否存在xml配置中的属性是否存在
    for (Object key : props.keySet()) {
        // hasSetter方法在下边代码块中放着
        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;
}
  • MetaClass.forClass

反射:创建了一个reflector对象出来,这个reflector对象就是把Configuration中所有的属性和方法都获取到。

private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
    this.reflector = reflectorFactory.findForClass(type);
}
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
    return new MetaClass(type, reflectorFactory);
}
// 检查prop中的属性是否存在对象的属性,如果不存在直接报错
public boolean hasSetter(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (!prop.hasNext()) {
        return reflector.hasSetter(prop.getName());
    }
    if (reflector.hasSetter(prop.getName())) {
        MetaClass metaProp = metaClassForProperty(prop.getName());
        return metaProp.hasSetter(prop.getChildren());
    } else {
        return false;
    }
}
  • DefaultReflectorFactory.findForClass
@Override
public Reflector findForClass(Class<?> type) {
    if (classCacheEnabled) {
        // synchronized (type) removed see issue #461
        return MapUtil.computeIfAbsent(reflectorMap, type, Reflector::new);
    }
    return new Reflector(type);
}

loadCustomVfs

加载vfs标签,这个我在近5年都没有用过,也没了解过这个。这个就直接跳过了

private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    String value = props.getProperty("vfsImpl");
    if (value != null) {
        String[] clazzes = value.split(",");
        for (String clazz : clazzes) {
            if (!clazz.isEmpty()) {
                @SuppressWarnings("unchecked")
                Class<? extends VFS> vfsImpl = (Class<? extends VFS>) Resources.classForName(clazz);
                configuration.setVfsImpl(vfsImpl);
            }
        }
    }
}

loadCustomLogImpl

这里是日志配置选项

在这里插入图片描述

可以看到mybatis适配了多种的实现。

private void loadCustomLogImpl(Properties props) {
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
}
  • BaseBuilder.resolveClass

这里可以看到最终解析的log也可以自定义,只要实现了Log接口,并且注册到configuration中的typeAlias中就可以。

protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
protected <T> Class<? extends T> resolveClass(String alias) {
    if (alias == null) {
        return null;
    }
    try {
        return resolveAlias(alias);
    } catch (Exception e) {
        throw new BuilderException("Error resolving class. Cause: " + e, e);
    }
}
protected <T> Class<? extends T> resolveAlias(String alias) {
    return typeAliasRegistry.resolveAlias(alias);
}

typeAliasesElement

中文官网:https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases

<typeAliases>
    <!-- <package name="domain.blog"/>  -->
    <typeAlias alias="Author" type="domain.blog.Author"/>
</typeAliases>
@Alias("author")
public class Author {
    private String id;
}

typeAliasRegistry这个是类型别名注册器。类型别名总共有3种使用方式。

  1. xml中配置package,从代码上看,package和alias只有一种会生效,而且package的优先级高
  2. xml中配置alias
  3. 在实体类上增加注解@Alias
private void typeAliasesElement(XNode parent) {
    // 有设置过typeAliases配置
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
			// 如果配置了package就按照包名去扫描
            if ("package".equals(child.getName())) {
                String typeAliasPackage = child.getStringAttribute("name");
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                // 上边解析package的时候,下面的这些方法基本都会用到。就不做单独的分析了。
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        // 扫描@Alias注解
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}
  • TypeAliasRegistry.registerAliases
public void registerAliases(String packageName) {
    registerAliases(packageName, Object.class);
}

public void registerAliases(String packageName, Class<?> superType) {
    // 实例ResolverUtil
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 寻找package中所有的类
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // 再通过resolverUtil.getClasses()获取到所有的类
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    // 循环遍历Set中所有的类
    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);
}

private final Map<String, Class<?>> typeAliases = new 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 (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
        throw new TypeException(
            "The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    // 否则直接假如到typeAliases中
    typeAliases.put(key, value);
}

// 再看看这里都是已经注册过的类,这时终于明白为什么可以直接在对应的类型上写string,写int了。typeAlias到此就结束了。
public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("char", Character.class);
    registerAlias("character", Character.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);
	// ... 
}
  • ResolverUtil.find

package为传入的路径,遍历所有的class类,如果以.class结尾,loadClasss,然后加入到matches(Set)中.

private Set<Class<? extends T>> matches = new HashSet<>();
public ResolverUtil<T> find(Test test, String packageName) {
    String path = getPackagePath(packageName);

    try {
        List<String> children = VFS.getInstance().list(path);
        for (String child : children) {
            if (child.endsWith(".class")) {
                addIfMatching(test, child);
            }
        }
    } catch (IOException ioe) {
        log.error("Could not read package: " + packageName, ioe);
    }

    return this;
}
protected void addIfMatching(Test test, String fqn) {
    try {
        String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
        ClassLoader loader = getClassLoader();
        if (log.isDebugEnabled()) {
            log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
        }

        Class<?> type = loader.loadClass(externalName);
        if (test.matches(type)) {
            matches.add((Class<T>) type);
        }
    } catch (Throwable t) {
        log.warn("Could not examine class '" + fqn + "'" + " due to a " + t.getClass().getName() + " with message: "
                 + t.getMessage());
    }
}
public Set<Class<? extends T>> getClasses() {
    return matches;
}

pluginElement

插件中文官网:https://mybatis.org/mybatis-3/zh/configuration.html#plugins

插件是可以配置多个的

<plugins>
    <plugin interceptor="org.mybatis.example.ExamplePlugin">
        <property name="someProperty" value="100"/>
    </plugin>
</plugins>
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 interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor()
          .newInstance();
      interceptorInstance.setProperties(properties);
      // 加到插件链中去,下面的代码可以看到插件是一个ArrayList
      configuration.addInterceptor(interceptorInstance);
    }
  }
}
  • Configuration.addInterceptor
protected final InterceptorChain interceptorChain = new InterceptorChain();
public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}
  • InterceptorChain.addInterceptor
public class InterceptorChain {

    private final List<Interceptor> interceptors = new ArrayList<>();

    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
            target = interceptor.plugin(target);
        }
        return target;
    }

    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }

}

objectFactoryElement

默认的ObjectFactory实现。

Configuration类中,protected ObjectFactory objectFactory = new DefaultObjectFactory();

objectFactory是这样实例的,就是直接new了一个DefaultObjectFactory。

如果不适用默认的实现,可以自己写一个,然后配置到xml中。解析的时候就会用到自己实现的。

private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        Properties properties = context.getChildrenAsProperties();
        ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        factory.setProperties(properties);
        configuration.setObjectFactory(factory);
    }
}

通过DefaultObjectFactory中的代码可以了解到这个类主要是为了创建类的。那么在整个适用的过程中,这个类应该是以单例的形式存在的。

public class DefaultObjectFactory implements ObjectFactory, Serializable {

    private static final long serialVersionUID = -8855120656740914948L;

    @Override
    public <T> T create(Class<T> type) {
        return create(type, null, null);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        Class<?> classToCreate = resolveInterface(type);
        // we know types are assignable
        return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
    }

    private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        try {
            Constructor<T> constructor;
            if (constructorArgTypes == null || constructorArgs == null) {
                constructor = type.getDeclaredConstructor();
                try {
                    return constructor.newInstance();
                } catch (IllegalAccessException e) {
                    if (Reflector.canControlMemberAccessible()) {
                        constructor.setAccessible(true);
                        return constructor.newInstance();
                    }
                    throw e;
                }
            }
            constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));
            try {
                return constructor.newInstance(constructorArgs.toArray(new Object[0]));
            } catch (IllegalAccessException e) {
                if (Reflector.canControlMemberAccessible()) {
                    constructor.setAccessible(true);
                    return constructor.newInstance(constructorArgs.toArray(new Object[0]));
                }
                throw e;
            }
        } catch (Exception e) {
            String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList).stream()
                .map(Class::getSimpleName).collect(Collectors.joining(","));
            String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList).stream()
                .map(String::valueOf).collect(Collectors.joining(","));
            throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values ("
                                          + argValues + "). Cause: " + e, e);
        }
    }

    protected Class<?> resolveInterface(Class<?> type) {
        Class<?> classToCreate;
        if (type == List.class || type == Collection.class || type == Iterable.class) {
            classToCreate = ArrayList.class;
        } else if (type == Map.class) {
            classToCreate = HashMap.class;
        } else if (type == SortedSet.class) { // issue #510 Collections Support
            classToCreate = TreeSet.class;
        } else if (type == Set.class) {
            classToCreate = HashSet.class;
        } else {
            classToCreate = type;
        }
        return classToCreate;
    }

    @Override
    public <T> boolean isCollection(Class<T> type) {
        return Collection.class.isAssignableFrom(type);
    }

objectWrapperFactoryElement

这个用于对对象进行包装,名字带有wrapper,跟上边的是一样的加载逻辑

private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        configuration.setObjectWrapperFactory(factory);
    }
}

reflectorFactoryElement

这个是反射类,实例化过程和上边的一样

private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        configuration.setReflectorFactory(factory);
    }
}

settingsElement

这个是配置文件中的setting设置的属性,都有一个默认的值。

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")));
    configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
    configuration.setArgNameBasedConstructorAutoMapping(
        booleanValueOf(props.getProperty("argNameBasedConstructorAutoMapping"), false));
    configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
    configuration.setNullableOnForEach(booleanValueOf(props.getProperty("nullableOnForEach"), false));
}

environmentsElement & databaseIdProviderElement

创建数据源

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
            environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory)
                    .dataSource(dataSource);
                configuration.setEnvironment(environmentBuilder.build());
                break;
            }
        }
    }
}
private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
        String type = context.getStringAttribute("type");
        // awful patch to keep backward compatibility
        if ("VENDOR".equals(type)) {
            type = "DB_VENDOR";
        }
        Properties properties = context.getChildrenAsProperties();
        databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
        databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
        String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
        configuration.setDatabaseId(databaseId);
    }
}

typeHandlerElement

创建类型转换器

private void typeHandlerElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                String handlerTypeName = child.getStringAttribute("handler");
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);
                if (javaTypeClass != null) {
                    if (jdbcType == null) {
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}
  • TypeHandlerRegistry.register
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();

public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
        for (Class<?> handledType : mappedTypes.value()) {
            register(handledType, typeHandler);
            mappedTypeFound = true;
        }
    }
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
        try {
            TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
            register(typeReference.getRawType(), typeHandler);
            mappedTypeFound = true;
        } catch (Throwable t) {
            // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
        }
    }
    if (!mappedTypeFound) {
        register((Class<T>) null, typeHandler);
    }
}
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
        for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            register(javaType, handledJdbcType, typeHandler);
        }
        if (mappedJdbcTypes.includeNullJdbcType()) {
            register(javaType, null, typeHandler);
        }
    } else {
        register(javaType, null, typeHandler);
    }
}
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
        Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
        if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            map = new HashMap<>();
        }
        map.put(jdbcType, handler);
        typeHandlerMap.put(javaType, map);
    }
    allTypeHandlersMap.put(handler.getClass(), handler);
}

先获取注解MappedTypes,如果有这个注解的话,直接调用register(handledType, typeHandler);方法,把jdbcType和javaType保存到一个map中。

这里的配置方式有2种,一种是在对应的实现类上加注册@MappedJdbcTypes@MappedTypes,另外一种是直接在xml中配置。
从源码里,我们也可以看出来他为什么支持这2种。

这里有2个map,jdbcTypeHandlerMap这个是保存jdbc类型和java中类型的关系的。allTypeHandlersMap是保存类和类型转换器之间的关系。

mapperElement

mapper映射器:https://mybatis.org/mybatis-3/zh/configuration.html#%E6%98%A0%E5%B0%84%E5%99%A8%EF%BC%88mappers%EF%BC%89

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);
          try (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);
          try (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.");
        }
      }
    }
  }
}

目前使用package的比较多,就看这个解析的逻辑吧

<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
  • Configuration.addMappers
public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
}
  • MapperRegistry.addMappers

每加载一个mapper,就会创建一个对应类型的MapperProxyFactory代理工厂。

public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
}

public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}
  • MapperAnnotationBuilder.parse

这个parse是把所有的xml文件都解析出来,解析成功后放入到mapperRegistry的knownMapper集合中

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 解析xml文件
        loadXmlResource();
        configuration.addLoadedResource(resource);
        // 设置namespace
        assistant.setCurrentNamespace(type.getName());
		// 解析cache标签
        parseCache();
        // 解析caecheRef标签
        parseCacheRef();
        for (Method method : type.getMethods()) {
            if (!canHaveStatement(method)) {
                continue;
            }
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                && method.getAnnotation(ResultMap.class) == null) {
                parseResultMap(method);
            }
            try {
                parseStatement(method);
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        String xmlResource = type.getName().replace('.', '/') + ".xml";
        // #1347
        InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
        if (inputStream == null) {
            // Search XML mapper that is not in the module but in the classpath.
            try {
                inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
            } catch (IOException e2) {
                // ignore, resource is not required
            }
        }
        if (inputStream != null) {
            XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource,
                                                              configuration.getSqlFragments(), type.getName());
            xmlParser.parse();
        }
    }
}
  • XMLMapperBuilder.parse
public void parse() {
    //  如果还没解析的话,就解析
    if (!configuration.isResourceLoaded(resource)) {
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

private void configurationElement(XNode context) {
    try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        // 下面解析xml中对应的cache等标签,一个个的读取,完成后,最终存放到了Configuration中
        builderAssistant.setCurrentNamespace(namespace);
        cacheRefElement(context.evalNode("cache-ref"));
        cacheElement(context.evalNode("cache"));
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        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);
    }
}

private void buildStatementFromContext(List<XNode> list) {
    // 多数据源的话,走这里
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,
                                                                            requiredDatabaseId);
        try {
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

  • XMLStatementBuilder.parseStatementNode
public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                                                   configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
            ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType
        .valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    if (resultTypeClass == null && resultMap == null) {
        resultTypeClass = MapperAnnotationBuilder.getMethodReturnType(builderAssistant.getCurrentNamespace(), id);
    }
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
        resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");
    boolean dirtySelect = context.getBooleanAttribute("affectData", Boolean.FALSE);

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
                                        parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
                                        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}
  • MapperBuilderAssistant.addMappedStatement
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
                                          SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
                                          String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
                                          boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
                                          LanguageDriver lang, String resultSets, boolean dirtySelect) {

    if (unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType)
        .keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang)
        .resultOrdered(resultOrdered).resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType)
        .flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
        statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
}
  • Configuration.addMappedStatement

最终放入到了mappedStatements中。这个mappedStatements对应的就是curd标签。后续可以根据id获取到mapper中对应的sql语句。

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(
    "Mapped Statements collection")
public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
}

到这里,mybatis的配置文件解析就完成了 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/513540.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java经典笔试题—day06

Java经典笔试题—day06 &#x1f50e;选择题&#x1f50e;编程题&#x1f95d;不要二&#x1f95d;把字符串转换成整数 &#x1f50e;结尾 &#x1f50e;选择题 (1)关于抽象类与最终类&#xff0c;下列说法错误的是&#xff1f; A. 抽象类能被继承&#xff0c;最终类只能被实…

Python、Pytest、Allure、Selenium和Jenkins实现自动化测试集成实例

下方查看历史精选文章 重磅发布 - 自动化框架基础指南pdfv1.1大数据测试过程、策略及挑战 测试框架原理&#xff0c;构建成功的基石 在自动化测试工作之前&#xff0c;你应该知道的10条建议 在自动化测试中&#xff0c;重要的不是工具 本文将介绍如何使用Python、Pytest、Allur…

燃气巡检系统

燃气管网检查是整个管网可靠运行的重要保证&#xff0c;尤其是加强天然气管道巡检管理更是重中之重。要做好管网维护工作&#xff0c;必须认真把握好每个环节&#xff0c;每个过程&#xff0c;每个细节&#xff0c;认真落实管网巡维制度等&#xff0c;而通过凡尔码管理系统可使…

程序员痛心流涕自述:“因为把自己代码给了别人,我亲手断送了自己的前程”

在求职的过程中&#xff0c;一般都会有投递简历、笔试、面试以及背调的环节&#xff0c;而在这几个环节中折戟沉沙的人也着实不少。 不少人觉得&#xff0c;在求职时简历需要优化&#xff0c;背调不能有瞒报、捏造的情况&#xff0c;而笔试面试则是纯纯的要靠硬实力。 虽然说…

MySQL基础(二十六)索引的创建与设计原则

1. 索引的声明与使用 1. 1 索引的分类 MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。 从功能逻辑上说&#xff0c;索引主要有 4 种&#xff0c;分别是普通索引、唯一索引、主键索引、全文索引。 按照物理实现方式&#xff0c;索引可以分…

【Linux】shell编程之循环语句

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、循环语句二、for循环语句1.for 语句的结构2.for语句应用示例 三、while 循环语句1.while 循环语句结构2.while语句应用示例 四、until 循环五、跳出循环六、死循…

【Linux】信号的保存

信号的小细节真的很多~ 文章目录 前言一、信号的保存总结 前言 首先我们先引出一个新的概念&#xff0c;叫核心转储。linux系统提供了一种能力&#xff0c;操作系统可以将一个进程在异常的时候将核心代码部分进行核心转储&#xff0c;将内存中进程的相关数据全部dump到磁盘中&…

新版ripro全站美化子主题美化包使用说明手册

前言: 安装 Ripro 前,首先是需要先安装好 WordPress 主程序,因为 Ripro和美化包只是 WordPress 的一枚主题而已! 如何安装 WordPress 并不在本教程范围内,建议百度 如何安装好 WordPress ,但是还是想建议一下,尽量选择 Linux+php+nginx 的机器来安装 WordPress! 安装…

win部署CAS服务并使用

前提描述&#xff1a;通过本次了解cas是个什么东西&#xff0c;并使用它。 cas为oss(单点登录)的一种实现方案。要实现cas单点登录&#xff0c;首先需要部署cas的server服务。 CAS是Central Authentication Service的缩写&#xff0c;中央认证服务&#xff0c;。 一、安装CAS…

若依管理系统RuoYi-Vue:权限系统设计详解

文章目录 摘要数据库表结构设计菜单管理目录、菜单和按钮的区别菜单权限 api接口权限配置方法PreAuthorize注解介绍数据权限 前端vue权限拦截菜单权限按钮权限 摘要 若依&#xff08;RuoYi&#xff09;是一款基于Spring Boot和Vue.js开发的快速开发平台&#xff0c;它的权限管…

今天面了个阿里拿25k出来的小哥,让我见识到了什么是测试天花板

2022年堪称大学生就业最难的一年&#xff0c;应届毕业生人数是1076万。失业率超50%&#xff01; 但是我观察到一个数据&#xff0c;那就是已经就业的毕业生中&#xff0c;计算机通信等行业最受毕业生欢迎&#xff01; 计算机IT行业薪资高&#xff0c;平均薪资是文科其他岗位的…

优秀CRM系统的四个条件

如今&#xff0c;构建“以客户为中心”的经营模式&#xff0c;是许多企业提升竞争力的核心战略。CRM系统能够管理客户关系&#xff0c;提高销售线索转化率&#xff0c;帮助企业实现业绩增长。那么众多品牌中&#xff0c;CRM系统哪家公司做得更好&#xff1f; CRM做得好有哪几个…

Vue换肤主题

拷贝颜色选择组件 **ThemePicker <template><!-- navabar的换肤组件 --><el-color-pickerv-model="theme":predefine="[#409EFF,

智慧校园水电节能监管系统

现阶段各高校用电设备量多范围广&#xff0c;包含寝室、办公室、教室、会议厅、试验室、公共图书馆、运动场馆、饭堂、路面、园林绿化等地方&#xff0c;能耗极大。而且大多数节能意识薄弱&#xff0c;欠缺科学合理、科学合理的规章制度开展监管&#xff0c;造成电力能源很多消…

达索的全面的三维设计和产品开发工具CATIA V5-6R 2020版本下载与安装配置教程

目录 前言一、CATIA 安装二、CATIA更新包安装三、使用配置四、帮助文档安装&#xff08;非必要&#xff09;总结 前言 Dassault Systemes公司的CATIA软件是一种基于计算机辅助设计&#xff08;CAD&#xff09;和计算机辅助制造&#xff08;CAM&#xff09;的软件&#xff0c;用…

Vue(组件间通信:props、自定义事件、全局事件总线、消息订阅与发布)

一、props props不仅可以实现父给子传递信息&#xff0c;还可以进行子给父传递信息 1.父给子传递信息&#xff1a; 父组件中给子组件实例传递信息 子组件利用props进行接收组件传递信息&#xff08;接收方式有三种&#xff1a;数组、对象、配置对象&#xff09; 2.子给父传递…

【表面缺陷检测】基于yolov5的布匹表面缺陷检测(附代码和数据集)

写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 路虽远,行则将至;事虽难,做则必成。只要有愚公移山的志气、滴水穿石的毅力,脚踏实地,埋头苦干,积跬步以至千里,就…

Mybatis Plus 入门 简单的CRUD 使用详解 条件查询 分页查询 DML操作 MP代码生成器

Mybatis Plus入门 MP是 MybatisPlus&#xff0c;简称MP&#xff0c;是一个 Mybatis 的增强工具&#xff0c;在 Mybatis 的基础上只做增强不做改变。MP为简化开发、提高效率而生。 它已经封装好了单表curd方法&#xff0c;我们直接调用这些方法就能实现单表CURD。 注意&#xf…

一分钟图情论文:《AIGC驱动的智慧图书馆转型:框架、路径与挑战》

一分钟图情论文&#xff1a;《AIGC驱动的智慧图书馆转型&#xff1a;框架、路径与挑战》 AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;是一种全新的生产方式&#xff0c;利用人工智能技术自动生成文本、图片、语音、视频甚至虚拟现实等各种形式的数…

5 创建映射

5 映射 上边章节安装了ik分词器&#xff0c;如果在索引和搜索时去使用ik分词器呢&#xff1f;如何指定其它类型的field&#xff0c;比如日期类型、数 值类型等。 本章节学习各种映射类型及映射维护方法。 5.1 映射维护方法 1、查询所有索引的映射&#xff1a; GET&#xf…