吃透mybatis源码-mybatis初始化(代码片段)

墨家巨子@俏如来 墨家巨子@俏如来     2022-11-29     259

关键词:

来来来,给俏如来扎起。感谢老铁们对俏如来的支持,2021一路有你,2022我们继续加油!你的肯定是我最大的动力

博主在参加博客之星评比,点击链接 , https://bbs.csdn.net/topics/603957267 疯狂打Call!五星好评 ⭐⭐⭐⭐⭐ 感谢。


前言

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

同时Mybatis也是面试过程中被高频问到的一门技术,今天我就带大家一起来对Mybatis的重要原理及其源码进行一个分析。

Mybatis的入门案例

我们需要先写一个简单的入门案例,根据入门来分析Mybatis的执行原理

第一步:我们需要导入Mybatis的基础依赖

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.4.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.47</version>
</dependency>

第二步:然后准备一个mybatis的xml配置文件

<?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>
    <properties resource="db.properties"></properties>
    <!--导入参数来源-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--用于生成可视的sql命令-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
            <!--如果下面这种,则自动扫描包pojo下的注解类的别名-->
        <package name="cn.whale.domian"></package>
    </typeAliases>
    <environments default="development">
        <environment id="development"><!--环境-->
            <transactionManager type="JDBC"/><!--事务管理器-->
            <dataSource type="POOLED"><!--数据源-->
                <property name="driver" value="$mysql.driver"/>
                <property name="url" value="$mysql.url"/>
                <property name="username" value="$mysql.username"/>
                <property name="password" value="$mysql.password"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/StudentMapper.xml"/>
    </mappers>
</configuration>

数据库配置文件如下

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/test?useSSL=false
mysql.username=root
mysql.password=admin

第三步:然后编写SQL映射文件StudnetMapper.xml , 先来个简单的查询

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.whale.mapper.StudentMapper">

    <resultMap id="resultMap" type="cn.whale.domian.Student">
        <id column="id" property="id"/>
        <result column="username" property="username" />
    </resultMap>

    <select id="selectAll" resultType="cn.whale.domian.Student">
        select * from student
    </select>

    <select id="selectById" resultMap="resultMap">
        select id,username from student
    </select>
</mapper>

第四步:编写实体类,和mapper映射器接口

public interface StudentMapper 
    List<Student> selectAll();
    Student selectById(Long id);


第五步:编写测试类

public class StudentTest 

    @Test
    public void test() throws IOException 
        //加载配置
        InputStream inputStream= Resources.getResourceAsStream("mybatis-config.xml");
        //创建一个sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        
        Student student = sqlSession.selectOne("cn.whale.mapper.StudentMapper.selectAll",1L);
        System.out.println(student);

        //使用最原始方式: namespace.statementid 执行
        List<Student>  objects = sqlSession.selectList("cn.whale.mapper.StudentMapper.selectAll");
        objects.stream().forEach( System.out::println);
        
  		//使用mapper映射器接口方式执行
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectAll();
        students.stream().forEach( System.out::println);
    



入门案例开发完毕。

Mybatis执行流程

下面是Mybatis的组成结构图,可能你看起来会有点懵逼,但是没关系,每个组件我们都会在后面说道,这里先混个眼熟就行了

这里说几个比较重要的组件

  • SqlSessionFactoryBuilder : 用到了建造者模式,用来解析配置文件和创建SqlSessionFactory
  • SqlSessionFactory : 用来创建SqlSession的工厂类,全局唯一。
  • SqlSession : 这个是MyBatis链接数据库的一次会话对象,如果Mybatis和数据库的链接中断,会话也就结束。所以SqlSession不应该是单利的。
  • Configuration :配置对象,主要包括mybatis-config.xml中的配置项目
  • Executor : 执行器,SqlSesion底层执行的时候用到

然后我们先来分析一下Mybatis的执行流程,根据上面的测试案例我们可以分为两部分。创建SqlSession和执行SqlSession。首先第一行代码 :InputStream inputStream= Resources.getResourceAsStream("mybatis-config.xml")
的作用是加载Mybatis配置文件,然后通过SqlSessionFactoryBuilder().build解析配置文件,以及mapper.xml 映射文件,并创建一个 SqlSessionFactory

然后是通过 sqlSessionFactory.openSession() 创建SqlSession,SqlSession是和数据库的一次会话对象。然后是通过sqlSession.selectList来执行语句,可以通过“namespace”加上“statementid”来找到要执行的SQL,也可以通过获取Mapper映射器接口的方式来执行,第二种方式最终会采用第一种方式去执行SQL。

执行完SQL会有结果集,然后会调用结果映射器处理结果集并返回,下面这个图是宏观层面的,Mybatis执行流程

Mybatis执行流程如下

  1. 初始化阶段,加载配置文件
  2. 根据配置文件,创建SqlSessionFactoryBuider,执行build方法来创建SqlSessionFactory,build方法会解析配置文件,然后封装到一个Configuration对象中。Configuration会保存在创建的SqlSessionFactory
  3. 通过SqlSessionFactory来创建SqlSesion,底层会创建一个Executor执行器保存在SqlSession中
  4. 然后就是SqlSesson的执行了,SqlSession会调用 executor 执行器去执行
  5. 执行器中会创建一个StatementHandler,调用StatementHandler去执行Statement语句,当然执行Statement语句前涉及到参数的处理
  6. 执行完成之后使用ResultSetHandler映射结果为实体对象并返回

源码解析-SqlSessionFactory的创建

代码入手肯定是new SqlSessionFactoryBuilder().build(inputStream),直接看源码见:org.apache.ibatis.session.SqlSessionFactoryBuilder#build(j…)

 public SqlSessionFactory build(InputStream inputStream) 
   return build(inputStream, null, null);
 
 
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) 
    try 
    	//1. 创建一个XML配置的Builder,inputStream是通过Resources.getResourceAsStream加载的xml配置
    	//environment 和 properties都是null
    	//这一步会创建一个 Configuration对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //2. parser.parse()解析一个configuration对象,然后创建SqlSessionFactory 
      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.
      
    
  

Mybatis的源码相比Spring来说要简单太多了,首先是通过Resources.getResourceAsStream加载的xml配置(inputStream)交给 XMLConfigBuilder ,创建一个XML解析器在new XMLConfigBuilder(…)代码中会创建Configuration对象。然后调用parser.parse() 解析配置,解析之后得到一个 Configuration 对象,交给builder方法去创建一个默认的DefaultSqlSessionFactory,同时把Configuraton对象保存给SqlSessionFactory

我们先来看parser.parse()方法的源码,然后再看build方法的源码,见org.apache.ibatis.builder.xml.XMLConfigBuilder#parse

  public Configuration parse() 
  	//parsed是用来判断是否已经解析过了
    if (parsed) 
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    
    parsed = true;
    //解析配置,使用的是XPathParser解析器来解析一个configuration根节点。
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  

使用xPathParser解析器来解析配置文件,/configuration 就是mybatis-config.xml配置中的根节点,继续看parseConfiguration方法的源码

 //解析配置项目
  private void parseConfiguration(XNode root) 
    try 
      //issue #117 read properties first
      //1.拿到properties元素,也就是  <properties resource="db.properties"></properties>
      propertiesElement(root.evalNode("properties"));
      //2.处理 <settings> 元素,并设置到Properties 对象中
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //3.处理  <typeAliases> 用户配置的别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //4.处理 <plugins> 插件
      pluginElement(root.evalNode("plugins"));
      //5.处理  <objectFactory type=""></objectFactory> 对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //用来装饰object的工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.处理反射工厂<reflectorFactory type=""/>
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //把 settings  设置到COnfiguration对象中
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.处理 <environments default="development"> , 数据源就在这个元素里面
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //8.处理     <typeHandlers></typeHandlers> 类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //9 处理mappers 映射文件
      mapperElement(root.evalNode("mappers"));
     catch (Exception e) 
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    
  

这里我们看到,parseConfiguration方法对于mybatis配置文件中的所有项目都做了解析。我大致说一下每个步骤做了啥,具体的细节水友自己去断点看

  1. propertiesElement(root.evalNode(“properties”)) : 解析 ,加载db.properties中的配置项目,然后会把properties设置到Configuration的variables存储起来

  2. Properties settings = settingsAsProperties(root.evalNode(“settings”)); 处理 元素,转换为Properties ,然后通过 settingsElement(settings); 方法把settings中的配置保存到Configfiguration。见org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsElement

  3. typeAliasesElement(root.evalNode(“typeAliases”)); :处理别名配置,如果是通过<package>方式配置别名,会把配置的实体类的class添加到Configuration对象的TypeAliasRegistry别名注册器中,TypeAliasRegistry内部维护了一个new HashMap<String, Class<?>>() ,就是以类的简单名字小写为key,以实体类的class为值来映射别名到HashMap。见org.apache.ibatis.type.TypeAliasRegistry
    如果是通过<typeAlias type="" 方式直接配置别名,那么就会注册到 BaseBuilderTypeAliasRegistry中。
    在Configuration对象的构造器中默认映射了很多的内置类型的别名。

  4. pluginElement(root.evalNode(“plugins”)); 处理插件配置,该方法会解析拦截器配置比如<plugin interceptor="cn.whale.interceptor.MyInterceptor" ></plugin>,然后使用反射创建实例,保存到Configuration的InterceptorChain interceptorChain 拦截器执行链中,这里使用到了责任链模式。拦截器我们后面单独写一篇文章来说。

  5. objectFactoryElement(root.evalNode(“objectFactory”)); 对象工厂处理解析,默认情况Mybaits在映射结果的时候会使用 DefaultObjectFactory 工厂利用反射来实例化实体类,你可以通过 <objectFactory type=""></objectFactory> 配置来创建自定义的实例化工厂。指定自己的实例化方式。该工厂类也是保存到configuration对象的ObjectFactory objectFactory 字段上。

  6. environmentsElement(root.evalNode(“environments”)); 环境处理,<environments default="development">中主要包括事务 <transactionManager type="JDBC"/><dataSource type="POOLED">数据源的配置。environmentsElement方法中会根据type=JDBCTypeAliasRegistry中找到一个JdbcTransactionFactory事务工厂,使用反射创建实例,根据type="POOLED"找到一个PooledDataSource,使用反射创建实例,最后创建一个 Environment 环境对象,把 JdbcTransactionFactory 事务工厂 和PooledDataSource连接池保存到Environment中

  7. typeHandlerElement(root.evalNode(“typeHandlers”)); 处理类型处理器<typeHandlers></typeHandlers>,typeHandler是用来处理数据库的类型到Java的类型转换的,在 TypeHandlerRegistry的构造器中初始化了内置的很多的类型处理器,比如:VARCHAR类型转换为String就需要用到内置的 StringTypeHandler 处理器。当然我们也可以定义我们自己的类型处理器。

  8. mapperElement(root.evalNode(“mappers”)); 处理mapper映射文件,比如:<mapper resource="mapper/StudentMapper.xml"/>,方法内部会构建 XMLMapperBuilder 去解析xml文件,见org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

      public void parse() 
          //解析 cache , cache,parameterMap,resultMap,sql,select|insert|update|delete
          //把SQL元素解析后封装成一个MappedStatement,存储到Configuration中的Map<String, MappedStatement>中
          if (!configuration.isResourceLoaded(resource)) 
            configurationElement(parser.evalNode("/mapper"));
            configuration.addLoadedResource(resource);
            //绑定Mapper接口
            bindMapperForNamespace();
          
      
          parsePendingResultMaps();
          parsePendingCacheRefs();
          parsePendingStatements();
    
    

    configurationElement方法底层会解析到mapper.xml文件,包括: cache,parameterMap,resultMap,sql,select | insert | update | delete等元素的解析。然后把SQL语句封装成MappedStatement,以 namespace.id 为 key把MappedStatement映射到Configuration中的Map<String, MappedStatement>中。
    bindMapperForNamespace();方法中,会根据namespace找到mapper映射器接口,然后添加到 MapperRegistry中。

     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);
            configuration.addMapper(boundType);
          
        
      
    
    
    

    MapperRegistry中维护了一个HashMap<Class<?>, MapperProxyFactory<?>>() ,key是mapper接口的class 类型,Value是一个MapperProxyFactory工厂,工厂中维护了mapper接口和接口中的方法如下

    public class MapperProxyFactory<T> 
      //mapper接口
    private final Class<T> mapperInterface;
    //接口中的方法
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
    

源码解析-SqlSession的创建

接下来我们分析 sqlSessionFactory.openSession(); ,该方法是通过SqlSessionFactory创建SqlSession见:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()

@Override
  public SqlSession openSession() 
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  

这里重configuration中获取默认的executor类型默认是simple,事务的提交方式指定为手动提交,然后交给 openSessionFromDataSource 去处理

// execType :执行器的类型  ; autoCommit :事务提交方式为手动提交
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) 
    Transaction tx = null;
    try 
      //1.从Configuration拿到配置对象,配置对象中包括 JdbcTranscationFactory 和 PooledDataSource
      final Environment environment = configuration.getEnvironment();
      //从environment总获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //2.通过事务工厂transactionFactory创建Transaction事务对象,默认实现是JdbcTransaction
      //JdbcTransaction中包括了Connection连接对象;DataSource 数据源;autoCommmit提交方式为false
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //3.根据execType创建执行器,默认是SimpleExecutor,
      //SimpleExecutor继承了BaseExecutor,其中中维护了Transcation对象和PerpetualCache一级缓存一级configuration
      //SimpleExecutor被包装到 CachingExecutor 总,这里使用了装饰者模式
      //然后会把Executor添加到Configuration的interceptorChain拦截器链中
      final Executor executor = configuration.newExecutor(tx, execType);
      //4.创建SqlSession ,默认实现是DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
     catch (Exception e) 
      //5.发生异常,关闭事务
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
     finally 
      ErrorContext.instance().reset();
    
  

总结一下上面的方法的核心业

吃透mybatis源码-通过分析pagehelper源码来理解mybatis的拦截器

2021一路有你,2022我们继续加油!你的肯定是我最大的动力博主在参加博客之星评比,点击链接,https://bbs.csdn.net/topics/603957267疯狂打Call!五星好评⭐⭐⭐⭐⭐感谢前言面试官:用过pagehelper做分页把,... 查看详情

mybatis源码学习配置文件初始化(代码片段)

mybatis是项目中常用到的持久层框架,今天我们学习下mybatis,随便找一个例子可以看到通过读取配置文件建立SqlSessionFactory,然后在build拿到关键的sqlsession,这是我从网上随便找了下例子,  关键的方法在于newSqlSessionFactoryBuil... 查看详情

mybatis源码分析-内置数据源(代码片段)

文章目录1.简介2.内置数据源初始化过程3.UnpooledDataSource3.1初始化数据库驱动3.2获取数据库连接4.PooledDataSource4.1辅助类介绍4.2获取连接4.3回收连接4.4小节5.总结序号内容链接1MyBatis源码分析-MyBatis入门https://thinkwon.blog.csdn.net/article/det... 查看详情

mybatis源码学习-类型转换(typehandlerregistry)(代码片段)

历史文章:Mybatis源码学习(8)-类型转换(TypeHandler)定义TypeHandler后,Mybatis还需要对这些TypeHandler进行管理,Mybatis是通过TypeHandlerRegistry来实现TypeHandler的管理的。TypeHandlerRegistry的初始化是在Configurati 查看详情

mybatis源码——日志errorcontext类

...程序执行量增加(例如:xml文件中sql报错,那么一定会有初始化、xml加载等信息出现);  (2)日志输出的顺序,是按顺序且隔离的; 二、附上源代码 packageorg.apache.ibatis.executor;/***@authorClintonBegin*/publicclassErrorCon 查看详情

mybatis源码学习配置文件初始化

mybatis是项目中常用到的持久层框架,今天我们学习下mybatis,随便找一个例子可以看到通过读取配置文件建立SqlSessionFactory,然后在build拿到关键的sqlsession,这是我从网上随便找了下例子,关键的方法在于newSqlSessionFactoryBuilder().build(... 查看详情

mybatis源码学习-类型转换(typehandlerregistry)(代码片段)

...ypeHandlerRegistry来实现TypeHandler的管理的。TypeHandlerRegistry的初始化是在Configuration中,Configuration包含一个privatefinal字段,直接初始化了TypeHandlerRegistry,而TypeHandlerRegistry的初始化阶段,会构建JDBC数据类型、Java数据类... 查看详情

mybatis工作原理(含部分源码)

MyBatis的初始化1、读取配置文件,形成InputStreamStringresource="mybatis.xml";//加载mybatis的配置文件(它也加载关联的映射文件)InputStreaminputStream=null;try{inputStream=Resources.getResourceAsStream(resource);}catch(IOExceptione){e.pr 查看详情

框架-mybatis源码一步步深入

简介上一章我们大概了解了MyBatis初始化过程,本章主要了解SqlSessionFactoryBuilder、Configuration,它是构建SqlSessionFactory的主要工具,所有MyBatis配置信息都可以在Configuration中找到,SqlSessionFactoryBuilder的主要作用作用就是构建Configuratio... 查看详情

mybatis源码解析之执行sql语句

作者:郑志杰mybatis操作数据库的过程//第一步:读取mybatis-config.xml配置文件InputStreaminputStream=Resources.getResourceAsStream("mybatis-config.xml");//第二步:构建SqlSessionFactory(框架初始化)SqlSessionFactorysqlSessionFactory=newSqlS 查看详情

一文吃透springboot整合mybatis-plus(保姆式教程)(代码片段)

...:国学周更-心性养成之路🥭本文内容:一文吃透SpringBoot整合mybatis-plus(保姆式教程)文章目录手动整合mybatis-plus详解1、引入依赖2、创建基本目录结构3、配置application.yml4、在entity包下创建实体类5、创建Mapper... 查看详情

利刃出鞘,mybatis初始化(代码片段)

文章目录一、前言二、MyBatis的初始化做了什么2.1Mybatis的初始化过程就是加载自己运行时所需要的配置信息的过程2.2Mybatis的配置信息有哪些2.3mybatis-config.xml与Configuration类2.4MyBatis初始化的两种方式三、MyBatis基于XML配置文件创建Con... 查看详情

mybatis深入理解-----mybatis初始化机制详解(代码片段)

对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外。本章将通过以下几点详细介绍MyBatis的初始化过程。一、MyBatis的初始化做了什么1、configuration任何框架的初始化,无非是加载自己运行时所需要的配置信息... 查看详情

mybatis源码:mybatis配置解析(代码片段)

  Mybatis有两个核心配置,全局配置会影响Mybatis的执行;Mapper配置定义了查询的SQL,下面我们来看看Mybatis是如何加载配置文件的。  本文基于Mybatis源码(一):源码编译准备中案例进行分析,主要示例代码如下:publicstaticvoidma... 查看详情

了解mybatis源码手写mybatis(代码片段)

一:mybatis概述  MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java... 查看详情

mybatis源码分析(代码片段)

MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以对配置和原生Map使用简单的XML或注解,将接口和Java的POJOs(PlainOldJavaObjects,普通的Java... 查看详情

mybatis源码阅读:mybatis基础模块-反射模块(代码片段)

一、概述  MyBatis在进行参数处理、结果集映射等操作时会使用到大量的反射操作,Java中的反射功能虽然强大,但是代码编写起来比较复杂且容易出错,为了简化反射操作的相关代码,MyBatis提供了专门的反射模... 查看详情

mybatis之builder包处理mybatis初始化(代码片段)

mybatis之builder包处理mybatis初始化mybatis有两种xml的文件,一种是Mybatis-config.xml负责配置mybatis的各种配置以及指定映射文件;一种是xml映射文件负责sql。其中配置文件有很多子节点,各自有各自的功能,xml也有多种子节点... 查看详情