Mybatis基本用法--上


Mybatis基本用法--上

本文只是為自己查漏補缺。全面的請看官方文檔,支持中英文
原理參考:http://blog.csdn.net/luanlouis/article/details/40422941

第一部分 基本概念

1.1 什么是MyBatis

  MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或注解,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。

1.2 經典配置

從 XML 中構建 SqlSessionFactory

  每個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的實例為中心的。 而 SqlSessionFactory 本 身 是 由 SqlSessionFactoryBuilder 創建的,它可以從 XML 配置,注解或手動配置 Java 來創建 SqlSessionFactory。但是當Mybatis與一些依賴注入框架(如Spring或者Guice)同時使用時,SqlSessions將被依賴注入框架所創建,所以你不需要使用SqlSessionFactoryBuilder或者SqlSessionFactory

  從 XML 文件中構建 SqlSessionFactory 的實例非常簡單,建議使用類路徑下的資源文件進行配置。MyBatis 包含一個名叫 Resources 的工具類,它包含一些實用方法,可使從 classpath 或其他位置加載資源文件更加容易。

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

典型配置文件:

<?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>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <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>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

  要注意 XML 頭部的聲明,用來驗證 XML 文檔正確性。environment 元素體中包含了事務管理和連接池的配置。mappers 元素則是包含一組 mapper 映射器(這些 mapper 的 XML 文件包含了 SQL 代碼和映射定義信息)

從 SqlSessionFactory 中獲取 SqlSession

  既然有了 SqlSessionFactory ,顧名思義,我們就可以從中獲得 SqlSession 的實例了。SqlSession 完全包含了面向數據庫執行 SQL 命令所需的所有方法。你可以通過 SqlSession 實例來直接執行已映射的 SQL 語句。

SqlSession session = sqlSessionFactory.openSession();

通過 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="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>

那么利用上面的SqlSession

try {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
  session.close();
}

通過注解定義

@Select({"select * from Blog where id=#{id}"})
Blog selectBlog(int id);

那么利用上面的SqlSession

try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
} finally {
  session.close();
}

  可以看出:使用接口(基於注解),不但可以執行更清晰和類型安全的代碼,而且還不用擔心易錯的字符串字面值以及強制類型轉換。
  其實可以結合使用,接口中:簡單的方法使用注解,復雜的方法使用xml配置。畢竟,對於簡單語句來說,注解使代碼顯得更加簡潔,然而 Java 注解對於稍微復雜的語句就會力不從心並且會顯得更加混亂
  要求:

  1. mapper命名空間org.mybatis.example.BlogMapper應該對應類路徑,即接口應該在org.mybatis.example.BlogMapper類路徑下;
  2. 具有相同的文件名,比如BlogMapper.java的配置為BlogMapper.xml(** 看不清請Ctrl+鼠標滾輪放大頁面 **);
  3. xml配置可以放在resources對應目錄下,且路徑也為org.mybatis.example.BlogMapper。
      下面給出例子,但為NewsDAO的配置

  即上面的xml配置文件不變,刪去注解@Select({"select * from Blog where id=#{id}"})

Blog selectBlog(int id);

1.3 作用域(Scope)和生命周期

對於依賴注入框架Spring
  依賴注入框架可以創建線程安全的、基於事務的 SqlSession 和映射器(mapper)並將它們直接注入到你的 bean 中,因此可以直接忽略它們的生命周期。如果對如何通過依賴注入框架來使用 MyBatis 感興趣可以研究一下 MyBatis-Spring 或 MyBatis-Guice 兩個子項目。

其他:

  SqlSessionFactoryBuilder:一旦創建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域(也就是局部方法變量)。
  SqlSessionFactory:一旦被創建就應該在應用的運行期間一直存在,因此 SqlSessionFactory 的最佳作用域是應用作用域
  SqlSession:每個線程都應該有它自己的 SqlSession 實例。所以它的最佳的作用域是請求或方法作用域。每次收到的 HTTP 請求,就可以打開一個 SqlSession,返回一個響應,就關閉它。你應該把這個關閉操作放到 finally 塊中以確保每次都能執行關閉。
  映射器實例(Mapper Instances):最好把映射器放在方法作用域(method scope)內。即上面的BlogMapper mapper = session.getMapper(BlogMapper.class);

第二部分 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="org/mybatis/example/config.properties">
        <property name="username" value="dev_user"/>
        <property name="password" value="F2Fa3!33TYyg"/>
    </properties>

    <settings>
        <setting name="cacheEnabled" value="true"/>
        ...
    </settings>

    <typeAliases>
        <typeAlias alias="Author" type="domain.blog.Author"/>
          ...
    </typeAliases>

    <plugins>
        <plugin interceptor="org.mybatis.example.ExamplePlugin">
            <property name="someProperty" value="100"/>
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <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 type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="db2"/>
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>

    <mappers>
        <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
        ...
    </mappers>
</configuration>

2.1 properties 屬性

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

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

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

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

  這個例子中的 username 和 password 將會由 properties 元素中設置的相應值來替換。 driver 和 url 屬性將會由 config.properties 文件中對應的值來替換。

2.2 settings 設置

設置參數 描述 有效值 默認值
cacheEnabled 所有映射器中配置的緩存全局開關。 true | false true
lazyLoadingEnabled 延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 true |false false
aggressiveLazyLoading 開啟時,任何方法的調用都會加載該對象的所有屬性,否則每個屬性會按需加載. true | false false (true in ≤3.4.1)
multipleResultSetsEnabled 對於未知的SQL查詢,允許單一語句返回不同的結果集以達到通用的效果。 true | false true
useColumnLabel 允許使用列標簽代替列名 true | false true
useGeneratedKeys (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數據庫內部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關系數據庫管理系統的自動遞增字段) true | false false
autoMappingBehavior 指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示取消自動映射;PARTIAL 只會自動映射沒有定義嵌套結果集映射的結果集。 FULL 會自動映射任意復雜的結果集(無論是否嵌套)。 NONE, PARTIAL, FULL PARTIAL
defaultExecutorType 配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重復用預處理語句(prepared statements); BATCH 執行器將重復用語句(即多次執行以批量操作)並執行批量更新。 SIMPLE,REUSE,BATCH SIMPLE
defaultStatementTimeout 設置超時時間,它決定驅動等待數據庫響應的秒數。 任意正整數 Not Set (null)
mapUnderscoreToCamelCase 是否開啟自動駝峰命名規則(camel case)映射,即從經典數據庫列名 A_COLUMN 到經典 Java 屬性名 aColumn 的類似映射。 true|false false
callSettersOnNulls 指定當結果集中值為 null 的時候是否調用映射對象的 setter(map 對象時為 put)方法,這對於有 Map.keySet() 依賴或 null 值初始化的時候是有用的。注意基本類型(int、boolean等)是不能設置成 null 的。 true | false false
logImpl 指定 MyBatis 所用日志的具體實現,未指定時將自動查找。 SLF4J,LOG4J,LOG4J2,JDK_LOGGING,COMMONS_LOGGING,STDOUT_LOGGING,NO_LOGGING Not set

  延遲加載:延遲加載(lazy load)是(也稱為懶加載)Hibernate3關聯關系對象默認的加載方式,所謂延遲加載就是當調用load方法加載對象時,返回代理對象,等到真正用到對象的內容時才發出sql語句,這個對象上的所有屬性都是默認值。
有如下程序代碼:

User user=(User)session.load(class, id);//直接返回的是代理對象
System.out.println(user.getId());//沒有發送sql語句到數據庫加載,因為id不用查就知道
user.getName();//創建真實的User實例,並發送sql語句到數據庫中

2.3 typeAliases 類型別名

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

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

  當這樣配置時,Blog可以用在任何使用domain.blog.Blog的地方。
  當然也可以用注解指定,看下面的例子:

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

2.4 typeHandlers 類型處理器

  無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,還是從結果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。
  要注意 MyBatis 不會窺探數據庫元信息來決定使用哪種類型,所以你必須在參數和結果映射中指明那是哪種類型的字段, 以使其能夠綁定到正確的類型處理器上。 這是因為:MyBatis 直到語句被執行才清楚數據類型。可以在這里設定:

<typeHandlers>
  <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>

也可以在映射器文件中設定:

<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
	<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
		<id column="id" property="id"/>
		<result column="name" property="name"/>
		<result column="funkyNumber" property="funkyNumber"/>
		<result column="roundingMode" property="roundingMode typeHandler="org.apache.ibatis.type.EnumTypeHandler""/>
	</resultMap>

	<select id="getUser" resultMap="usermap">
		select * from users
	</select>
	<insert id="insert">
	    insert into users (id, name, funkyNumber, roundingMode) values (
	    	#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
	    )
	</insert>
</mapper>

2.5 plugins 插件

  MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:

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

  這些類中方法的細節可以通過查看每個方法的簽名來發現,或者直接查看 MyBatis 的發行包中的源代碼。 假設你想做的不僅僅是監控方法的調用,那么你應該很好的了解正在重寫的方法的行為。 因為如果在試圖修改或重寫已有方法的行為的時候,你很可能在破壞 MyBatis 的核心模塊。 這些都是更低層的類和方法,所以使用插件的時候要特別當心。

  通過 MyBatis 提供的強大機制,使用插件是非常簡單的,只需實現 Interceptor 接口,並指定了想要攔截的方法簽名即可。

// ExamplePlugin.java
  @Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
  public class ExamplePlugin implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
      return invocation.proceed();
    }
    public Object plugin(Object target) {
      return Plugin.wrap(target, this);
    }
    public void setProperties(Properties properties) {
    }
  }
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

  上面的插件將會攔截在 Executor 實例中所有的 “update” 方法調用, 這里的 Executor 是負責執行低層映射語句的內部對象。

2.6 environments 環境

  沒必要配置 ,Spring + MyBatis完全可以將不同環境的配置放到application-dev.yml,application-prod.yml,application.yml等配置文件中包括dataSource 數據源的配置

2.7 dataSource 數據源

  有三種內建的數據源類型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):
UNPOOLED
  這個數據源的實現只是每次被請求時打開和關閉連接。雖然有一點慢,它對在及時可用連接方面沒有性能要求的簡單應用程序是一個很好的選擇。UNPOOLED 類型的數據源僅僅需要配置以下 5 種屬性:

屬性 描述
driver 這是 JDBC 驅動的 Java 類的完全限定名。
url 這是數據庫的 JDBC URL 地址。
username 登錄數據庫的用戶名。
password 登錄數據庫的密碼。
defaultTransactionIsolationLevel 默認的連接事務隔離級別。
driver.encoding UTF8(可選項)

POOLED
  這種數據源的實現利用“池”的概念將 JDBC 連接對象組織起來,避免了創建新的連接實例時所必需的初始化和認證時間。 使得並發 Web 應用可以快速響應請求。

  除了上述提到 UNPOOLED 下的屬性外,會有更多屬性用來配置 POOLED 的數據源:

屬性 描述
poolMaximumActiveConnections 在任意時間可以同時使用的最大連接數量,默認值:10
poolMaximumIdleConnections 任意時間可能存在的空閑連接數,經驗值建議設置與poolMaximumActiveConnections相同即可
poolMaximumCheckoutTime 獲取鏈接時如果沒有idleConnection同時activeConnection達到最大值,則從activeConnections列表第一個鏈接開始(即最先開始的鏈接,也最可能快速結束),檢查是否超過該設置的時間,如果超過,則被強制失效,返回鏈接。默認值為20000毫秒(即 20 秒),建議設置在預期最大的SQL執行時間。
poolTimeToWait 這是一個底層設置,如果獲取連接花費相當長的時間,它會給連接池打印狀態日志並重新嘗試獲取一個連接(避免在誤配置的情況下一直安靜的失敗),默認值:20000 毫秒(即 20 秒)。
poolPingQuery 發送到數據庫的偵測查詢,用來檢驗連接是否處在正常工作秩序中並准備接受請求。默認是“NO PING QUERY SET”,建議使用select 1,開銷小
poolPingEnabled 是否啟用偵測查詢。若開啟,也必須使用一個可執行的 SQL 語句設置 poolPingQuery 屬性(最好是一個非常快的 SQL),默認值:false,建議啟用,防止服務器端異常關閉,導致客戶端錯誤。
poolPingConnectionsNotUsedFor 用來配置poolPingQuery多長時間被調用一次。可以被設置匹配標准的數據庫鏈接超時時間,來避免不必要的偵測。默認值0(也就是所有鏈接每一時刻都被偵測到,但僅僅當poolPingEnabled為true時適用)。建議小於服務器端超時時間,MySQL默認超時是8小時。

JNDI
  這個數據源是為了使用如Spring或應用服務器這類的容器,容器可以集中或在外部配置數據源,然后設置JNDI上下文的引用。
這個數據源只需要配置兩個屬性:

屬性 描述
initial_context 這個屬性用來在 InitialContext 中尋找上下文(即,initialContext.lookup(initial_context))。這是個可選屬性,如果忽略,那么 data_source 屬性將會直接從 InitialContext 中尋找。
data_source 這是引用數據源實例位置的上下文的路徑。提供了 initial_context 配置時會在其返回的上下文中進行查找,沒有提供時則直接在 InitialContext 中查找。

  和其他數據源配置類似,可以通過添加前綴“env.”直接把屬性傳遞給初始上下文。比如:
env.encoding=UTF8

2.8 mappers 映射器

  告訴 MyBatis 到哪里去找映射文件。
比如:

<mappers>
  <!-- Using classpath relative resources -->
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <!-- Using url fully qualified paths -->
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <!-- Register all interfaces in a package as mappers -->
  <package name="org.mybatis.builder"/>
</mappers>

第三部分 Mapper XML 文件

3.1 SQL 映射文件的頂級元素

cache – 給定命名空間的緩存配置。
cache-ref – 其他命名空間緩存配置的引用。
resultMap – 是最復雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。
sql – 可被其他語句引用的可重用語句塊。
insert – 映射插入語句
update – 映射更新語句
delete – 映射刪除語句
select – 映射查詢語句

  下面分別介紹:

3.2 select

  比如一個簡單的Mapper 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="com.nowcoder.dao.NewsDAO">
    <sql id="table">news</sql>
    <sql id="selectFields">id,title, link, image, like_count, comment_count,created_date,user_id
    </sql>
    <select id="selectByUserIdAndOffset" resultType="com.nowcoder.model.News">
        SELECT
        <include refid="selectFields"/>
        FROM
        <include refid="table"/>

        <if test="userId != 0">
            WHERE user_id = #{userId}
        </if>
        ORDER BY id DESC
        LIMIT #{offset},#{limit}
    </select>
</mapper>

  這需要很多單獨的 JDBC 的代碼來提取結果並將它們映射到對象實例中,這就是 MyBatis 節省你時間的地方。

select元素屬性

<select
  id="selectPerson"
  parameterType="int"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10000"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
屬性 描述
id 在命名空間中唯一的標識符,可以被用來引用這條語句。
parameterType 傳入這條語句的參數類的完全限定名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的參數,默認值為 unset。
resultType 返回的期望類型的類的完全限定名或別名。注意如果是集合情形,那應該是集合可以包含的類型,而不能是集合本身。使用 resultType 或 resultMap,但不能同時使用。
resultMap 外部 resultMap 的命名引用。結果集的映射是 MyBatis 最強大的特性,對其有一個很好的理解的話,許多復雜映射的情形都能迎刃而解。
flushCache 若將其設置為 true,只要語句被調用,本地緩存和二級緩存都被清空,默認值:false。
useCache 將其設置為 true,將會導致本條語句的結果被二級緩存,默認值:對 select 元素為 true。
timeout 拋出異常前的,超時時間等待秒數。默認值為 unset(依賴驅動)。
fetchSize 每次批量返回的結果行數,嘗試影響驅動程序每次批量返回的結果行數和這個設置值相等。默認值為 unset(依賴驅動)。
statementType STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一個,默認值為 unset (依賴驅動)。
databaseId 如果配置了 databaseIdProvider,MyBatis 會加載所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。
resultSets 這個設置僅對多結果集的情況適用,它將列出語句執行后返回的結果集並每個結果集給一個名稱,名稱是逗號分隔的。

resultSetType

FORWORD_ONLY 結果集的游標只能向下滾動。
SCROLL_INSENSITIVE 結果集的游標可以上下移動,當數據庫變化時,當前結果集不變。
SCROLL_SENSITIVE 返回可滾動的結果集,當數據庫變化時,當前結果集同步改變。

3.3 insert, update 和 delete

Insert, Update, Delete 's Attributes

屬性 描述
id 同select
parameterType 同select
flushCache 同select,默認值:true(對應插入、更新和刪除語句)。
timeout 同select
statementType 同select,默認值:PREPARED。
useGeneratedKeys (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數據庫內部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關系數據庫管理系統的自動遞增字段),默認值:false。
keyProperty (僅對 insert 和 update 有用)唯一標記一個屬性,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設置它的鍵值,默認:unset。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
keyColumn (僅對 insert 和 update 有用)通過生成的鍵值設置表中的列名,這個設置僅在某些數據庫(像 PostgreSQL)是必須的,當主鍵列不是表中的第一列的時候需要設置。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
databaseId 同select

例如:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

  如果 id 使用了自動生成的列類型:

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

  如果你的數據庫還支持多行插入, 你也可以傳入一個Authors數組或集合,並返回自動生成的主鍵。

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

3.4 sql

  這個元素可以被用來定義可重用的 SQL 代碼段,可以包含在其他語句中。比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </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
    inner join some_table t2
</select>

3.5 參數(Parameters)

  這不是一個頂級元素,但很重要。
  默認情況下,使用#{}格式的語法會導致 MyBatis 創建預處理語句屬性並安全地設置值(比如?)。這樣做更安全,更迅速,通常也是首選做法。
比如:

<insert id="insertUser" parameterType="User">
  insert into users (id, username, password)
  values (#{id}, #{username}, #{password})
</insert>

  如果 User 類型的參數對象傳遞到了語句中,id、username 和 password 屬性將會被查找,然后將它們的值傳入預處理語句的參數中。你也可以

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

  但其實你只需要簡單指定屬性名,其他的事情 MyBatis 會自己去推斷,最多你需要為可能為空的列名指定 jdbcType。

#{firstName}
#{middleInitial,jdbcType=VARCHAR}

字符串替換
  不過有時你只是想直接在 SQL 語句中插入一個不改變的字符串。比如,像 ORDER BY,你可以這樣來使用:

ORDER BY ${columnName}

  這里 MyBatis 不會修改或轉義字符串。
  **注意:這種方式是不安全的,會導致潛在的 SQL 注入攻擊,因此要么不允許用戶輸入這些字段,要么自行轉義並檢驗(即將輸入中的特殊字符轉義處理,比如"&"→ "&", "<"→"<"," "→"  "。

3.6 Result Maps

  resultMap 元素是 MyBatis 中最重要最強大的元素。它就是讓你遠離 90%的需要從結果 集中取出數據的 JDBC 代碼的那個東西。 ResultMap 的設計就是簡單語句不需要明確的結果映射,而很多復雜語句只需要描述它們的關系。
  當應用程序使用 JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 對象)來作為領域模型,大部分可以省略 resultMap,MyBatis 會在幕后自動創建一個 ResultMap,基於屬性名來映射列到 JavaBean 的屬性上。
  要記住類型別名是你的伙伴。使用它們你可以不用輸入類的全路徑。

<!-- In mybatis-config.xml file -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

解決列名不匹配的兩種方式

第一種:

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

第二種:

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

  引用它的語句使用 resultMap 屬性就行了(注意我們去掉了 resultType 屬性)。比如:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>
ResultMap Attributes
屬性| 描述 --- | ----- id |當前命名空間中的一個唯一標識,用於標識一個result map. type |類的全限定名, 或者一個類型別名 autoMapping |如果設置這個屬性,MyBatis將會為這個ResultMap開啟或者關閉自動映射。這個屬性會覆蓋全局的屬性autoMappingBehavior。默認值為:unset。

id & result

<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

  這些是結果映射最基本內容。id 和 result 都映射一個單獨列的值到簡單數據類型(字符 串,整型,雙精度浮點數,日期等)的單獨屬性或字段。

  唯一不同是 id 表示的結果將是當比較對象實例時用到的標識屬性,類似於主鍵。這幫助來改進整體表現,特別是緩存和嵌入結果映射(也就是聯合映射) 。

Id and Result Attributes
屬性| 描述 --- | ----- property |映射到列結果的字段或屬性。比如,你可以這樣映射一些東西: “username” ,或者映射到一些復雜的東西: “address.street.number” 。 column |從數據庫中得到的列名,或者是列名的重命名標簽。 javaType|一個 Java 類的完全限定名,或一個類型別名。如果你映射到一個JavaBean,MyBatis 通常可以斷定類型。然而,如果你映射到的是 HashMap,那么你應該明確地指定 javaType 來保證所需的行為。 jdbcType |**JDBC 類型僅僅需要對插入,更新和刪除操作可能為空的列進行設置**。 typeHandler |使用這個屬性,你可以覆蓋默認的類型處理器。這個屬性值是類的完全限定名或者是一個類型處理器的實現,或者是類型別名。

3.7 Result Maps高級用法

1.首先,我們先看看一個常見的博客頁面的組成,如下:
  a.頁面上能夠展示的部分:正文,標題,日期,作者,評論正文,評論時間,評論人等等
  b.頁面之外的部分:用戶名,用戶id,用戶密碼,用戶基本信息(電話,郵箱,地址,興趣,特長,等等)
2.將我們頁面上的信息從數據庫中查出來的SQL語句轉化為Mapper文件中的語句,可能是如下內容:

<!-- Very Complex Statement -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
  select
       B.id as blog_id,
       B.title as blog_title,
       B.author_id as blog_author_id,
       A.id as author_id,
       A.username as author_username,
       A.password as author_password,
       A.email as author_email,
       A.bio as author_bio,
       A.favourite_section as author_favourite_section,
       P.id as post_id,
       P.blog_id as post_blog_id,
       P.author_id as post_author_id,
       P.created_on as post_created_on,
       P.section as post_section,
       P.subject as post_subject,
       P.draft as draft,
       P.body as post_body,
       C.id as comment_id,
       C.post_id as comment_post_id,
       C.name as comment_name,
       C.comment as comment_text,
       T.id as tag_id,
       T.name as tag_name
  from Blog B
       left outer join Author A on B.author_id = A.id
       left outer join Post P on B.id = P.blog_id
       left outer join Comment C on P.id = C.post_id
       left outer join Post_Tag PT on PT.post_id = P.id
       left outer join Tag T on PT.tag_id = T.id
  where B.id = #{id}
</select>

其對應着非常復雜的結果集合,Mapper文件可能長這個樣子,(注意當我們在select語句中使用B.title as blog_title,在resultMap的<result property="title" column="blog_title"/> 可以不設,系統會自動映射生成<result property="title" column="blog_title"/>,但是加上更清晰,也不會增加系統負擔)如下:

<!-- Very Complex Result Map -->  
<resultMap id="detailedBlogResultMap" type="Blog">  
  <constructor>  
    <idArg column="blog_id" javaType="int"/>  
  </constructor>  
  <result property="title" column="blog_title"/>  
  <association property="author" javaType="Author">  
    <id property="id" column="author_id"/>  
    <result property="username" column="author_username"/>  
    <result property="password" column="author_password"/>  
    <result property="email" column="author_email"/>  
    <result property="bio" column="author_bio"/>  
    <result property="favouriteSection" column="author_favourite_section"/>  
  </association>  
  <collection property="posts" ofType="Post">  
    <id property="id" column="post_id"/>  
    <result property="subject" column="post_subject"/> 
    <!-- 由於自動映射等級為:PARTIAL,自動的映射author_id;同時注意上面對Author類已經映射過了, -->  
    <association property="author" javaType="Author"/>  
    <collection property="comments" ofType="Comment">  
      <id property="id" column="comment_id"/>  
    </collection>  
    <collection property="tags" ofType="Tag" >  
      <id property="id" column="tag_id"/>  
    </collection>  
    <discriminator javaType="int" column="draft">  
      <case value="1" resultType="DraftPost"/>  
    </discriminator>  
  </collection>  
</resultMap>  

  注意上面是以Blog為中心,來找對應的作者,對應的文章,對應的評論和標簽。下面,開始詳細說明每一個元素,請讀者一定按照單元測試的方法推進,千萬不要一次性配置大量屬性,以免影響學習興趣。

3.7.1 構造方法

<constructor>  
   <idArg column="id" javaType="int"/>  
   <arg column="username" javaType="String"/> 
   <arg column="age" javaType="_int"/> 
</constructor>  

  盡管對於大部分的數據傳輸對象(DTO)對象,以及我們的domain模型,屬性值都是能夠起到相應的作用,但是,在某些情況下如我們想使用一些固定的類。比如:表格中包括一些僅供瀏覽的數據或者很少改變的數據。Mybatis的構造函數注入功能允許我們在類初始化時就設置某些值,而不暴露其中的public方法。
例如,程序中我們存在這樣一個實體類,如下:

public class User {
   //...
   public User(Integer id, String username, int age) {
     //...
  }
//...
}

  在Mybatis中,為了向這個構造方法中注入結果,Mybatis需要通過它的參數來表示構造方法。java中,沒有反射參數名稱的方法,因此,當創建一個構造方法的元素時,必須保證參數是按照順序排列的,而且,數據類型也必須匹配!

3.7.2 關聯

<association property="author" column="blog_author_id" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
</association>

  關聯元素用來處理數據模型中的“has-one”關系。比如一個博客賬號只能屬於一個用戶。關聯映射大部分是基於這種應用場景。關聯中不同的是你需要告訴 MyBatis 如何加載關聯。MyBatis 在這方面會有兩種不同的方式:

  • 嵌套查詢:通過執行另外一個 SQL 映射語句來返回預期的復雜類型。
  • 嵌套結果:使用嵌套結果映射來處理重復的聯合結果的子集。

關聯的嵌套查詢:即分別執行sql語句,一個sql語句的執行依賴於另外一條語句的結果,比如:

<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

  我們有兩個查詢語句:一個來加載博客,另外一個來加載作者,而且博客的結果映射描 述了“selectAuthor”語句應該被用來加載它的 author 屬性。其他所有的屬性將會被自動加載,假設它們的列和屬性名相匹配。
  這種方式很簡單, 但是對於大型數據集合和列表將不會表現很好。 問題就是我們熟知的 “N+1 查詢問題”。比如我們需要獲得4個作者對應的博客列表,按照嵌套查詢的方法:

select * from BLOG; 
select * from BLOG where Author_ID=1;
select * from BLOG where Author_ID=2;
select * from BLOG where Author_ID=3;
select * from BLOG where Author_ID=4;

  select語句的數目太多,需要頻繁的訪問數據庫,會影響檢索性能。如果需要查詢n個作者,那么必須執行n+1次select查詢語句。這就是經典的n+1次select查詢問題。 這種檢索策略沒有利用SQL的連接查詢功能,例如以上5條select語句完全可以通過以下1條select語句來完成:

select * from BLOG left outer join Author on BLOG.Author_ID=AUTHOR.Author_ID 

關聯的嵌套結果
使用嵌套結果來聯合查詢,比如左連接,右連接,內連接等。比如:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

  非常重要: id元素在嵌套結果映射中扮演着非常重要的角色。你應該總是指定一個或多個可以唯一標識結果的屬性。實際上如果你不指定它的話, MyBatis仍然可以工作,但是會有嚴重的性能問題。在可以唯一標識結果的情況下, 盡可能少的選擇屬性。主鍵是一個顯而易見的選擇(即使是復合主鍵)。
  現在,上面的示例用了外部的結果映射元素來映射關聯。這使得 Author 結果映射可以重用。然而,如果你不需要重用它的話。你可以嵌套結果映射:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
  </association>
</resultMap>

  如果blog有一個co-author怎么辦? select語句將看起來這個樣子:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio,
    CA.id           as co_author_id,
    CA.username     as co_author_username,
    CA.password     as co_author_password,
    CA.email        as co_author_email,
    CA.bio          as co_author_bio
  from Blog B
  left outer join Author A on B.author_id = A.id
  left outer join Author CA on B.co_author_id = CA.id
  where B.id = #{id}
</select>

  再次調用Author的resultMap將定義如下:

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

  當連接多表時,你將不得不使用列別名來避免ResultSet中的重復列名。指定columnPrefix允許你映射列名到一個外部的結果集中。你需要指定columnPrefix去重用映射resultMap。

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author"
    resultMap="authorResult" />
  <association property="coAuthor"
    resultMap="authorResult"
    columnPrefix="co_" />
</resultMap>

  上面已經看到了如何處理“has-one”類型關聯。但是“has-many”是怎樣的,比如一個作者只有一個博客賬號,但是卻有多篇文章?下面這個部分就是來討論這個主題的。

3.7.3 集合

<collection property="posts" ofType="domain.blog.Post">
  <id property="id" column="post_id"/>
  <result property="subject" column="post_subject"/>
  <result property="body" column="post_body"/>
</collection>

  一個作者有很多文章,那么結果返回的接口寫法如下:

private List<Post> posts;

  集合也有嵌套查詢和嵌套結果,我們掌握后者:

<select id="selectBlog" resultMap="blogResult">
  select
  B.id as blog_id,
  B.title as blog_title,
  B.author_id as blog_author_id,
  P.id as post_id,
  P.subject as post_subject,
  P.body as post_body,
  from Blog B
  left outer join Post P on B.id = P.blog_id
  where B.id = #{id}
</select>

我們聯合了博客表和文章表,每個博客賬號有多篇文章,和association相比唯一不同點在ofType="Post":

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

下面的寫法方便重用:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
  <id property="id" column="id"/>
  <result property="subject" column="subject"/>
  <result property="body" column="body"/>
</resultMap>

3.7.4 鑒別器

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultType="carResult">
      <result property="doorCount" column="door_count" />
    </case>
    <case value="2" resultType="truckResult">
      <result property="boxSize" column="box_size" />
      <result property="extendedCab" column="extended_cab" />
    </case>
    <case value="3" resultType="vanResult">
      <result property="powerSlidingDoor" column="power_sliding_door" />
    </case>
    <case value="4" resultType="suvResult">
      <result property="allWheelDrive" column="all_wheel_drive" />
    </case>
  </discriminator>
</resultMap>

  有時一個單獨的數據庫查詢也許返回很多不同 (但是希望有些關聯) 數據類型的結果集。鑒別器元素就是被設計來處理這個情況的, 還有包括類的繼承層次結構。 鑒別器非常容易理 解,因為它的表現很像 Java 語言中的 switch 語句。

3.7.5 自動映射

  在簡單的場景下,MyBatis可以替你自動映射查詢結果。 如果遇到復雜的場景,你需要構建一個result map。當自動映射查詢結果時,MyBatis會獲取sql返回的列名並在java類中查找相同名字的屬性(忽略大小寫)。 這意味着如果Mybatis發現了ID列和id屬性,Mybatis會將ID的值賦給id。
  通常數據庫列使用大寫單詞命名,單詞間用下划線分隔;而java屬性一般遵循駝峰命名法。 為了在這兩種命名方式之間啟用自動映射,需要將 mapUnderscoreToCamelCase設置為true。
  自動映射的功能也能夠在特殊的resultMap下繼續工作。在這種情況下,對於每一個結果映射的集合,所有出現在結果集當中的列,如果沒有被手動的設置映射,那么它都會被自動的映射。 在接下來的例子中, id 和 userName列將被自動映射, hashed_password 列將根據配置映射。

<select id="selectUsers" resultMap="userResultMap">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password
  from some_table
  where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
  <result property="password" column="hashed_password"/>
</resultMap>

有三種自動映射等級

NONE - 禁用自動映射。僅設置手動映射屬性。
PARTIAL - 會自動的映射結果,除了那些定義在內部的已經存在嵌套的映射(默認)
FULL - 自動映射所有(但當不同表有相同的列名時容易出錯,別用)。
通過添加autoMapping屬性可以忽略自動映射等級配置,你可以啟用或者禁用自動映射指定的ResultMap。

<resultMap id="userResultMap" type="User" autoMapping="false">
  <result property="password" column="hashed_password"/>
</resultMap>

3.8 緩存

  mybaits提供一級緩存,和二級緩存。

  一級緩存是SqlSession級別的緩存。在操作數據庫時需要構造 sqlSession對象,在對象中有一個(內存區域)數據結構(HashMap)用於存儲緩存數據。不同的sqlSession之間的緩存數據區域(HashMap)是互相不影響的。在同一個sqlSession中兩次執行相同的sql語句,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次會從緩存中獲取數據將不再從數據庫查詢,從而提高查詢效率。當一個sqlSession結束后該sqlSession中的一級緩存也就不存在了。Mybatis默認開啟一級緩存。但如果開啟了二級緩存,那么在關閉sqlsession后,會把該sqlsession一級緩存中的數據添加到namespace的二級緩存中。
  對sqlsession執行commit操作,也就意味着用戶執行了update、delete等操作,那么數據庫中的數據勢必會發生變化,如果用戶請求數據仍然使用之前內存中的數據,那么將讀到臟數據。所以在執行sqlsession操作后,會清除保存數據的HashMap,用戶在發起查詢請求時就會重新讀取數據並放入一級緩存中了。
如何開啟二級緩存:

1、 在mybatis總配置文件中加入一行設置
<settings>
   <!--開啟二級緩存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

2、在需要開啟二級緩存的mapper.xml中加入caceh標簽
<cache/>

  二級緩存是mapper級別的緩存,按namespace分,如果namespace相同則使用同一個相同的二級緩存區,多個SqlSession去操作數據庫得到數據會存在二級緩存區域。注意:即使開啟了二級緩存,不同的sqlsession之間的緩存數據也不是想互訪就能互訪的,必須等到sqlsession關閉了以后,才會把其一級緩存中的數據寫入二級緩存。

但是我們並不使用mybaits提供的二級緩存,理由如下:

  1. 緩存是以namespace為單位的,不同namespace下的操作互不影響。例如UserMapper.xml包含一個命名空間,所有針對user表的insert,update,delete操作都在這個命名空間下。假設另外一個XXXMapper.xml對user表的內容進行select查詢,並將查詢結果二級緩存。然后我們對user表進行insert,update或者delete操作,insert,update,delete操作會清空所在namespace下的全部緩存,但是XXXMapper.xml命名空間下的緩存卻沒有變化,導致XXXMapper.xml再次查詢錯誤。
  2. 多表操作不能使用緩存。比如我要查詢某一個作者的全部文章,作者為一張表,文章為一張表。首先,對不同表的增刪改一般放到不同的namespace,原因是:假設我將多表的全部操作放到一個namespace,那么我對任意一張表的增刪改都會觸發清空這個namespace的全部緩存,導致緩存一直在變,那我就要一直查表,那要緩存也沒意義了。然后,假設將查詢某一個作者的全部文章這一操作放到作者所在的那個namespace,那么文章表的增刪改由於和查詢某一個作者的全部文章這一操作不在同一namespace,導致這一操作的二級緩存不變,查詢錯誤。
      所以放棄Mybatis二級緩存,在業務層使用可控制的二級緩存代替更好,即服務器上的緩存推薦使用redis或者ehcache做分布式二級緩存


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM