Mybatis 配置文件解析(一)

一、Mybatis配置

1.1 配置全局文件

在使用MyBtis的时候,我们首先需要配置一个全局配置文件,在这个配置文件中,我们可以配置MyBatis的属性、数据源、插件、别名、基础设置以及SQL配置文件的路径等

其中在<mappers>标签中,可以定义SQL配置文件的信息,Mybatis提供了四种配置SQL文件的方式,但第四种我们通常都不会用,而第一种和第三种,指定包路径或Mapper接口路径的用法,都需要xml的文件名和接口名一样;而第二种注解指定xml,不需要一样,是因为在xml中的namespace属性可以指定xml对用的接口是哪一个

基础配置如下:

<configuration>
    <!--properties 扫描属性文件.properties  -->
    <properties resource="db.properties"></properties>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <package name="com.lizhi.entity.User"/>
    </typeAliases>

    <plugins>
        <plugin interceptor="com.lizhi.plugins.ExamplePlugin" >
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--//  mybatis内置了JNDI、POOLED、UNPOOLED三种类型的数据源,其中POOLED对应的实现为org.apache.ibatis.datasource.pooled.PooledDataSource,它是mybatis自带实现的一个同步、线程安全的数据库连接池 一般在生产中,我们会使用c3p0或者druid连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${mysql.driverClass}"/>
                <property name="url" value="${mysql.jdbcUrl}"/>
                <property name="username" value="${mysql.user}"/>
                <property name="password" value="${mysql.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--1.必须保证接口名(例如IUserDao)和xml名(IUserDao.xml)相同,还必须在同一个包中-->
        <package name="com.lizhi.mapper"/>
        <!--2.不用保证同接口同包同名-->
        <mapper resource="com/mybatis/mappers/EmployeeMapper.xml"/>
        <!--3.保证接口名(例如IUserDao)和xml名(IUserDao.xml)相同,还必须在同一个包中-->
        <mapper class="com.mybatis.dao.EmployeeMapper"/>
        <!--4.不推荐:引用网路路径或者磁盘路径下的sql映射文件 file:///var/mappers/AuthorMapper.xml-->
        <mapper url="file:E:/Study/myeclipse/_03_Test/src/cn/sdut/pojo/PersonMapper.xml"/>
    </mappers>
</configuration>

1.2 配置SQL文件

在上面,我们使用的是通过<package>标签来指定SQL配置,所以SQL配置文件的名称和Mapper接口的名称必须保持一致

SQL配置文件:

<mapper namespace="com.lizhi.mapper.UserMapper">
    <cache ></cache>
    <!-- Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?-->
    <resultMap id="result" type="com.lizhi.entity.User">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="name" jdbcType="VARCHAR" property="userName"/>
        <result column="create_time" jdbcType="DATE" property="createTime"/>
        <!--<collection property="" select=""-->
    </resultMap>

    <select id="selectById" resultMap="result"  >
        select id,name ,create_time from user
        <where>
            <if test="id > 0">
                and id=#{id}
            </if>
        </where>
    </select>

</mapper>

Mapper接口:

package com.lizhi.mapper;

import com.lizhi.entity.User;

public interface UserMapper {

    User selectById(Integer id);

    void updateForName(String id,String username);
}

public class User implements Serializable{

    private Long id ;
    private String userName ;
    private Date createTime;
}

1.3 使用Mybatis

上面已经完成了Mybatis的基本配置,下面就可以直接使用了,首先就是去加载配置文件,生成一个SqlSessionFactory,然后再根据SqlSessionFactory创建一个SqlSession,然后通过动态代理的方式获取Mapper接口的代理对象,然后就是调用具体的方法。Mybatis最核心的东西就包括两部分内容:扫描配置类和数据操作,后面会有文件详解介绍Mybatis数据操作的流程,这边文章主要介绍Mybatis是再配置类扫描时,都做了什么,最后生成一个SqlSessionFactory

String resource = "mybatis-config.xml";
//将XML配置文件构建为Configuration配置类
Reader reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory   解析xml文件  1
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

// 数据源 执行器  DefaultSqlSession 2
SqlSession session = sqlSessionFactory.openSession();
try {
    // 创建动态代理
    UserMapper mapper = session.getMapper(UserMapper.class);
    System.out.println(mapper.getClass());
    User user = mapper.selectById(1);
    System.out.println(user.getUserName());

    session.commit();
} catch (Exception e) {
    e.printStackTrace();
    session.rollback();
} finally {
    session.close();
}

二、解析全局配置文件

通过使用Mybatis的代码可以看出,主要是通过SqlSessionFactoryBuilder类的build()方法来实现的,接下来我们详细看下这个build()方法是如何解析的

在Mybatis中,提供了好多种解析类,它们都是继承自BaseBuilder类,在BaseBuilder类中,有一个Configuration属性,它就是用来存放Mybatis所有的

全局配置文件使用XMLConfigBuilder来解析,调用parse()进行解析返回一个Configuration对象,然后再调用build()方法生成一个SqlSessionFactory,我们重点看parse()方法是如何解析的

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    return build(parser.parse());
}

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

需要注意的是,Mybatis会把xml配置文件解析成一个XNode的类型,这个类型是Mybatis内部定义的,就不具体去看了,调用解析器的evalNode()方法,把<configuration></configuration>标签解析成一个XNode对象,然后调用parseConfiguration()解析XNode对象的信息

public Configuration parse() {
    // 若已经解析过了 就抛出异常
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // 设置解析标志位
    parsed = true;
    // 解析我们的mybatis-config.xml的节点 <configuration></configuration>
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

在parseConfiguration()方法中,就是按顺序来对各个节点进行解析了,下面详细介绍各个节点的解析

private void parseConfiguration(XNode root) {
    propertiesElement(root.evalNode("properties"));

    Properties settings = settingsAsProperties(root.evalNode("settings"));
    /**
     * 基本没有用过该属性
     * VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
       Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
       解析到:org.apache.ibatis.session.Configuration#vfsImpl
     */
    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"));
}

2.1 属性解析

<properties/>标签解析成一个XNode节点,<properties/>提供了三种配置属性的方式,分别是:

<properties resource="db.properties"></properties>
<properties url="http://www.baidu.com/db.properties"></properties>
<properties>
    <property name="lizhi" value="lizhi"/>
</properties>

针对这三种方式的属性配置,Mybatis通过不同的方法来解析,调用getChildrenAsProperties()来解析第三种配置,就是首先获取<properties/>下的子节点,然后把子节点的namevalue属性拿出来,作为Properties的key和value;而第一种和第三种方式,都是先获取文件或URL的输入流,然后调用Properties的load()方法来加载

最后把这些属性加入到当前的解析器和configuration属性中

/**
 * 解析 properties节点
 *     <properties resource="mybatis/db.properties" />
 *     解析到org.apache.ibatis.parsing.XPathParser#variables
 *           org.apache.ibatis.session.Configuration#variables
 */
propertiesElement(root.evalNode("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) {
            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);
    }
}

2.2 基础设置解析

settings节点下的配置,都是Mybatis提供的,如果没有配置,Mybatis会使用默认的配置,这一步只是把检验配置是否正确,然后把配置存在settings属性中,后面才会把这些属性设置到Configuration属性当中

/**
 * 解析我们的mybatis-config.xml中的settings节点
 * 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
 * <settings>
      <setting name="cacheEnabled" value="true"/>
      <setting name="lazyLoadingEnabled" value="true"/>
     <setting name="mapUnderscoreToCamelCase" value="false"/>
     <setting name="localCacheScope" value="SESSION"/>
     <setting name="jdbcTypeForNull" value="OTHER"/>
      ..............
     </settings>
 *
 */
Properties settings = settingsAsProperties(root.evalNode("settings"));

检验配置参数是否正确,localReflectorFactory是ReflectorFactory的一个实例,ReflectorFactory可以根据传入的Class类型,调用findForClass()生成一个Reflector实例,而Reflector实例中包含了该类的类型,可以参数名,可写参数名,以及所有的getter/setter方法

MetaClass对象里面就包含了ReflectorFactory和Reflector两个实例,所以遍历所有的配置,然后判断Configuration类里面是否有这些属性的setter方法,判断逻辑也很简单,就是在创建Reflector实例的时候,会把所有只有一个参数且方法名是以set开始的方法都拿出来,然后截取set后面的字符串,同时把首字母变成小写,然后就存在Reflector的setMethods属性中,key就是转换后的方法名,value是把方法封装成了一个MethodInvoker对象,方便通过反射直接调用方法

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
    // 其实就是去configuration类里面拿到所有setter方法, 看看有没有当前的配置项
    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;
}
public class Reflector {

    private final Class<?> type;
    private final String[] readablePropertyNames;
    private final String[] writablePropertyNames;
    private final Map<String, Invoker> setMethods = new HashMap<>();
    private final Map<String, Invoker> getMethods = new HashMap<>();
    private final Map<String, Class<?>> setTypes = new HashMap<>();
    private final Map<String, Class<?>> getTypes = new HashMap<>();
    private Constructor<?> defaultConstructor;
}

这一步只是对配置项进行了校验,以及缓存属性值,而在后面的方法中,会直接调用configuration的set方法来赋值

// 设置settings 和默认值到configuration
settingsElement(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));
    ……
}

2.3 日志配置

我们可以通过logImpl来配置Log的实现类,可以是具体的类,也可以是类的别名。首先就会从别名注册器中直接去拿,如果没有,再通过Class.forName()方法来加载该类,然后设置到configuration中

/**
 * 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
 * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
 * 解析到org.apache.ibatis.session.Configuration#logImpl
 */
loadCustomLogImpl(settings);

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

2.4 别名配置

在Configuration类实例化的时候,就回去实例化TypeAliasRegistry(别名处理器)、TypeHandlerRegistry(类型处理器)、MapperRegistry(mapper接口注册器)等等这些属性,而这些属性在实例化的时候都有一些默认的值,以别名处理器TypeAliasRegistry为例,就会添加默认的一些别名:

/**
 * mybaits对我们默认的别名支撑
 */
public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    ……
}

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.put(key, value);
}

但是在使用Mybatis的时候,也可以通过<typeAliases></typeAliases>节点来配置别名

该节点提供了两种配置别名的方式,如果是通过<package>来指定某个类包,则会去遍历包下面所有类,除了接口、匿名类、内部类等,其他都会去加载,然后缓存在别名解析器中

如果是通过aliastype配置别名,就直接加载type指定的类

对于这两种配置方式,如果没有指定别名的名称,Mybatis首先会去看类上是否有@Alias注解,把它的value()值最为别名,否则就调用Class类的getSimpleName()方法,返回值作为别名

最后就是把别名和对应的类注册到configuration属性的TypeAliasRegistry属性中即可

/**
 * 解析我们的别名
 * <typeAliases>
     <typeAlias alias="Author" type="cn.tulingxueyuan.pojo.Author"/>
  </typeAliases>
 <typeAliases>
    <package name="cn.tulingxueyuan.pojo"/>
 </typeAliases>
 解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
 除了自定义的,还有内置的
 */
typeAliasesElement(root.evalNode("typeAliases"));

// 注册别名
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            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");
                Class<?> clazz = Resources.classForName(type);
                if (alias == null) {
                    typeAliasRegistry.registerAlias(clazz);
                } else {
                    typeAliasRegistry.registerAlias(alias, clazz);
                }
            }
        }
    }
}

2.5 插件配置

Mybatis中提供了四大类型的插件,分别作用于Executor、ParameterHandler、StatementHandler和ResultSetHandler,Executor是一个执行器,SqlSession内部真正工作的就是一个Executor对象

遍历plugins节点下所有子节点,获取这些子节点的属性值,创建一个拦截器的实例,然后把属性添加到实例中,最后把这些拦截器添加到configuration属性的拦截器链中

/**
 * 解析我们的插件(比如分页插件)
 * mybatis自带的
 * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
   ParameterHandler (getParameterObject, setParameters)
   ResultSetHandler (handleResultSets, handleOutputParameters)
   StatementHandler (prepare, parameterize, batch, update, query)
  解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
 */
pluginElement(root.evalNode("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);
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}

我们通过实现Interceptor接口来定义一个拦截器,但在类上面,需要使用@Intercepts注解来表明该拦截器的类型,以及需要拦截的方法

@Intercepts({@Signature( type= Executor.class,  method = "query", args ={
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
        })})
public class ExamplePlugin implements Interceptor {

    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("代理");
        Object[] args = invocation.getArgs();
        MappedStatement ms= (MappedStatement) args[0];
        // 执行下一个拦截器、直到尽头
        return invocation.proceed();
    }

}

2.6 数据源环境配置

在配置文件中,可以配置多个数据源,但只有一个会生效,也就是environmentsdefault指定的一个才会生效

/**
 * 解析我们的mybatis环境
   <environments default="dev">
     <environment id="dev">
       <transactionManager type="JDBC"/>
       <dataSource type="POOLED">
       	<property name="driver" value="${jdbc.driver}"/>
       	<property name="url" value="${jdbc.url}"/>
       	<property name="username" value="root"/>
       	<property name="password" value="Zw726515"/>
       </dataSource>
     </environment>
 </environments>
 *  解析到:org.apache.ibatis.session.Configuration#environment
 *  在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
 */
environmentsElement(root.evalNode("environments"));

通过transactionManagerElement()方法来解析transactionManager节点,生成一个事务工厂,用于创建事务

然后通过dataSourceElement()方法解析dataSource节点,生成一个数据库连接工厂,然后调用getDataSource()方法获得数据库连接信息,最后把这些信息,封装成一个Environment实例,设置到configuration中

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

在解析数据库事务类型和连接池类型时,都会用别名去获取具体的类,以事务为例:

我们通常使用的时候,直接指定别名就行了,获得指定的type类型后,会调用resolveClass()方法来解析对应的Class,其实在Configuration类实例化的时候,可会注册一些别名

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");  // JDBC
    Properties props = context.getChildrenAsProperties();
    TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
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);
    ……
}

2.7 数据库厂商配置

解析所有databaseIdProvider节点的属性,然后通过上面配置的数据源,创建一个数据库连接来获取数据库产品的名称(使用完就关掉),然后于节点的name属性匹配,将value值设置到configuration中

/**
 * 解析数据库厂商
 *     <databaseIdProvider type="DB_VENDOR">
          <property name="SQL Server" value="sqlserver"/>
          <property name="DB2" value="db2"/>
          <property name="Oracle" value="oracle" />
          <property name="MySql" value="mysql" />
       </databaseIdProvider>
 *  解析到:org.apache.ibatis.session.Configuration#databaseId
 */
databaseIdProviderElement(root.evalNode("databaseIdProvider"));

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

2.8 类型处理器配置

类型处理器的用途就很多了,尤其是在处理查询的结果集的时候,就需要用到类型处理器,而Mybatis也给我们提供了很多内置的类型处理器,都在Configuration的TypeHandlerRegistry实例中,在TypeHandlerRegistry实例化的时候,会添加默认的类型处理器

public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
	……
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    ……
}

自定义类型转换器需要实现TypeHandler接口,并通过@MappedJdbcTypes和@MappedTypes两个注解来指定相互转换的类型

@MappedTypes(JSONObject.class)
@MappedJdbcTypes(JdbcType.JSON)
public class JsonTypeHandler implements TypeHandler {
    ……
}

一般情况下,这些默认的类型处理器就够用了,但我们也可以自定义类型处理器,类型转换器的解析于别名配置的解析基本一样

/**
 * 解析我们的类型处理器节点
 * <typeHandlers>
      <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
    </typeHandlers>
    解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
 */
typeHandlerElement(root.evalNode("typeHandlers"));

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);
                }
            }
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值