天天看點

MyBatis核心元件介紹二-元件一

作者:小雨在進步

前面介紹了如何使用MyBatis操作資料庫,我們接觸到MyBatis比較核心的一個元件——SqlSession。SqlSession是MyBatis提供的面向使用者的操作資料庫API。那麼MyBatis底層是如何工作的呢?為了解開MyBatis的神秘面紗,我們需要了解一下MyBatis的其他幾個比較核心的元件及這些元件的作用。

MyBatis核心元件介紹二-元件一

這些元件的作用如下:

Configuration:用于描述MyBatis的主配置資訊,其他元件需要擷取配置資訊時,直接通過Configuration對象擷取。除此之外,MyBatis在應用啟動時,将Mapper配置資訊、類型别名、TypeHandler等注冊到Configuration元件中,其他元件需要這些資訊時,也可以從Configuration對象中擷取。

MappedStatement:MappedStatement用于描述Mapper中的SQL配置資訊,是對Mapper XML配置檔案中<select|update|delete|insert>等标簽或者@Select/@Update等注解配置資訊的封裝。

SqlSession:SqlSession是MyBatis提供的面向使用者的API,表示和資料庫互動時的會話對象,用于完成資料庫的增删改查功能。SqlSession是Executor元件的外觀,目的是對外提供易于了解和使用的資料庫操作接口。

Executor:Executor是MyBatis的SQL執行器,MyBatis中對資料庫所有的增删改查操作都是由Executor元件完成的。

StatementHandler:StatementHandler封裝了對JDBC Statement對象的操作,比如為Statement對象設定參數,調用Statement接口提供的方法與資料庫互動,等等。

ParameterHandler:當MyBatis架構使用的Statement類型為CallableStatement和PreparedStatement時,ParameterHandler用于為Statement對象參數占位符設定值。

ResultSetHandler:ResultSetHandler封裝了對JDBC中的ResultSet對象操作,當執行SQL類型為SELECT語句時,ResultSetHandler用于将查詢結果轉換成Java對象。

TypeHandler:TypeHandler是MyBatis中的類型處理器,用于處理Java類型與JDBC類型之間的映射。它的作用主要展現在能夠根據Java類型調用PreparedStatement或CallableStatement對象對應的setXXX()方法為Statement對象設定值,而且能夠根據Java類型調用ResultSet對象對應的getXXX()擷取SQL執行結果。

了解了MyBatis的核心元件後,我們再來了解一下使用MyBatis操作資料庫的過程。前面介紹MyBatis架構的基本使用時,我們使用到了SqlSession元件,它是使用者層面的API。實際上SqlSession是Executor元件的外觀,目的是為使用者提供更友好的資料庫操作接口,這是設計模式中外觀模式的典型應用。真正執行SQL操作的是Executor元件,Executor可以了解為SQL執行器,它會使用StatementHandler元件對JDBC的Statement對象進行操作。

當Statement類型為CallableStatement和PreparedStatement時,會通過ParameterHandler元件為參數占位符指派。ParameterHandler元件中會根據Java類型找到對應的TypeHandler對象,TypeHandler中會通過Statement對象提供的setXXX()方法(例如setString()方法)為Statement對象中的參數占位符設定值。StatementHandler元件使用JDBC中的Statement對象與資料庫完成互動後,當SQL語句類型為SELECT時,MyBatis通過ResultSetHandler元件從Statement對象中擷取ResultSet對象,然後将ResultSet對象轉換為Java對象。

1.Configuration

MyBatis架構的配置資訊有兩種,一種是配置MyBatis架構屬性的主配置檔案;另一種是配置執行SQL語句的Mapper配置檔案。Configuration的作用是描述MyBatis主配置檔案的資訊。Configuration類中定義了一系列的屬性用來控制MyBatis運作時的行為,這些屬性代碼如下:

public class Configuration {
//環境變量
  protected Environment environment;

  //允許在嵌套語句中使用分頁(RowBounds),false表示允許
  protected boolean safeRowBoundsEnabled;
  //允許在嵌套語句中使用分頁,false表示允許(ResultHandler)
  protected boolean safeResultHandlerEnabled = true;
  // 是否開啟自動駝峰命名規則的映射
  protected boolean mapUnderscoreToCamelCase;
  // 當開啟時,任何方法的調用都會加載該對象的所有屬性,否則,每個屬性會按需加載
  // 參考lazyLoadTriggerMethods
  protected boolean aggressiveLazyLoading;
  // 是否允許單一語句傳回多結果集(需要JDBC驅動支援)
  protected boolean multipleResultSetsEnabled = true;
  //允許JDBC支援自動生成主鍵,需要驅動相容。如果為true, 強制使用自動生成的主鍵,
  //盡管理患局不能相容,但仍可正常工作。
  protected boolean useGeneratedKeys;
  // 使用列标簽代替列名,不同的驅動在這方面會有不同的表現,具體可參考JDBC驅動相關文檔或通過測
  //試這兩種不同的模式來觀察JDBC聚動的結果
  protected boolean useColumnLabel = true;
  //是否開片Napper緩存,即二級緩存 ,true表示開啟
  protected boolean cacheEnabled = true;
  //指定當結果集中的值為null的時候是否調用映射對象的Setter方法
  //對于Map.keySet()依賴或null的值初始化的時候是沒有用處的,int,boolean之類不能設定成null
  protected boolean callSettersOnNulls;
  /*
  * 允許使用方法簽名中的名稱作為語句參數名稱。
  * 為了使用該特性, 你的工程必須采用Java8編譯,并 且加上-parameters選項
  * */
  protected boolean useActualParamName = true;
  //當傳回行所有列為空的時候,mybatis會預設傳回null
  //設定這個值的時候,mybatis會傳回一個空實列。也适用于嵌套的結果集(collection,association)
  protected boolean returnInstanceForEmptyRow;

  // 指定mybatis增加到日志名稱的字首
  protected String logPrefix;
  // 指定mybaits所用日志的具體實作,未指定時候自動查找
  // 如:SLF4J LOG4J LOG4J2 FOK LOGGING COMMONS LOCGING STOOUT LOGGING NO LOGGING
  protected Class<? extends Log> logImpl;
  //指定VFS的實作,VFS的實作類的全限定名,以逗号分割
  protected Class<? extends VFS> vfsImpl;
  // Mythatis利用本地緩存機制防止循環引用和加速複查詢。
  // 預設值為SESSION,這種情況下會緩存一個會話中的所有查詢。
  // 設定值為STATEMENT,本地會話僅用在語句執行上,對相同 SqlSemssion的不同調用将不會共享資料
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  //當沒有為參數指定JDBC類型時候,指定JDBC類型的值為null
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  //指定哪個對象的方法會觸發一次延遲加載
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[]{"equals", "clone", "hashCode", "toString"}));
  //設定逾時的時間,它決定驅動等待資料庫相應的秒數
  protected Integer defaultStatementTimeout;
  //預設的FetchSize,用于設定Statement對象的FetchSize大小,用于限制從資料庫擷取的最大行數
  // 為null由具體的JDBC決定
  protected Integer defaultFetchSize;
  /*
  * 配置狀認的 Executor,
  * SIMPLE:普通的Executor
  * REUSE:執行器會複用Statement對象
  * BATCH:批量執行所有的更新語句
  *
  * */
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  /*
  * 指定mybatis如何自動識别列到java實體屬性
  * NONE:取消自動映射,
  * PARTIAL:隻會自動映射沒有定義嵌套結果集映射的結果集
  * FULL:自動映射任意複雜的結果集(無論是否嵌套)
  *  */
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  /*
  * 指定發現自動映射目标未知列《或未知的屬性》的行為
  * NONE:不做任何反應
  * WARNING:輸出提醒日志
  * FAILING :映射失敗抛出SqlSessionException
  *
  * */
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  // 延遲加載的全局開關:當開啟時, 所有關聯對象都會延遲加載,特定關聯關系中可通過設定fetchType
  //屬性來覆寫該項的開關狀态
  protected boolean lazyLoadingEnabled = false;
  // 指定mybatis建立具有延遲加載能力對象所用的代理工具,CGLIB JAVASSIST
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  protected String databaseId;

  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
   */
  protected Class<?> configurationFactory;
}           

這些屬性的值可以在MyBatis主配置檔案中通過<setting>标簽指定,例如:

<settings>
		<setting name="useGeneratedKeys" value="true"/>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="logImpl" value="LOG4J"/>
	</settings>           

Configuration除了控制MyBatis的行為外,還作為容器存放TypeHandler(類型處理器)、TypeAlias(類型别名)、Mapper接口及Mapper SQL配置資訊。這些資訊在MyBatis架構啟動時注冊到Configuration元件中。Configuration類中通過下面的屬性儲存TypeHandler、TypeAlias等資訊:

// 用于注冊Mapper接口資訊,建立Mapper接口的Class對象和MapperProxyFactory對象之間的關系,
  // 其中MapperProxyFactory對象用于建立Mapper動态代理對象
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  // 用于注冊MyBatis插件資訊,MyBatis插件實際上就是一個攔截器。
  protected final InterceptorChain interceptorChain = new InterceptorChain();
//  用于注冊所有的TypeHandler,并建立Jdbc類型、JDBC類型與TypeHandler之間的對應關系。
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  // 用于注冊所有的類型别名
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  // 用于注冊LanguageDriver,LanguageDriver用于解析SQL配置,将配置資訊轉換為SqlSource對象。
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  /*
  * MappedStatement對象描述<insert|select|update|delete>等标簽或者通過
  * @Select、@Delete、@Update、@Insert等注解配置的SQL資訊。
  * MyBatis将所有的MappedStatement對象注冊到該屬性中,其中Key為Mapper的Id,Value為MappedStatement對象。
  *
  *
  * */
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  // 用于注冊Mapper中配置的所有緩存資訊,其中Key為Cache的Id,也就是Mapper的命名空間,Value為Cache對象
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  // 用于注冊Mapper配置檔案中通過<resultMap>标簽配置的ResultMap資訊,ResultMap用于建立Java實體屬性與資料庫字段之間的映射關系,
  // 其中Key為ResultMap的Id,該Id是由Mapper命名空間和<resultMap>标簽的id屬性組成的,Value為解析<resultMap>标簽後得到的ResultMap對象。
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  // 用于注冊Mapper中通過<parameterMap>标簽注冊的參數映射資訊。Key為ParameterMap的Id,由Mapper命名空間和<parameterMap>标簽的id屬性構成,
  // Value為解析<parameterMap>标簽後得到的ParameterMap對象。
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");

 // 用于注冊KeyGenerator,KeyGenerator是MyBatis的主鍵生成器,MyBatis中提供了3種KeyGenerator,即Jdbc3KeyGenerator(資料庫自增主鍵)、NoKeyGenerator(無自增主鍵)、
  // SelectKeyGenerator(通過select語句查詢自增主鍵,例如oracle的sequence)。
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

  // 用于注冊所有Mapper XML配置檔案路徑。
  // 内容是namespace:+type
  protected final Set<String> loadedResources = new HashSet<String>();
  // 用于注冊Mapper中通過<sql>标簽配置的SQL片段,Key為SQL片段的Id,Value為MyBatis封裝的表示XML節點的XNode對象。
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

  // 存放解析異常的XMLStatementBuilder對象
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  // 存放解析異常的CacheRefResolver對象
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
  // 存放解析異常的ResultMapResolver對象
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  // 存放解析異常的MethodResolver對象
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
...

           

MyBatis架構啟動時,會對所有的配置資訊進行解析,然後将解析後的内容注冊到Configuration對象的這些屬性中。

除此之外,Configuration元件還作為Executor、StatementHandler、ResultSetHandler、ParameterHandler元件的工廠類,用于建立這些元件的執行個體。Configuration類中提供了這些元件的工廠方法,這些工廠方法簽名如下:

//ParameterHandler元件工廠方法
public ParameterHandler newParameterHandler(MappedStatement mappedStatementObject parameterObject,  BoundSql boundSql);
// ResultSetHandler 元件工廠方法
public ResultSetHandler newResultSetHandler(Executor executor,MappedStatement mappedStatement,RowBounds rowBounds,ParameterHandlerparameterHandler,ResultHandler resultHandler,BoundSql boundSql);
// StatementHandler 元件工廠方法
public StatementHandler newStatementHandler(Executor executor,MappedStatement mappedStatement,Object parameterObject,RowBounds rowBoundsResultHandler resultHandler, BoundSql boundSql);
// Executor 元件工廠方法
public Executor newExecutor(Transtransaction,ExecutorTypeexecutorType);           

這些工廠方法會根據MyBatis不同的配置建立對應的實作類。例如,Executor元件有4種不同的實作,分别為BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutor,當defaultExecutorType的參數值為REUSE時,newExecutor()方法傳回的是ReuseExecutor執行個體,當參數值為SIMPLE時,傳回的是SimpleExecutor執行個體,這是典型的工廠方法模式的應用。

MyBatis采用工廠模式建立Executor、StatementHandler、ResultSetHandler、ParameterHandler的另一個目的是實作插件攔截邏輯,這一點在後面會詳細介紹。

2.Executor

SqlSession是MyBatis提供的操作資料庫的API,但是真正執行SQL的是Executor元件。Executor接口中定義了對資料庫的增删改查方法,其中query()和queryCursor()方法用于執行查詢操作,update()方法用于執行插入、删除、修改操作。Executor接口有幾種不同的實作類,如圖所示

MyBatis核心元件介紹二-元件一

MyBatis提供了3種不同的Executor,分别為SimpleExecutor、ResueExecutor、BatchExecutor,這些Executor都繼承至BaseExecutor,BaseExecutor中定義的方法的執行流程及通用的處理邏輯,具體的方法由子類來實作,是典型的模闆方法模式的應用。SimpleExecutor是基礎的Executor,能夠完成基本的增删改查操作,ResueExecutor對JDBC中的Statement對象做了緩存,當執行相同的SQL語句時,直接從緩存中取出Statement對象進行複用,避免了頻繁建立和銷毀Statement對象,進而提升系統性能,這是享元思想的應用。

BatchExecutor則會對調用同一個Mapper執行的update、insert和delete操作,調用Statement對象的批量操作功能。另外,我們知道MyBatis支援一級緩存和二級緩存,當MyBatis開啟了二級緩存功能時,會使用CachingExecutor對SimpleExecutor、ResueExecutor、BatchExecutor進行裝飾,為查詢操作增加二級緩存功能,這是裝飾器模式的應用。

接下來以一個案例介紹如何直接使用Executor元件與資料庫互動,代碼如下:

public void testExecutor() throws IOException, SQLException {
        // 擷取配置檔案輸入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 通過SqlSessionFactoryBuilder的build()方法建立SqlSessionFactory執行個體
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 調用openSession()方法建立SqlSession執行個體
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Configuration configuration = sqlSession.getConfiguration();
        // 從Configuration對象中擷取描述SQL配置的MappedStatement對象
        MappedStatement listAllUserStmt = configuration.getMappedStatement(
                "com.blog4java.mybatis.example.mapper.UserMapper.listAllUser");
        //建立ReuseExecutor執行個體
        Executor reuseExecutor = configuration.newExecutor(
                new JdbcTransaction(sqlSession.getConnection()),
                ExecutorType.REUSE
        );
        // 調用query()方法執行查詢操作
        List<UserEntity> userList =  reuseExecutor.query(listAllUserStmt,
                null,
                RowBounds.DEFAULT,
                Executor.NO_RESULT_HANDLER);
        System.out.println(JSON.toJSON(userList));
    }           

如上面的代碼所示,Executor與資料庫互動需要Mapper配置資訊,MyBatis通過MappedStatement對象描述Mapper的配置資訊,是以Executor需要一個MappedStatement對象作為參數。MyBatis在應用啟動時,會解析所有的Mapper配置資訊,将Mapper配置解析成MappedStatement對象注冊到Configuration元件中,我們可以調用Configuration對象的getMappedStatement()方法擷取對應的MappedStatement對象,擷取MappedStatement對象後,根據SQL類型調用Executor對象的query()或者update()方法即可。

3.MappedStatement

MyBatis通過MappedStatement描述<select|update|insert|delete>或者@Select、@Update等注解配置的SQL資訊。在介紹MappedStatement元件之前,我們先來了解一下MyBatis中SQL Mapper的配置。不同類型的SQL語句需要使用對應的XML标簽進行配置。這些标簽提供了很多屬性,用來控制每條SQL語句的執行行為。下面是<select>标簽中的所有屬性:

<select
id="getUserById"
parameterType="intu
parameterMap="deprecated"
resultType="hashmap"
resultMap="userResultMap
flushCache="falser
useCache="true"
timeout="10000
fetchSize="256"
statementType="PREPARED
resultSetType="FORWARD ONLY">           

這些屬性的含義如下。

id:在命名空間中唯一的辨別符,可以被用來引用這條配置資訊。

parameterType:用于指定這條語句的參數類的完全限定名或别名。這個屬性是可選的,MyBatis能夠根據Mapper接口方法中的參數類型推斷出傳入語句的類型。

parameterMap:引用通過<parameterMap>标簽定義的參數映射,該屬性已經廢棄。

resultType:從這條語句中傳回的期望類型的類的完全限定名或别名。注意,如果傳回結果是集合類型,則resultType屬性應該指定集合中可以包含的類型,而不是集合本身。

resultMap:用于引用通過<resultMap>标簽配置的實體屬性與資料庫字段之間建立的結果集的映射(注意:resultMap和resultType屬性不能同時使用)。

flushCache:用于控制是否重新整理緩存。如果将其設定為 true,則任何時候隻要語句被調用,都會導緻本地緩存和二級緩存被清空,預設值為false。

useCache:是否使用二級緩存。如果将其設定為 true,則會導緻本條語句的結果被緩存在MyBatis的二級緩存中,對應<select>标簽,該屬性的預設值為true。

timeout:驅動程式等待資料庫傳回請求結果的秒數,逾時将會抛出異常。

fetchSize:用于設定JDBC中 Statement對象的fetchSize屬性,該屬性用于指定SQL執行後傳回的最大行數。

statementType:參數可選值為STATEMENT、PREPARED或CALLABLE,這會讓MyBatis分别使用Statement、PreparedStatement或CallableStatement與資料庫互動,預設值為PREPARED。

databaseId:如果配置了 databaseIdProvider,MyBatis會加載所有不帶databaseId或比對目前 databaseId 的語句。

resultOrdered:這個設定僅針對嵌套結果 select語句适用,如果為 true,就是假定嵌套結果包含在一起或分組在一起,這樣的話,當傳回一個主結果行的時候,就不會發生對前面結果集引用的情況。這就使得在擷取嵌套結果集的時候不至于導緻記憶體不夠用,預設值為false。

resultSets:這個設定僅對多結果集的情況适用,它将列出語句執行後傳回的結果集并每個結果集給一個名稱,名稱使用逗号分隔。

lang:該屬性用于指定LanguageDriver實作,MyBatis中的LanguageDriver用于解析<select|update|insert|delete>标簽中的SQL語句,生成SqlSource對象。

上面的屬性中,resultMap、resultType、resultSetType、fetchSize、useCache、resultSets、resultOrdered是<select>标簽特有的,其他屬性是<update|insert|delete>标簽共有的。另外,<insert>和<update>标簽有幾個特有的屬性,下面是<insert>标簽的所有屬性:

<insert
id="insertUser"
parameterType="com.blog4java.User"
flushCache="true"
statementType="PREPARED"
keyProperty="id'
keyColumn="id"
useGeneratedKeys="true"
timeout="20">           

這幾個屬性的作用如下:

useGeneratedKeys:該屬性僅對<update>和<insert>标簽有用,屬性值為true時,MyBatis使用JDBC Statement對象的getGeneratedKeys()方法來取出由資料庫内部生成的鍵值,例如MySQL自增主鍵。

keyProperty:該屬性僅對<update>和<insert>标簽有用,用于将資料庫自增主鍵或者<insert>标簽中<selectKey>标簽傳回的值填充到實體的屬性中,如果有多個屬性,則使用逗号分隔。

keyColumn:該屬性僅對<update>和<insert>标簽有用,通過生成的鍵值設定表中的列名,這個設定僅在某些資料庫(例如PostgreSQL)中是必需的,當主鍵列不是表中的第一列時需要設定,如果有多個字段,則使用逗号分隔。

MappedStatement類通過下面這些屬性儲存<select|update|insert|delete>标簽的屬性資訊:

public final class MappedStatement{
    ...
	private String id;
	private Integer fetchSize;
	private Integer timeout;
	private StatementType statementType;
    private ResultSetType resultSetType;
    private ParameterMap parameterMap;
    private List<ResultMap> resultMaps;
    private boolean flushCacheRequired;
	private boolean useCache;
	private boolean resultOrdered;
    private SqlCommandType sqlCommandType;
    private LanguageDriver lang;
    private String[] keyProperties;
    private String[] keyColumns;
	private String databaseId;
	private String[] resultSets;
    ...
}               

除此之外,MappedStatement類還有一些其他的屬性,這些屬性及其含義如下:

private Cache cache; // 二級緩存執行個體
private SqlSource sqlSource; // 解析 SQL 語生成的 SqlSource 執行個體
private String resource; // Mapper 資源路徑
private Configuration configuration; // Confiquration 對象的引用
private KeyGenerator ;//主鍵生成政策
private boolean hasNestedResultMaps; // 是否有套的 ResultMap
privateprivate Log statementLog; // 輸出日志           

cache:二級緩存執行個體,根據Mapper中的<cache>标簽配置資訊建立對應的Cache實作。

sqlSource:解析<select|update|insert|delete>,将SQL語句配置資訊解析為SqlSource對象。

resource:Mapper配置檔案路徑。

configuration:Configuration對象的引用,友善擷取MyBatis配置資訊及TypeHandler、TypeAlias等資訊。

keyGenerator:主鍵生成政策,預設為Jdbc3KeyGenerator,即資料庫自增主鍵。當配置了<selectKey>時,使用SelectKeyGenerator生成主鍵。

hasNestedResultMaps:<select>标簽中通過resultMap屬性指定ResultMap是不是嵌套的ResultMap。

statementLog:用于輸出日志。

繼續閱讀