基於Mybatis的配置文件入門必看篇

Mybatis 配置文件入門

從這篇文章開始,我們將從其核心配置文件入手,對Mybatis支持的核心配置文件進行簡單詳細的描述。

從下面這段代碼是我們在使用mybatis前的配置初始化過程

我們通過閱讀其源碼來逐步瞭解內部實現原理。

// Mybatis 通過SqlSessionFactory獲取SqlSession, 然後才能通過SqlSession與數據庫進行交互
 private static SqlSessionFactory getSessionFactory() {
  SqlSessionFactory sessionFactory = null;
  String resource = "configuration.xml";
  try {
   sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
  } catch (IOException e) {
   e.printStackTrace();
  }
  return sessionFactory;
 }

我們進入到SqlSessionFactoryBuilder類裡面

查看其源碼:

/**
 *    Copyright 2009-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.session; 
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
 
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory; 
/**
 * Builds {@link SqlSession} instances.
 *
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {
 
  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }
 
  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }
 
  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }
 
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
 
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
 
  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }
 
  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }
 
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
    
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
 
}

在這個類中,支持多種構造SqlSessionFactory的方法。可以隻傳入mybatis配置文件,也可以同時傳入properties配置文件替代mybatis配置文件中的<properties>元素標簽,另外也支持傳入環境參數envirmont參數。

我們跟隨著源碼繼續往下看:

 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

這裡創建瞭一個XMLConfigBuilder類實例,通過他來對mybatis配置文件(一個xml配置文件)進行解析。

解析的代碼入口如下所示:

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
 
  private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

從這裡看出,配置文件是以configuration為根節點,在根節點之下有多個子節點,它們分別為:settings、properties、typeAliases、plugins、objectFactory、objectWrapperFactory、environments、databaseIdProvider、typeHandlers、mappers。

MyBatis核心配置文件標簽簡介

XML 映射配置文件

MyBatis的配置文件包含瞭影響MyBatis行為甚深的設置(settings)和屬性(properties)信息。文檔的頂層結果如下:

configuration配置

properties屬性

setting設置

typeAliases類型命名

typeHandlers類型處理器

objectFactory對象工廠

plugins插件

environments環境

environment環境變量

transactionManager事務管理器

dataSource數據源

databaseIdProyider數據庫廠商標識

mappers映射

properties

屬性都是可外部配置且可動態替換的,既可以在典型的Java屬性文件中配置,亦可通過properties元素的子元素來傳遞。

例如:

<!-- 
  mybatis的核心配置文件
   1.數據庫的連接信息(連接池)
  -->
  <properties resource="jdbc.properties"></properties>

其中的屬性就可以在整個配置文件中使用來替換需要動態配置的屬性值。

比如:

  <!-- 默認連接池 -->
  <dataSource type="POOLED">
       <property name="driver" value="${driverClass}"/>
       <property name="url" value="${url}"/>
       <property name="username" value="${userid}"/>
       <property name="password" value="${password}"/>
   </dataSource>

properties屬性:將數據庫連接參數單獨配置在jdbc.properties中,隻需要在mybatis.xml文件中加載jdbc.properties的屬性值。 在mybatis.xml中就不需要對數據庫連接參數硬編碼(硬編碼是指將可變變量用一個固定值來代替的方法)。在properties 元素體內定義的屬性首先被讀取。然後會讀取properties元素中resource或url加載屬性,它會覆蓋已讀取的同名屬性。

註意:如果在properties標簽裡面定義的屬性被${}所引用瞭,對#{}不管用。那麼它不會讀取parameterType裡面的參數值。比如properties裡面定義瞭id屬性,值為40,在映射文件中引用該值,${id}那麼我從parameterType裡面傳值時,不管我傳基本類型還是引用類型進去都不會覆蓋這個${id}值。始終都會讀取40.

屬性也可以被傳遞到SqlSessionBuilder.build()方法中。

例如:

SqlSessionFactoryBuilder源碼:

 public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }
 
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

typeAliases

類型別名是為Java類型設置一個短的名字。它隻和XML配置有關,存在意義僅在於用來減少類完全限定名的冗餘。

例如:

 <!-- 給類定義別名 -->
  <typeAliases>
   <typeAlias type="cn.et.lesson02.annotion.Food" alias="food"/>
   <typeAlias alias="Author" type="domain.blog.Author"/>
    <typeAlias alias="Blog" type="domain.blog.Blog"/> 
  </typeAliases>

當這樣配置時,Blog可以用在任何使用domain.blog.Blog的地方。

也可以指定一個包名,MyBatis會在包名下面搜索需要的Java Bean

比如:

<typeAliases> <package name="domain.blog"/> </typeAliases>

每一個包domain.blog中的Java Bean,在沒有註解的情況下,會使用Bean的首字母小寫的非限定類名來作為它的別名。比如domain.blog.Author的別名為author;若有註解,則別名為其註解值。

看下面的例子:

@Alias("author") public class Author { ... }

mapper標簽(映射配置):加載映射文件

<mappers>
   <!-- 
    通過resource加載單個映射文件
     加載單個類路徑下的映射文件 
      使用相對於類路徑的資源:
   -->
   <mapper resource="cn/et/lesson01/FoodMapper.xml"/>
   <!-- 
     使用類路徑加載單個映射文件
        <mapper url="cn.domarvel.dao.UserMapper"/>
         註意:此種方法要求mapper接口名稱和mapper映射文件名稱相同,且放在同一個目錄中。並且還有一個前提是:使用的是mapper代理方法
    -->
    <!-- 
      自動批量加載指定包下的所有Mapper接口配置文件
        <package name="cn.domarvel.dao"/>
                     註意:此種方法要求mapper接口名稱和mapper映射文件名稱相同,且放在同一個目錄中。並且還有一個前提是:使用的是mapper代理方法
     -->
  </mappers>

Mapper XML文件

MyBatis的真正強大在於他的映射語句,也是它的魔力所在,由於它的異常強大,映射器的XML文件就顯得相對簡單。如果拿它跟具有相同功能的JDBC代碼進行對比,你會立即發現省掉瞭將近95%的代碼。MyBatis就是針對SQL構建的,並且比普通的方法做的更好。

mapper標簽:映射文件的根節點,在根節點中支持九個元素。

namespace是用於綁定Dao接口的,當你的namespace綁定接口後,你可以不用寫接口實現類,mybatis會通過該綁定自動幫你找到對應要執行的SQL語句

註意:接口中的方法與映射文件中的SQL語句的ID一一對應

<mapper namespace="a">
</mapper>
<mapper namespace="cn.et.lesson02.xml.FoodMapper" >
</mapper>

SQL映射文件有很少的幾個頂級元素(按照它們應該被定義的順序):

cache:給定命名空間的緩存配置

cache-ref:其命名空間緩存配置的引用。

resultMap:是最復雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。

parameterMap:已廢棄,老式風格的參數映射。內聯參數是首選,這個元素可能在將來被移除,這裡不會記錄。

sql:可被其他語句引用的可重用語句塊。

insert:映射插入語句

update:映射更新語句

delete:映射更新語句

select:映射查詢語句

select

查詢語句是MyBatis中最常用的元素之一,光能把數據存到數據庫中價值並不大,如果還能重新取出來才有用,多數應用也都是查詢比修改要頻繁。對每個插入、更新或刪除操作,通常對應多個查詢操作。這是MyBatis的基本原則之一,也是將焦點和努力放在查詢和結果映射的原因。簡單查詢的select元素時非常簡單的。

比如

<select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>

這個語句被稱作selectPerson,接收一個int(或Integer)類型的參數,並返回一個HashMap類型的對象,其中的鍵是列名,值便是結果行中的對應值。

註意參數符號:#{id}

select元素有很多屬性允許你配置,來決定每條語句的作用細節。

SELECT多條件查詢

parameterType用於傳遞參數多參數可以使用傳入對象以及map的方式傳遞多個參數。

#{}表示傳遞的參數值 類同jdbc的? ${}表示直接將參數值替換類同’%值%’

比如:

<select id=“selectPerson” parameterType=“map”    resultType=“person”> 
 SELECT * FROM PERSON WHERE ID = #{id} and name like ‘%${name}%' 
</select>

Map中必須存在id和name的鍵值對

SELECT調用存儲過程

創建存儲過程prg_add(p1 in number,p2 in number,p3 out number)

Mybatis映射文件中使用select調用存儲過程

<select id=“prgAdd" statementType="CALLABLE">  <![CDATA[  
{call pro_hello (
 #{p1,mode=IN,jdbcType=NUMBER},
 #{p2,mode=IN,jdbcType=NUMBER},
 #{result,mode=OUT,jdbcType=NUMBER})}  
   ]]>  
 </select>  

測試調用過程

Map<String, String> param = new HashMap<String, String>();  
param.put(“p1”, 1);  param.put(“p2”, 2);  
String returnValue = (String) session.selectOne(" prgAdd ", param);  
System.out.println("result=" + param.get("result"));  
System.out.println("returnValue=" + returnValue);  

insert、update和delete

數據更變語句insert、update和delete的實現非常接近

<insert id="insertAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" keyProperty="" keyColumn="" useGeneratedKeys="" timeout="20">
<update id="updateAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20"> 
<delete id="deleteAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20">

insert、update和delete參數

parameterType:入參的全限定類名或類型別名

keyColumn:設置數據表自動生成的主鍵名。對特定數據庫(如PostgreSQL),若自動生成的主鍵不是第一個字段則必須設置

keyProperty:默認值unset,用於設置getGeneratedKeys方法或selectKey子元素返回值將賦值到領域模型的哪個屬性中

useGeneratedKey:取值范圍true|false(默認值)設置是否使用JDBC的getGenereatedKeys方法獲取主鍵並賦值到keyProperty設置的領域模型屬性中。

MySQL和SQLServer執行auto-generated key field,因此當數據庫設置好自增長主鍵後,可通過JDBC的getGeneratedKeys方法獲取。但像Oralce等不支持auto-generated key field的數據庫就不能用這種方法獲取主鍵瞭

flushCache:取值范圍true(默認值)|false,設置執行該操作後是否會清空二級緩存和本地緩存

timeout:默認為unset(依賴jdbc驅動器的設置),設置執行該操作的最大時限,超時將拋異常

databaseId:取值范圍oracle|mysql等,表示數據庫廠傢,元素內部可通過`<if test=”_databaseId = ‘oracle'”>`來為特定數據庫指定不同的sql語句

selectKey

對於不支持自動生成類型的數據庫或可能不支持自動生成主鍵JDBC驅動來說,MyBatis有另外一種方法來生成主鍵。

這裡有一個簡單的示例,它可以生成一個隨機ID(最好不要這麼做,但這裡展示瞭MyBatis處理問題的靈活性及其所關心的廣度):

<insert id="saveFood" >
  
	<!-- 
	 selectKey 在Mybatis中是為瞭解決Insert數據時不支持主鍵自動生成的問題,他可以很隨意的設置生成主鍵的方式。
	 		   SelectKey需要註意order屬性,像Mysql一類支持自動增長類型的數據庫中,
	 		   order需要設置為after才會取到正確的值。像Oracle這樣取序列的情況,需要設置為before,否則會報錯。
	 	keyProperty:selectKey 語句結果應該被設置的目標屬性。
	 	order:可設置為 BEFORE 或 AFTER,如果設置為 BEFORE,那麼它會首先選擇主鍵,設置 keyProperty 然後執行插入語句。
	 								如果設置為 AFTER,那麼先執行插入語句,然後是 執行selectKey元素
		statementType:和前面的相 同,MyBatis 支持 STATEMENT ,PREPARED 和CALLABLE 語句的映射類型,
					       分別代表 PreparedStatement 和CallableStatement 類型。	
					       
		註意:selectKey操作會將操作查詢結果賦值到insert元素的parameterType的入參實例下對應的屬性中。並提供給insert語句使用
	 -->
 	<selectKey keyProperty="foodId" order="BEFORE" resultType="int" statementType="STATEMENT">
 		select FOOD_SEC.Nextval from dual
 	</selectKey>
	 insert into food values(#{foodId},#{foodName},#{price})
 </insert>

在上面的示例中,selectKey元素將會首先運行,food的id會被設置,然後插入語句會被調用,這給瞭你一個和數據庫中來處理自動生成的主鍵類似的行為,避免瞭使Java代碼變得復雜

SQL

這個元素可以被用來定義可重用的SQL代碼段,可以包含在其他語句中,它可以被靜態地(在加載參數)參數化,不同的屬性值太高包含的實例變化。

比如:

<sql id="userColumns"> 
${alias}.id,${alias}.username,${alias}.password 
</sql>

這個SQL片段可以被包含在其他語句中,例如:

<select id="selectUsers" resultType="map"> select <include refid="userColumns">
<property name="alias" value="t1"/></include>, <include refid="userColumns"><property name="alias" value="t2"/>
</include> from some_table t1 cross join some_table t2 </select>

屬性值可以用於包含的refid屬性或者包含的字句裡面的屬性值。

Result Map

resultMap 元素時MyBatis中最重要最強大的元素。它就是讓你遠離90%的需要從結果集中取出數據的JDBC代碼的那個東西,而且在一些情形下允許你做一些JDBC不支持的事情。事實上,編寫相似於對復雜語句聯合映射這些等同的代碼,也許可以跨過上千行的代碼。ResultMap的設計就是簡單語句不需要明確的結果映射,而很多復雜語句確實需要描述它們的關系。

	<!-- 
 	resultMap  
		基本作用:建立SQL查詢結果字段與實體屬性的映射關系 
	 	屬性
	 		id屬性 ,resultMap標簽的標識。
	 		type屬性 ,返回值的全限定類名,或類型別名。
	 	autoMapping 屬性 ,值范圍true(默認值)|false,設置是否啟動自動映射功能,
	 		自動映射功能就是自動查找與字段名小寫同名的屬性名,並調用setter方法。而設置為false後,
	 		則需要在resultMap內明確註明映射關系才會調用對應的setter方法。
 		
 		子元素:
 			id元素 ,用於設置主鍵字段與領域模型屬性的映射關系
			result元素 ,用於設置普通字段與領域模型屬性的映射關系
				property	需要映射到JavaBean 的屬性名稱。 	
 				
 				column	數據表的列名或者標簽別名。
 				
 				javaType	一個完整的類名,或者是一個類型別名。如果你匹配的是一個JavaBean,
 							那MyBatis 通常會自行檢測到。然後,如果你是要映射到一個HashMap,
 							那你需要指定javaType 要達到的目的。
 						
 				jdbcType	數據表支持的類型列表。這個屬性隻在insert,update 或delete 的時候針對允許空的列有用。
 							JDBC 需要這項,但MyBatis 不需要。如果你是直接針對JDBC 編碼,且有允許空的列,而你要指定這項。
 	 			
 	 			typeHandler	使用這個屬性可以覆寫類型處理器。這項值可以是一個完整的類名,也可以是一個類型別名。	
 	 			
 			association聯合
 						聯合元素用來處理“一對一”的關系。需要指定映射的Java實體類的屬性,屬性的javaType(通常MyBatis 自己會識別)。
 						對應的數據庫表的列名稱。如果想覆寫的話返回結果的值,需要指定typeHandler。 
						不同情況需要告訴MyBatis 如何加載一個聯合。MyBatis 可以用兩種方式加載:
 	
 							select: 執行一個其它映射的SQL 語句返回一個Java實體類型。較靈活;
							resultsMap: 使用一個嵌套的結果映射來處理通過join查詢結果集,映射成Java實體類型。
 	 
 	 
 	 -->
 	<resultMap type="grade" id="gradeMap" autoMapping="true" >
 		<!-- 
 			因為 autoMapping="false"(默認是true) 所以要明確註明映射關系  如果為true則會自動映射   不需要寫下面這行代碼
 			<result column="gid" property="gid"/>
 			還有下面這種方法
 				需要gid為主鍵的情況下使用
 			
 			列和屬性的關系  主鍵使用id 非主鍵使用result
 			<id column="gid" property="gid"/>
 		-->
 		<!-- 將查詢的結果gname賦給gname1 -->
 		<id column="gid" property="gid"/>
 		<result column="gname" property="gname"/>
 	</resultMap>

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: