【Mybatis源码分析 3.5.1版本】

2 篇文章 0 订阅
1 篇文章 0 订阅

前言

         mybatis中文官网地址:https://mybatis.org/mybatis-3/zh/index.html


一、Mybatis介绍

        MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 

        后面所有的代码都是Mybatis的源码!!!!!java代码!!!!!


二、Mybatis配置文件

       截图自中文官网:

         配置文件样例:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--​详细参考官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#properties-->

    <!--配置文件的路径-->
    <properties resource="mybaits.properties"></properties>
    <!--设置(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>
    <!--类型别名(typeAliases)-->
    <typeAliases>
        <typeAlias alias="Author" type="domain.blog.Author"/>
        <typeAlias alias="Blog" type="domain.blog.Blog"/>
        <typeAlias alias="Comment" type="domain.blog.Comment"/>
        <typeAlias alias="Post" type="domain.blog.Post"/>
        <typeAlias alias="Section" type="domain.blog.Section"/>
        <typeAlias alias="Tag" type="domain.blog.Tag"/>
    </typeAliases>
    <!--类型处理器(typeHandlers)
    MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型
    -->
    <typeHandlers>
        <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
    </typeHandlers>
    <!--对象工厂(objectFactory)-->
    <objectFactory type="org.mybatis.example.ExampleObjectFactory">
        <property name="someProperty" value="100"/>
    </objectFactory>
    <!--插件(plugins)-->
    <plugins>
        <plugin interceptor="org.mybatis.example.ExamplePlugin">
            <property name="someProperty" value="100"/>
        </plugin>
    </plugins>
    <!--环境配置(environments)-->
    <environments default="development">
        <environment id="development">
            <!--事务管理器(transactionManager)-->
            <transactionManager type="JDBC">
                <property name="..." value="..."/>
            </transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--数据库厂商标识(databaseIdProvider)-->
    <databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="db2"/>
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>
    <!--映射器(mappers)-->
    <!-- 使用相对于类路径的资源引用 -->
    <mappers>
        <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
        <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
        <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    <!-- 
    使用完全限定资源定位符(URL) 
    <mappers>
        <mapper url="file:///var/mappers/AuthorMapper.xml"/>
        <mapper url="file:///var/mappers/BlogMapper.xml"/>
        <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>
    使用映射器接口实现类的完全限定类名
    <mappers>
        <mapper class="org.mybatis.builder.AuthorMapper"/>
        <mapper class="org.mybatis.builder.BlogMapper"/>
        <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    将包内的映射器接口实现全部注册为映射器
    <mappers>
        <package name="org.mybatis.builder"/>
    </mappers>
    -->

</configuration>

三、SqlSessionFactory源码解析

        根据工具类创建SqlSessionFactory对象

InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);

        进入build方法

// 当前类:SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}

// 当前类:SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 把传入的Mybatis的配置文件路径,包装成一个XMLConfigBuilder对象,解析xml 
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // build方法返回一个DefaultSqlSessionFactory对象
      // parse方法用来解析xml配置文件,返回Configuration对象
      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.
      }
    }
}

// 当前类:SqlSessionFactoryBuilder
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

        进入XPathParser的parse方法

// 当前类:XPathParser
public Configuration parse() {
 // 默认时false
 if (parsed) {
  // 全局配置文件不可以重复解析
  throw new BuilderException("Each XMLConfigBuilder can only be used once.");
 }
 // 改为true
 parsed = true;
 // 解析入口,parser.evalNode("/configuration")返回XNode对象,xml文件解析成的java bean
 parseConfiguration(parser.evalNode("/configuration"));
 // 返回全局配置文件
 return configuration;
}

        进入parseConfiguration方法

// 当前类:XMLConfigBuilder
private void parseConfiguration(XNode root) {
  try {
    //解析全局配置文件的<configuration/>标签下的<properties/>标签
    //保存在Configuration的variables属性和XPathParser的variables属性里
    propertiesElement(root.evalNode("properties"));
    //解析全局配置文件的<configuration/>标签下的<setting/>标签到Properties对象里面
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    //解析全局配置文件的<configuration/>标签下的<vfsImpl/>标签,从本地和FTP加载文件 
    loadCustomVfs(settings);
    //解析全局配置文件的<configuration/>标签下的<logImpl/>标签,LOG4J,LOG4J2,SLF4J
    loadCustomLogImpl(settings);
    //解析全局配置文件的<configuration/>标签下的<typeAliases/>标签,类别名
    typeAliasesElement(root.evalNode("typeAliases"));
    //解析全局配置文件的<configuration/>标签下的<plugins/>标签,所有的插件类
    pluginElement(root.evalNode("plugins"));
    //解析全局配置文件的<configuration/>标签下的<objectFactory/>标签,对象工厂
    objectFactoryElement(root.evalNode("objectFactory"));
    //解析全局配置文件的<configuration/>标签下的<objectWrapperFactory/>标签,对象装饰类
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    //解析全局配置文件的<configuration/>标签下的<reflectorFactory/>标签,辅助性工厂类
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    ///给全局配置类configuration设置属性,没有则用默认值
    settingsElement(settings);
    //解析全局配置文件的<configuration/>标签下的<environments/>标签,多个数据源
    environmentsElement(root.evalNode("environments"));
    //解析全局配置文件的<configuration/>标签下的<databaseIdProvider/>标签,数据源标识
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    //解析全局配置文件的<configuration/>标签下的<typeHandlers/>标签,jdbcType和javaType映射类
    typeHandlerElement(root.evalNode("typeHandlers"));
    //解析全局配置文件的<configuration/>标签下的<mappers/>标签,sql所在文件,
    //支持xml文件路径(resource,url,文件下的sql标签如<select>),
    //class文件(package,class,这种格式的去解析方法上的Mybatis注解如:@Selcet),
    //最后将他们解析为MappedStatement放在全局配置类configuration的mappedStatements里面
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

         进入mapperElement(root.evalNode("mappers"))方法,讲解所有sql标签生成MappedStatement的过程

// 当前类:XMLConfigBuilder,parent为mapper标签
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 映射器<mapper>支持四种方式,<!-- 使用相对于类路径的资源引用 -->
    // <!-- 使用完全限定资源定位符(URL) --> <!-- 使用映射器接口实现类的完全限定类名 -->
    // <!-- 将包内的映射器接口实现全部注册为映射器 -->
    // 最常用的就是xml,第一种<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    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);
          // 把xml文件包装为流
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // 用解析工具类进行解析,resource="org/mybatis/builder/AuthorMapper.xml"
          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.");
        }
      }
    }
  }
}

        进入XMLMapperBuilder的parse方法

// 当前类:XMLMapperBuilder
public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    // 解析mapper的xml文件的mapper标签
    configurationElement(parser.evalNode("/mapper"));
    // 保存resource到Configuration对象loadedResources属性里面,    
    // resource="org/mybatis/builder/AuthorMapper.xml"
    configuration.addLoadedResource(resource);
    // 把所有的namespace属性值保存到Configuration对象mapperRegistry属性里面
    // <mapper namespace="test.Mapper">
    bindMapperForNamespace();
  }
  // 这几个用于解析配置文件发生异常之后,再次处理,后面会看到
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

        进入bindMapperForNamespace方法,这个方法要注意,很重要

// 当前类:org.apache.ibatis.builder.xml.XMLMapperBuilder
private void bindMapperForNamespace() {
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      //ignore, bound type is not required
    }
    if (boundType != null) {
      if (!configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:" + namespace);
        // 这里要注意,在生成Mapper对象时要用到
        configuration.addMapper(boundType);
      }
    }
  }
}

// 当前类:org.apache.ibatis.session.Configuration
public <T> void addMapper(Class<T> type) {
    // 进入这里
    mapperRegistry.addMapper(type);
}

// 当前类:org.apache.ibatis.binding.MapperRegistry
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 {
      // 在mybaits中,namespace属性值就是接口的全路径,所有在这里保存knownMappers集合中
      // 以接口class对象为key,MapperProxyFactory对象为value保存在knownMappers集合中,
      // 一个class对应一个MapperProxyFactory对象
      knownMappers.put(type, new MapperProxyFactory<>(type));
    
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

        进入configurationElement方法

// 当前类:XMLMapperBuilder
private void configurationElement(XNode context) {
  try {
    //得到namespace属性值,假如为:test.Mapper
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    //设置namespace属性,以后用namespace+id来保存MappedStatement对象
    builderAssistant.setCurrentNamespace(namespace);
    //解析mapper标签下的<cache-ref/>标签,引入其他命名空间二级缓存
    cacheRefElement(context.evalNode("cache-ref"));
    //解析mapper标签下的<cache/>标签,用来设置二级缓存,没有该标签不使用二级缓存
    cacheElement(context.evalNode("cache"));
    //解析mapper标签下的<parameterMap/>标签,不常用
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //解析mapper标签下的<resultMap/>标签
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //解析mapper标签下的<sql/>标签,重复sql提取
    sqlElement(context.evalNodes("/mapper/sql"));
    //解析mapper标签下的增删改查标签,讲解这个
    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);
  }
}

        进入buildStatementFromContext方法

// 当前类:org.apache.ibatis.builder.xml.XMLMapperBuilder
private void buildStatementFromContext(List<XNode> list) {
  // 获取设置的数据库标识
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId())
  }
  // 没有设置为null
  buildStatementFromContext(list, null);
}

        进入buildStatementFromContext方法

// 当前类:org.apache.ibatis.builder.xml.XMLMapperBuilder
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) {
      // 解析发生异常,在org.apache.ibatis.builder.xml.XMLMapperBuilder#parse方法继续处理
      // 可以看之前的XMLMapperBuilder的parse方法解析
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

        进入parseStatementNode方法

// 当前类:org.apache.ibatis.builder.xml.XMLStatementBuilder
public void parseStatementNode() {
  // 得到sql的id
  String id = context.getStringAttribute("id");
  // 数据库标识
  String databaseId = context.getStringAttribute("databaseId");
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }
  // 下面这些就是获取sql标签属性信息
  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENG
  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, builder
  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(sqlCommandTyp
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterType
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statem
  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");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");

  // 利用这个方法将xml的sql数据转换为MappedStatement对象,参数是sql的一些基本信息
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

        进入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) {
  if (unresolvedCacheRef) {
    throw new IncompleteElementException("Cache-ref not yet resolved");
  }
  id = applyCurrentNamespace(id, false);
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  // 利用构建者模式,构建MappedStatement对象
  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(valueOrDefault(flushCache, !isSelect))
      .useCache(valueOrDefault(useCache, isSelect))
      .cache(currentCache);
  ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  if (statementParameterMap != null) {
    statementBuilder.parameterMap(statementParameterMap);
  }
  // 返回一个MappedStatement对象,完成xml的sql转换为MappedStatement的过程
  MappedStatement statement = statementBuilder.build();
  // 保存在Configuration配置类的mappedStatements属性里面
  configuration.addMappedStatement(statement);
  return statement;
}

        到这SqlSessionFactory的源码就解析完了,主要就是把xml的配置信息进行java对象化,生成一个全局的配置对象Configuration,然后保存在DefaultSqlSessionFactory对象里面,然后返回就完了。


四、SqlSession源码解析

        上面介绍了SqlSessionFactory的源码,那么接下来就是根据SqlSessionFactory得到SqlSession对象了。

SqlSession session = sqlSessionFactory.openSession();

        进入openSession方法

// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
public SqlSession openSession() {
    // 参数:默认的执行器,事务管理器,是否自动提交
    // 具体的执行器这些参数,可以查看那个中文文档进行了解
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  // 事务管理器对象
  Transaction tx = null;
  try {
    // 得到环境变量对象
    final Environment environment = configuration.getEnvironment();
    // 事务管理器工厂对象
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // 创建一个事务管理器
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // 创建执行器
    final Executor executor = configuration.newExecutor(tx, execType);
    // 返回一个DefaultSqlSession,包含全局配置文件,执行器,和是否自动提交标志
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

        进入newExecutor方法

// 当前类:org.apache.ibatis.session.Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  // 根据配置的执行器类型,得到一个执行器对象,执行器的知识参考mybaits中文文档
  if (ExecutorType.BATCH == executorType) {
    //批量执行器
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    //复用Statement对象的执行器
    executor = new ReuseExecutor(this, transaction);
  } else {
    //简单执行器
    executor = new SimpleExecutor(this, transaction);
  }
  // 如果cacheEnabled属性为true,对执行器进行包装,cacheEnabled看中文官网的settings属性介绍
  if (cacheEnabled) {
    // 装饰器模式,带有缓存功能的执行器
    executor = new CachingExecutor(executor);
  }
  // 对执行器进行插件处理,就是对它功能进行增强,具体看中文官网的plugins属性介绍
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

        进入pluginAll方法,这里对插件的功能介绍之后,在后面对ParameterHandler,ResultSetHandler和StatementHandler 对象进行插件处理时,就不多介绍了,都是一样的逻辑。

// 当前类:org.apache.ibatis.plugin.InterceptorChain
public Object pluginAll(Object target) {
  // interceptors就是实现插件增强的全部实现类的集合,具体在中文官网看plugins插件配置的介绍
  for (Interceptor interceptor : interceptors) {
    // 采用责任链模式进行,层层代理,使得执行器的功能更多
    target = interceptor.plugin(target);
  }
  // 返回代理的执行器对象,赋值给DefaultSqlSession对象
  return target;
}

             插件实现的样例,对官网提供的代码进行改造

package test;

import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;

import java.util.Properties;
import java.util.concurrent.Executor;

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

    private Properties properties = new Properties();
    public Object intercept(Invocation invocation) throws Throwable {
        // implement pre processing if need
        System.out.println("我是执行器,我要开始工作了。。。。。。");
        Object returnObject = invocation.proceed();
        // implement post processing if need
        System.out.println("我是执行器,我工作结束了,下班。。。。。。");
        return returnObject;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

          进入plugin方法,假设目前只有上面这一个执行器的插件

// 当前类:test.ExamplePlugin,就是上面的那个样例类
public Object plugin(Object target) {
    // 进入wrap方法,target执行器对象:CachingExecutor,this插件对象:ExamplePlugin
    return Plugin.wrap(target, this);
}

        进入wrap方法

// 当前类:org.apache.ibatis.plugin.Plugin
// 这个里面的getSignatureMap和getAllInterfaces方法可以多看看很有意思
public static Object wrap(Object target, Interceptor interceptor) {
  // 得到当前插件拦截器的注解信息,取到它拦截的什么类和拦截的什么方法,注解格式    
  // @Intercepts({@Signature(type = Executor.class,method = "query",
  // args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  Class<?> type = target.getClass();
  // 得到当前对象和其父类,满足插件拦截的所有类
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  // 如果当前对象满足当前插件的拦截
  if (interfaces.length > 0) {
    // 返回一个JDK代理对象
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  // 如果当前对象不满足,当前插件的拦截条件就返回,让下一个插件继续处理
  return target;
}

        JDK代理的对象,在对象执行方法时,会进入实现了InvocationHandler接口的invoke方法,这是JDK的知识,可以自己去学习。进入Plugin的invoke方法。

// 当前类:org.apache.ibatis.plugin.Plugin
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 得到当前执行方法的类对象,查询该对象满足的所有拦截的方法
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    // 包括当前执行的方法
    if (methods != null && methods.contains(method)) {
      // 就执行插件的intercept方法,把当前对象,方法和参数传入,intercept假设为ExamplePlugin
      return interceptor.intercept(new Invocation(target, method, args));
    }
    // 没有或者不包括当前执行的方法,就直接执行该方法
    return method.invoke(target, args);
  } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
  }

        进入ExamplePlugin的intercept方法

package test;

import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;

import java.util.Properties;
import java.util.concurrent.Executor;

@Intercepts({@Signature(
        type= Executor.class,
        method = "update",
        args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
    
    private Properties properties = new Properties();
    // 这个就是我们自定义的插件实现类
    public Object intercept(Invocation invocation) throws Throwable {
        // implement pre processing if need
        System.out.println("我是执行器,我要开始工作了。。。。。。");
        // 等插件增强后,执行目标方法,Invocation的代码在下面
        Object returnObject = invocation.proceed();
        // implement post processing if need
        System.out.println("我是执行器,我工作结束了,下班。。。。。。");
        return returnObject;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

package org.apache.ibatis.plugin;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author Clinton Begin
 */
public class Invocation {

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    // 就是执行原来的方法本身
    return method.invoke(target, args);
  }

}

        插件的实现就是JDK动态代理,我认为Mybatis是最开始学习源码的最佳之选,因为它通俗易懂。

五、Mapper源码解析

        得到SqlSession对象之后,我们就可以得到Mapper对象了,因为Mapper是一个接口,所有肯定是一个代理对象,不知道JDK代理的原理是看不懂的。

Mapper mapper = session.getMapper(Mapper.class);
int i = mapper.selectCount("1");

         进入getMapper方法

// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSession
public <T> T getMapper(Class<T> type) {
    // configuration全局配置对象,就是在生成DefaultSqlSession对象,通过构造方法传入的
    return configuration.getMapper(type, this);
}

// 当前类:org.apache.ibatis.session.Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 根据mapperRegistry得到代理对象
    return mapperRegistry.getMapper(type, sqlSession);
}

        进入getMapper方法

// 当前类:org.apache.ibatis.binding.MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  // 看之前SqlSessionFactory源码解析的bindMapperForNamespace方法
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    // 生成代理
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

        进入newInstance方法

// 当前类:org.apache.ibatis.binding.MapperProxyFactory
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 生成的代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
     // 生成一个MapperProxy对象,包含SqlSession,Mapper接口对象,ConcurrentHashMap
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // this的newInstance方法
    return newInstance(mapperProxy);
  }

}

        既然用JDK代理,我门就要进入MapperProxy的invoke方法了

// 当前类:org.apache.ibatis.binding.MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 如果是Object的方法直接执行
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    // JDK1.8的新特性 接口中可以定义默认方法
    } else if (isDefaultMethod(method)) {
      // 这里没有深入研究过
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 这个很有意思
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

        进入cachedMapperMethod

// 当前类:org.apache.ibatis.binding.MapperProxy
private MapperMethod cachedMapperMethod(Method method) {
    // methodCache是一个Map<Method, MapperMethod>集合
    // 把当前method对象,和包装method的MapperMethod对象保存起来
    return methodCache.computeIfAbsent(method, 
        k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}

// 当前类:org.apache.ibatis.binding.MapperMethod
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  // 这个方法是根据传入的参数,从MappedStatements中获取一个MappedStatement对象,
  // 包含了sql语句的主要信息,包装为SqlCommand对象
  this.command = new SqlCommand(config, mapperInterface, method);
  // 这个就是将传入的信息,封装在一个内部类里面,Mapper接口和全局配置文件信息
  this.method = new MethodSignature(config, mapperInterface, method);
}

// 当前类:org.apache.ibatis.binding.MapperMethod
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  final String methodName = method.getName();
  final Class<?> declaringClass = method.getDeclaringClass();
  // ms对象,可以看DefaultSqlSessionFactory源码解析
  MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
      configuration);
  if (ms == null) {
    if (method.getAnnotation(Flush.class) != null) {
      name = null;
      type = SqlCommandType.FLUSH;
    } else {
      throw new BindingException("Invalid bound statement (not found): "
          + mapperInterface.getName() + "." + methodName);
    }
  } else {
    name = ms.getId();
    type = ms.getSqlCommandType();
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}

        进入mapperMethod.execute(sqlSession, args)方法

// 当前类:org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  // 根据不同的sql类型进行处理
  switch (command.getType()) {
    case INSERT: {
      // 把参数进行处理
      Object param = method.convertArgsToSqlCommandParam(args);
      // result为sql执行的结果,进入insert方法,command.getName()为id,
      // id=namespace+id(sql标签的id),用来从mappedStatements取ms对象
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName()
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
}
private Object rowCountResult(int rowCount) {
  final Object result;
  if (method.returnsVoid()) {
    result = null;
  } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
    result = rowCount;
  } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
    result = (long)rowCount;
  } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
    result = rowCount > 0;
  } else {
    throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
  }
  return result;
}

        进入insert方法

// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSession
public int insert(String statement, Object parameter) {
  // statement为id,parameter为参数
  return update(statement, parameter);
}

        进入update方法

// 当前类:org.apache.ibatis.session.defaults.DefaultSqlSession
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    // 取ms对象,很简单的代码,不看了
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 执行器executor,一般为CachingExecutor而且被插件增强的执行器代理对象
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

        进入执行器的update方法

// 当前类:org.apache.ibatis.executor.CachingExecutor
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  // 修改操作,要删除二级缓存
  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}

        进入delegate.update(ms, parameterObject)方法

// 当前类:org.apache.ibatis.executor.BaseExecutor
public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  // 清除一级缓存,为什么是一级,因为我们现在讲解的insert语句,可以看select语句的执行就知道了
  clearLocalCache();
  return doUpdate(ms, parameter);
}

        进入doUpdate方法

// 当前类:org.apache.ibatis.executor.SimpleExecutor
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    //返回一个StatementHandler对象,工具类
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    //返回Statement对象,很简单
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 插入数据
    return handler.update(stmt);
  } finally {
    closeStatement(stmt);
  }
}

// 当前类:org.apache.ibatis.executor.statement.PreparedStatementHandler
public int update(Statement statement) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  // 接下来就是jdbc的代码了
  ps.execute();
  int rows = ps.getUpdateCount();
  Object parameterObject = boundSql.getParameterObject();
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
  return rows;
}

        进入newStatementHandler方法

// 当前类:org.apache.ibatis.session.Configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
  // 返回StatementHandler,构造方法如下,根据不同策略生成StatementHandler对象
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  // 这里就是对statementHandler进行插件的增强,和Executor哪里一模一样
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

// 当前类:org.apache.ibatis.executor.statement.RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

  switch (ms.getStatementType()) {
    case STATEMENT:
      delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case PREPARED:
      // 假如是这个对象
      delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case CALLABLE:
      delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    default:
      throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
  }
}

        进入PreparedStatementHandler的构造方法

// 当前类:org.apache.ibatis.executor.statement.PreparedStatementHandler
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}

        进入父类的构造方法

// 当前类:org.apache.ibatis.executor.statement.BaseStatementHandler
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  this.configuration = mappedStatement.getConfiguration();
  this.executor = executor;
  this.mappedStatement = mappedStatement;
  this.rowBounds = rowBounds;

  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  this.objectFactory = configuration.getObjectFactory();
  if (boundSql == null) { // issue #435, get the key before calculating the statement
    generateKeys(parameterObject);
    boundSql = mappedStatement.getBoundSql(parameterObject);
  }
  this.boundSql = boundSql;
  // 这里生成ParameterHandler的代理对象,插件进行增强的
  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  // 这里生成ResultSetHandler的代理对象,插件进行增强的
  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

        进入newParameterHandler方法

// 当前类:org.apache.ibatis.session.Configuration
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  // 和其他插件的处理一模一样
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}

        到这里我们就看到了一个sql执行的全过程,也看到了插件的处理原理,下面是官网原话。

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

总结

        Mybaits的源码就分析完了,相对于Spring来说,它的源码很通俗易懂,特别适合准备开始研究源码的人阅读。祝愿大家能在源码中学到更多的知识和思想。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis-Plus 3.5.1 提供了一个代码生成器,可以帮助我们快速生成 MyBatis-Plus 的实体类、Mapper 接口、Mapper XML 文件等。 使用步骤: 1. 引入 MyBatis-Plus 3.5.1 的代码生成器依赖: ```xml <!-- MyBatis-Plus 3.5.1 的代码生成器依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.1</version> </dependency> ``` 2. 编写代码生成器配置文件: ```properties # 数据库配置 spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=root spring.datasource.password=root # 代码生成器配置 # 要生成的表名,多个表名用英文逗号分隔 generator.table-names=user,role,permission # 生成代码的包名 generator.package-name=com.example.mybatisplus # 生成代码的作者 generator.author=MyBatis-Plus # 是否覆盖已有文件 generator.file-overwrite=true # 是否在控制台输出日志 generator.log-console=true ``` 3. 编写代码生成器启动类: ```java import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; public class CodeGenerator { public static void main(String[] args) { // 数据源配置 DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"); dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver"); dataSourceConfig.setUsername("root"); dataSourceConfig.setPassword("root"); // 全局配置 GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java"); globalConfig.setAuthor("MyBatis-Plus"); globalConfig.setOpen(false); // 包名配置 PackageConfig packageConfig = new PackageConfig(); packageConfig.setParent("com.example.mybatisplus"); packageConfig.setEntity("model"); packageConfig.setMapper("mapper"); packageConfig.setXml("mapper.xml"); // 策略配置 StrategyConfig strategyConfig = new StrategyConfig(); strategyConfig.setNaming(NamingStrategy.underline_to_camel); strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel); strategyConfig.setEntityLombokModel(true); strategyConfig.setRestControllerStyle(true); strategyConfig.setInclude("user", "role", "permission"); // 代码生成器 AutoGenerator autoGenerator = new AutoGenerator(); autoGenerator.setDataSource(dataSourceConfig); autoGenerator.setGlobalConfig(globalConfig); autoGenerator.setPackageInfo(packageConfig); autoGenerator.setStrategy(strategyConfig); autoGenerator.execute(); } } ``` 4. 运行代码生成器启动类,即可生成实体类、Mapper 接口、Mapper XML 文件等。生成的文件会保存在指定的包名下。 注意事项: 1. 要生成的表名需要在 generator.table-names 属性中配置,多个表名用英文逗号分隔。 2. 要生成的代码包名需要在 PackageConfig 中配置。 3. 要生成的代码作者需要在 GlobalConfig 中配置。 4. 要生成的代码是否覆盖已有文件、是否在控制台输出日志等需要在 GlobalConfig 中配置。 5. 策略配置中的 NamingStrategy、entityLombokModel、restControllerStyle 等属性可以根据需求进行配置。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程界~小卡拉米

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值