Web基礎之Mybatis

Web基礎之Mybatis

  對比JdbcTempalte,mybatis才能稱得上是框架,JdbcTempalte頂多算是工具類,同時,對比Hibernate,Mybatis又有更多的靈活性,算是一種折中方案。html

特色:java

  1. 支持自定義SQL、存儲過程、及高級映射
  2. 實現自動對SQL的參數設置
  3. 實現自動對結果集進行解析和封裝
  4. 經過XML或者註解進行配置和映射,大大減小代碼量
  5. 數據源的鏈接信息經過配置文件進行配置

mybatis總體結構:mysql

mybatis

主配置文件
web


mybatis-config.xml

依賴sql

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

主配置文件數據庫

<?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>
  <!-- 環境:說明能夠配置多個,default:指定生效的環境 -->
  <environments default="development">
    <!-- id:環境的惟一標識 -->
    <environment id="development">
      <!-- 事務管理器,type:類型 -->
      <transactionManager type="JDBC" />
      <!-- 數據源:type-池類型的數據源 -->
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis" />
        <property name="username" value="root" />
        <property name="password" value="root" />
      </dataSource>
    </environment>
  </environments>
  <!-- 映射文件 -->
  <mappers>
     <mapper resource="UserMapper.xml"/>
  </mappers>
</configuration>

映射文件:
apache


UserMapper.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">
<!-- namespace(命名空間):映射文件的惟一標識 -->
<mapper namespace="UserMapper">

  <!-- 查詢的statement,id:在同一個命名空間下的惟一標識,resultType:sql語句的結果集封裝類型,這裏須要全名 -->
  <select id="queryUserById" resultType="cn.bilibili.mybatis.pojo.User">
    select * from tb_user where id = #{id}
  </select>
  
</mapper>

使用slf4j12記錄日誌:
數組


log4j.properties

依賴緩存

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.4</version>
</dependency>

配置文件session

log4j.rootLogger=DEBUG,A1
log4j.logger.org.apache=DEBUG
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.Target=System.err
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n

測試方法:


@Test

SqlSession sqlSession = null;
try {
    // 指定mybatis的全局配置文件
    String resource = "mybatis-config.xml";
    // 讀取mybatis-config.xml配置文件
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 構建sqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 獲取sqlSession會話
    sqlSession = sqlSessionFactory.openSession();
    // 執行查詢操做,獲取結果集。參數:1-命名空間(namespace)+「.」+statementId,2-sql的佔位符參數
    User user = sqlSession.selectOne("UserMapper.queryUserById", 1L);
    System.out.println(user);
} finally {
    // 關閉鏈接
    if (sqlSession != null) {
        sqlSession.close();
    }
}

  大體就是經過SqlSessionFactoryBuilder類得到sql會話工廠,經過sqlSession執行sql語句,而要執行的語句及其映射bean都已經配置在xml裏面。須要注意的是mybatis默認開啓事務,因此執行增刪改時須要手動提交。

Dao接口映射

  mybatis能夠直接映射Dao接口,而沒必要寫其實現類(實際上是mybatis幫咱們實現了啦)

Dao接口:


UserMapper

public interface UserMapper {
    public User queryUserById(Long id);

    public List<User> queryUserList();

    public void insertUser(User user);

    public void updateUser(User user);

    public void deleteUserById(Long id);
}

映射配置文件

<?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="UserMapper">
    <!-- id和方法名對應,而且惟一,所以不能有重載方法;返回類型和resultType對應; -->
    <select id="queryUserById"  resultType="cn.bilibili.mybatis.pojo.User">
        select * from tb_user where id = #{id}
    </select>

    ...

</mapper>

Tips:在IDEA中可使用Ctrl + Shift + T快速建立測試用例

數據庫字段和Bean屬性名稱不一致問題

  • sql語句查詢使用別名
  • 在主配置文件中開啓駝峯匹配:
  • 自定義resultMap映射
<settings>
    <!-- 開啓駝峯匹配 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

mybatis主配置

配置文檔的頂層結構以下:

mybatis頂層結構

具體能夠參考官方文檔
這裏介紹幾個經常使用的屬性。

properties

屬性:properties,能夠定義變量,而後使用${變量名}來取得其值,如:

<properties resource = "jdbc.properties" />

jdbc.properties內容:

driverClass = com.mysql.jdbc.Driver
url = jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8
username = root
password = 1234

而後即可以經過${driverClass}來配置驅動了。

settings

包含<setting>標籤,有namevalue屬性

mapUnderscoreToCamelCase

駝峯匹配(默認關閉)

<setting name="mapUnderscoreToCamelCase" value="true"/>

lazyLoadingEnabled

延遲加載(默認關閉)

<setting name="lazyLoadingEnabled" value="true"/>

cacheEnabled

二級緩存(默認開啓)

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

autoMappingBehavior

自動映射規則:

name屬性 描述 value屬性 默認value
autoMappingBehavior 指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示取消自動映射;PARTIAL 只會自動映射沒有定義嵌套結果集映射的結果集。 FULL 會自動映射任意複雜的結果集(不管是否嵌套)。 NONE, PARTIAL, FULL PARTIAL

什麼沙雕排版😅

typeAliases

  類型別名是爲 Java 類型設置一個短的名字。 它只和 XML 配置有關,存在的意義僅在於用來減小類徹底限定名的冗餘。

<typeAliases>
    <!-- 第一種 -->
    <typeAlias alias="User" type="com.bilibili.pojo.User"/>
    <!-- 第二種 -->
    <package name="com.bilibili.pojo"/>
</typeAliases>

  第一種是直接配置一個類的別名,第二種是配置掃描一個包下的全部類
或者註解方式配置別名:

@Alias("User")
public class User {
    ...
}

以及數據類型的別名(不區分大小寫,基本數據類型特殊命名):


數據類型別名

別名 映射的類型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

environments

Mybatis能夠配置多個環境,可是一個SQLSessionFactory只對應一個環境

<!-- 能夠配置多個環境,並設置默認環境 -->
<environments default="development">
    <!-- 配置環境,id:環境的惟一標識 -->
    <environment id="development">
        <!-- 事務管理器,type:使用jdbc的事務管理器 -->
        <transactionManager type="JDBC">
            <property name="..." value="..."/>
        </transactionManager>
        <!-- 數據源,type:池類型的數據源 -->
        <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>

能夠經過build方法的重載指定環境:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environmentID);
//reader爲主配置文件流,environment爲environmentID,properties爲讀取的變量文件,三個參數的任一改變都能改變環境
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environmentID, properties);

若是忽略了環境參數,那麼默認環境將會被加載,以下所示:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);

mappers

mapper的做用是告訴mybatis去哪裏照執行的SQL語句,能夠經過下面四種方式:

相對路徑的xml文件引用(resources目錄):

<!-- 使用相對於類路徑的資源引用,相對的是resources目錄 -->
<mappers>
    <mapper resource="UserMapper.xml"/>
</mappers>

絕對路徑的xml文件引用(不推薦):

<!-- 使用徹底限定資源定位符(URL) -->
<mappers>
    <mapper url="file:///D:/UserMapper.xml"/>
</mappers>

經過接口路徑

<!-- 使用映射器接口實現類的徹底限定類名 -->
<mappers>
    <mapper class="com.bilibili.mapper.UserMapper"/>
</mappers>

此方式條件:

  • 映射文件和mapper接口在同一個目錄下(或者resources下的同目錄)
  • 文件名一致
  • 映射文件的namespace必須和mapper接口的全路徑保持一致

經過接口所在包的路徑

<!-- 將包內的映射器接口實現所有註冊爲映射器 -->
<mappers>
    <package name="com.bilibili.mapper"/>
</mappers>

注意事項

  使用動態代理的方式實現接口映射時,mapper.xml文件的命名空間必須是接口的全限定名,而且不能使用typeAliases別名,由於別名是針對Java Bean

  前兩種爲直接指定xml文件位置,所以對xml路徑沒有什麼要求。
  後兩種爲指定接口而後尋找xml文件,所以xml須要在和接口相同的路徑(相對resources),而且!idea中resources目錄不能使用.(點)來創建多級目錄,須要使用/或者\來創建多級目錄!!!

mapper xml 映射文件

映射文件的結構:

  • cache – 對給定命名空間的緩存配置。
  • cache-ref – 對其餘命名空間緩存配置的引用。
  • resultMap – 是最複雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。
  • parameterMap – 已被廢棄!老式風格的參數映射。更好的辦法是使用內聯參數,此元素可能在未來被移除。文檔中不會介紹此元素。
  • sql – 可被其餘語句引用的可重用語句塊。
  • insert – 映射插入語句
  • update – 映射更新語句
  • delete – 映射刪除語句
  • select – 映射查詢語句

select

示例:

<select id="queryUserById" resultType="User">
    SELECT * FROM user WHERE ID = #{id}
</select>
  • id:在命名空間中惟一的標識符,能夠被用來引用這條語句。
  • resultType:返回類型
  • parameterType:參數類型,能夠省略
  • resultMap:結果映射,mybatis最強大的特性,不能夠和resultType同時使用

insert

示例:

<insert id = "addUser" useGeneratedKeys = "true" keyColumn = "id" keyProperty = "id" parameterType = "User">
    INSERT INTO user (
        id, 
        username, 
        password
    ) VALUES (
        NULL, 
        #{userName}, 
        #{password}
    )
</insert>
  • id:惟一標識符
  • useGeneratedKeys:開啓主鍵自增回顯,將自增加的主鍵值回顯到形參中(即封裝到User對象中)[可選]
  • keyColumn:數據庫中主鍵的字段名稱 [可選]
  • keyProperty:pojo中主鍵對應的屬性 [可選]
  • parameterType:參數類型 [可選]

update & delete

示例:

<update id = "updateUserById" parameterType = "User">
    UPDATE 
        user 
    SET 
        username = #{username},
        password = #{password}
    WHERE
        id = #{id}
</update>

<delete id = "deleteUserById" parameterType = "long">
    DELETE FROM user WHERE id = #{id}
</delete>

parameterType屬性

CRUD都有個parameterType標籤,用來指定接受的參數類型。

接收參數有兩種方式:

  1. #{ }預編譯,相似佔位符
  2. ${ }非預編譯,直接sql拼接,不能防止SQL注入,通常用來接收表名等屬性

參數類型有三種:

  1. 基本數據類型
  2. HashMap(使用方式和pojo相似)
  3. Pojo自定義包裝類

基本數據類型

當sql方法的參數是多個時:

例如queryUserByUserNameAndPassword(String username, String password)這種兩個參數時,能夠這樣接收參數:

<select id="queryUserByUserNameAndPassword" resultType="User">
    SELECT * FROM user WHERE username = #{0}/#{arg0}/#{param1} AND password = #{1}/#{arg1}/#{param2}
</select>

不能見名知意的變量不是好方法,因此咱們的解決方案是添加@Param註解:

queryUserByUserNameAndPassword(@Param(username) String username, @Param(password)String password)
<select id="queryUserByUserNameAndPassword" resultType="User">
    SELECT * FROM user WHERE username = #{username} AND password = #{password}
</select>

這樣就能夠見名知意了。

HashMap參數

示例:

User loginMap(Map<String,String> map); 
/****************************************/
Map<String, String> map = new HashMap<>();
map.put("userName","zhangsan");
map.put("password","123456");
User user = userMapper.loginMap(map);

xml中和註解用法相似:

<select id="loginMap" resultType="User">
    SELECT * FROM user WHERE username = #{userName} AND password = #{password}
</select>

Pojo

mapper.xml用法不變,xml是經過Getter方法來獲取值的。

${}的用法

一個參數時,默認狀況下使用${value}接收數據。可是這樣不能見名知意。
一樣使用@Param()註解。

${ }#{ }

  • #{ }
    • 預編譯
    • 編譯成佔位符
    • 能夠防止sql注入
    • 自動判斷數據類型(參數時字符串時會自動加字符串)
    • 一個參數時,可使用任意參數名稱進行接收(即#{xxx})
  • ${ }SQL拼接(不能防止SQL注入)
    • 非預編譯
    • sql的直接拼接
    • 不能防止sql注入
    • 須要判斷數據類型,若是是字符串,須要手動添加引號。
    • 一個參數時,參數名稱必須是value,才能接收參數。

  $還有一個問題是,在主配置文件中,使用${driver}獲取資源文件的驅動或者url等數據,若是用戶的password屬性和資源文件中的password屬性同名時,此時會讀取資源文件中的password而不會使用傳入的參數password,解決方法是在資源文件中的屬性都加入前綴:

jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8
jdbc.username = root
jdbc.password = 1234

固然,主配置文件裏面的引用也要修改。

ResultMap

resultMap是mybatis中最強大的特性,能夠很方便的解決下面兩個問題:

  • Pojo屬性名和表結構字段名不一致(有時候不僅是駝峯格式)
  • 高級查詢(主要是這個)

簡單映射示例:

在映射文件中配置自定義ResultMap:

<resultMap id="userResultMap" type="User" autoMapping="true">
    <!--配置主鍵映射關係,配置主鍵能夠增長查詢效率-->
    <id column="id" property="id"></id>
    <!--配置普通字段的映射關係-->
    <result column="user_name" property="userName"></result>
</resultMap>

autoMapping屬性:

  • 爲true時:resultMap中的沒有配置的字段會自動對應。若是不配置,則默認爲true。
  • 爲false時:只針對resultMap中已經配置的字段做映射。

在查詢語句中使用自定義映射:

<!-- resultMap屬性:引用自定義結果集做爲數據的封裝方式 -->
<select id="queryUserById" resultMap="userResultMap">
    select * from tb_user where id = #{id}
</select>

高級查詢

一對一映射

  當訂單(Order)對象內有用戶(User)屬性時,以前的狀況咱們是不能一次查詢出來的,可是有了映射即可以很方便的查詢:

<!-- id:惟一標識,type:返回類型,autoMapping:自動映射 -->
<resultMap id="orderResultMapper" type="Order" autoMapping="true">
<!-- 主鍵映射 -->
    <id column="id" property="id"/>
    <!-- 通常屬性映射 -->
    <result column="order_number" property="orderNumber"/>
    <result column="user_id" property="userId"/>
    <!-- 一對一映射,property:屬性,javaType:屬性類型 -->
    <association property="user" javaType="User" autoMapping="true">
        <!-- 主鍵映射,寫法和resultMap同樣 -->
        <id column="user_id" property="id"/>
    </association>
</resultMap>

<select id="queryOrderByOrderNumber" resultMap="orderResultMapper">
    SELECT *
    FROM tb_order o,
         tb_user u
    WHERE o.user_id = u.id
        AND o.order_number = #{orderNumber}
</select>

一對多映射

  當一個訂單內有多個信息時,即Order類中持有List<OrderDetail>,即可以進行一對多映射。
  這裏的訂單能夠理解爲多個商品一次下單,這一訂單中有多個OrderDetail,每一個OrderDetail對應一個商品

<resultMap id="orderResultMapper2" type="Order" autoMapping="true">
    <!-- 主鍵的字段使用SQL語句的中的別名 -->
    <id column="oid" property="id"/>

    <association property="user" javaType="User" autoMapping="true">
        <id column="uid" property="id"/>
    </association>
    <!-- 一對多映射,property:類中的屬性,javaType:該屬性對應的Java類型,ofType:該屬性存儲的類型,也就是泛型 -->
    <collection property="orderDetailList" javaType="list" ofType="OrderDetail" autoMapping="true">
        <id column="did" property="id"/>
    </collection>
</resultMap>

<select id="queryOrderAndUserAndOrderDetailsByOrderNumber" resultMap="orderResultMapper2">
    <!-- 當表數量較多時,須要指定不一樣表主鍵的別名來區分 -->
    SELECT *, o.id oid, detail.id did, u.id uid
    FROM tb_order o,
         tb_orderdetail detail,
         tb_user u
    where o.order_number = #{orderNumber}
        AND o.user_id = u.id
        AND detail.order_id = o.id;
</select>

注意,當表數量較多時,須要指定不一樣表主鍵的別名來區分。

多對多映射

每一個OrderDetail對應一個商品,這時即可以添加映射:

<resultMap id="orderResultMapper3" type="Order" autoMapping="true">
    <id column="oid" property="id"/>

    <association property="user" javaType="User" autoMapping="true">
        <id column="uid" property="id"/>
    </association>

    <collection property="orderDetailList" javaType="list" ofType="OrderDetail" autoMapping="true">
        <id column="did" property="id"/>
        <!-- 添加一對一映射 -->
        <association property="item" javaType="Item" autoMapping="true">
            <id column="iid" property="id"/>
        </association>
    </collection>

</resultMap>

<select id="queryOrderAndUserAndOrderDetailAndItemByOrderNumber" resultMap="orderResultMapper3">
    SELECT *, o.id oid, detail.id did, u.id uid, item.id iid
    FROM tb_order o,
         tb_user u,
         tb_orderdetail detail,
         tb_item item
    where o.order_number = #{orderNumber}
        and o.user_id = u.id
        and detail.order_id = o.id
        and detail.item_id = item.id
</select>

繼承

從上面的代碼能夠看出這樣映射雖然很方便,可是代碼存在冗餘的狀況:

冗餘

  圖中的代碼咱們已經在其餘映射中配置過了,當後面的映射須要這一段時,咱們即可以使用繼承。
  修改後的第三個映射的resultMap爲:

<!-- 添加extends屬性,即可以映射重用 -->
<resultMap id="orderResultMapper3" type="Order" autoMapping="true" extends="orderResultMapper">

    <collection property="orderDetailList" javaType="list" ofType="OrderDetail" autoMapping="true">
        <id column="did" property="id"/>

        <association property="item" javaType="Item" autoMapping="true">
            <id column="iid" property="id"/>
        </association>
    </collection>

</resultMap>

延遲加載

  延遲加載是指當咱們須要哪部分數據時,而後再去查。
  上面的幾個映射,每次查詢時會一股腦將數據所有查詢出來,即便不須要的數據也會查詢(由於只有一條語句)。當咱們查詢訂單時,須要用戶信息的時候再去查,所以SQL語句須要拆成兩條。

主配置文件中開啓延遲加載:

<settings>
    <!-- 開啓延遲加載(默認關閉) -->
    <setting name="lazyLoadingEnabled" value="true" />
    <!-- 關閉使用任意屬性,就加載延遲對象的功能。(在3.4.1及以前的版本默認值爲 true,3.4.1以後的版本不須要配置此項) -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

延遲加載示例:

<resultMap id="orderLazyUser" type="Order" autoMapping="true">
    <id column="id" property="id"/>
    <!-- property:屬性,select:延遲加載對象依賴的SQL語句,column:傳進去的參數 -->
    <association property="user" select="queryUserById" column="user_id" autoMapping="true">
        <id column="id" property="id"/>
    </association>
</resultMap>

<select id="queryOrderLazyUser" resultMap="orderLazyUser">
    SELECT * FROM tb_order where order_number = #{orderNumber}
</select>

<select id="queryUserById" resultType="User">
    SELECT * FROM tb_user WHERE id = #{id}
</select>

若是報錯須要添加cglib依賴(3.3以上不須要添加此依賴):

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>

SQL 片斷

對於使用很頻繁的SQL語句,能夠單獨抽離出來進行復用。

在同一個mapper.xml中:

<sql id = "testSql">
    id,
    username,
    password,
    ...
</sql>

<select id = "queryAllUser" resultType = "User">
    SELECT <include refid = "testSql"></include> FROM user
</select>

可是這樣只能在一個mapper文件中使用,所以咱們能夠把常用的寫入在一個mapper文件中:

CommonSQL.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="CommonSQL">
    <sql id="testSql">
        id,
        user_name,
        password,
        ...
    </sql>
</mapper>

在主配置中引入:

<mappers>
    <mapper resource = "CommonSQL" />
</mappers>

而後就能夠在mapper中使用:

<select id = "queryAllUser" resultType = "User">
    SELECT <include refid = "CommonSQL.testSql"></include> FROM user
</select>

refid = "namespace.sqlID"變一下便可。

XML 特殊字符

感受這個好麻煩啊,由於這些字符在SQL中出現的頻率過高了

符號 轉義
< &lt;
> &gt;
& &amp;
' &apos;
" &quot;

或者使用CDATA:<![CDATA[]]>

真是反人類。。。

動態SQL

  mybatis中也能夠動態拼接SQL語句(可是在xml中寫SQL太難受了),支持ognl表達式(Struts不就是由於這東西才被黑客利用爆出漏洞死掉的麼,還用。。。

if

示例:

<select id="queryMaleUserByUserNameOrId" resultType="User" >
    select * from tb_user where sex = 1
    <!-- if標籤,條件判斷,test屬性編寫ognl表達式  -->
    <if test="userName != null and userName.trim() != ''" >
        and user_name like #{userName}
    </if>
    <if test="id != null and id.trim() != '' " >
        and id like #{id}
    </if>
</select>

上面的SQL能夠經過用戶名或者ID來查詢。

choose, when, otherwise

  至關於switch, case, default,只不過自動break,一旦有一個when成立,後續的when都再也不執行。
示例:

<select id="queryMaleUserByIdOrUserName" resultType="User">
    select * from tb_user where sex = 1 
    <choose>
        <when test="id != null and id.trim()!='' " >
            and id like #{id}
        </when>
        <when test="userName != null and userName.trim()!=''" >
            and user_name like #{userName}
        </when>
        <otherwise>
            and active = 1 
        </otherwise>
    </choose>
</select>

  上面的SQL意思是若是提供了id就用id查詢,沒提供id就用用戶名查詢,都沒有的話則查詢全部的激活用戶。

where, set, trim

第一個查詢若是把性別也改成動態的:

<select id="queryUserByUserNameOrId" resultType="User" >
    select * from tb_user where 
    <if test = "sex != null and id.trim() != '' " >
        sex = #{sex}
    </if>
    <if test = "userName != null and userName.trim() != '' " >
        and user_name like #{userName}
    </if>
    <if test = "id != null and id.trim() != '' " >
        and id like #{id}
    </if>
</select>

若是sex參數爲空的話這條SQL語句就變成了這樣:

select * from tb_user where 
and user_name like #{userName}
and id like #{id}

有點逗比是否是,此時<where>標籤的做用就體現出來了:

<select id="queryUserByUserNameOrId" resultType="User" >
    select * from tb_user
    <where>
        <if test = "sex != null and id.trim() != '' " >
            sex = #{sex}
        </if>
        <if test = "userName != null and userName.trim() != '' ">
            and user_name like #{userName}
        </if>
        <if test = "id != null and id.trim() != '' " >
            and id like #{id}
        </if>
    </where>
</select>

  where 元素只會在至少有一個子元素的條件返回 SQL 子句的狀況下才去插入「WHERE」子句。並且,若語句的開頭爲「AND」或「OR」,where 元素也會將它們去除。

  若是 where 元素沒有按正常套路出牌,咱們能夠經過自定義 trim 元素來定製 where 元素的功能。好比,和 where 元素等價的自定義 trim 元素爲:

<trim prefix="WHERE" prefixOverrides="AND | OR ">
    ...
</trim>

<set>標籤和where相似:

<update id = "changePasswordOrUserName">
    UPDATE user 
    <set>
        <if test = "password != null">
            password = #{password},
        </if>
        <if test = "username != null">
            username = #{username}
        </if>
    </set>
    WHERE id = #{id}
</update>

  set 元素會動態前置 SET 關鍵字,同時也會刪掉無關的逗號,由於用了條件語句以後極可能就會在生成的 SQL 語句的後面留下這些逗號。

上面的句子就至關於:

<trim prefix = "SET" suffixOverrides = ",">
    ...
</trim>

foreach

動態 SQL 的另一個經常使用的操做需求是對一個集合進行遍歷,一般是在構建 IN 條件語句的時候。好比:

<select id="selectUserIn" resultType="User">
  SELECT *
  FROM user
  WHERE id in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

能夠迭代Collection、數組、Map,當參數爲Map時,index爲鍵,item爲值。

緩存

一級緩存
  緩存是sqlSession級別,默認開啓(沒法關閉?),可使用session.clearCache()方法清除緩存。對於同一條數據再次查詢會查詢緩存裏的數據。須要注意的是增、刪、改語句都會清除緩存,即便是不一樣數據。

二級緩存
  二級緩存須要Pojo對象實現Serializable接口,能夠實現不一樣sqlSession間公用緩存。當第一個sqlSession查詢一條數據後調用sqlSession.close()方法會將數據添加到二級緩存,第二個sqlSession再次查詢同一數據時會使用緩存。(數據增刪改一樣會狀況二級緩存)

開啓二級緩存:

<settings>
    <!-- 開啓二級緩存,默認是開啓的 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

註解方式使用Mybatis

註解須要在主配置文件中添加包掃描。

@Param

給參數添加別名(估計是由於反射不能獲取接口中聲明的局部變量名稱
示例:

User queryUserById(@Param("id") Integer id);

@Select、@Delete、@Update、@Insert

@Select("select * from tb_user where id = #{id}")
User queryUserById(@Param("id") Integer id);

@Delete("DELETE FROM tb_user WHERE id = #{id}")
int deleteUserById(@Param("id") Integer id);

@Update("UPDATE tb_user SET name = #{name} WHERE id = #{id}")
int updateNameById(@Param("name") String name, @Param("id") Integer id);

/*
@Options:參數配置
    useGeneratedKeys:主鍵回寫(默認false)
    keyColumn:主鍵字段名
    keyProperty:主鍵屬性(默認id)
*/
@Insert("insert into tb_user(user_name,password,name) values (#{userName}, #{password}, #{name}) ")
@Options(useGeneratedKeys = true,keyColumn = "id")
int addUser(User user);

@Results註解別名映射

@Select("select id uid,user_name,password pwd from tb_user where id=#{id}")
/*
Results:定義結果集,內部是一個Result註解的數組
    Result:結果集映射關係
        column:列名
        property:屬性名
*/
@Results({
    @Result(column = "uid",property = "id"),
    @Result(column = "user_name",property = "userName"),
    @Result(column = "pwd",property = "password")
})
public User findUserByIdResultMap(@Param("id") Long id);

註解高級查詢

一對一映射

@Select("select * from tb_order where order_number = #{orderNumber}")
@Results({
        /*
        一對一映射調用其餘接口的方法
        column:傳入的參數
        property:返回對應的屬性
        one表明一對一,值爲@One註解
            @One:一對一註解
                select:引用的查詢方法
        */
        @Result(column = "user_id",property = "user", one = @One(select = "com.bilibili.mybatis.mapper.UserMapper.queryUserById"))
})
Order queryOrderAndUserByOrderNumber(@Param("orderNumber") String orderNumber);

UserMapper接口:

//被調用的方法
public interface UserMapper {
    @Select("select * from tb_user where id = #{id}")
    User queryUserById(@Param("id") Integer id);
}

一對多映射

@Select("select * from tb_order where order_number = #{orderNumber}")
@Results({
        @Result(column = "id", property = "id"),
        //一對一映射
        @Result(column = "user_id",property = "user",one = @One(select = "com.bilibili.mybatis.mapper.UserMapper.queryUserById")),
        /*
        一對多映射
        many表明一對多,值爲@Many註解
            @Many:一對多註解
                select:引用的方法
        */
        @Result(column = "id", property = "orderDetailList", many = @Many(select = "com.bilibili.mybatis.mapper.OrderDetailMapper.queryOrderDetailsByOrderId"
        ))
})
Order queryOrderAndUserAndOrderDetailsByOrderNumber(@Param("orderNumber") String orderNumber);

OrderDetailMapper接口:

//引用的方法
public interface OrderDetailMapper {
    @Select("select * from tb_orderdetail where order_id = #{oid}")
    @Results({
            @Result(column = "id",property = "id"),
    })
    List<OrderDetail> queryOrderDetailsByOrderId(@Param("oid") Integer oid);
}

多對多映射

多對多隻須要在被引用的一對多方法裏添加一對一便可:

public interface OrderDetailMapper {
    @Select("select * from tb_orderdetail where id = #{id}")
    OrderDetail queryOrderDetailById(@Param("id") Integer id);

    @Select("select * from tb_orderdetail where order_id = #{oid}")
    @Results({
            @Result(column = "id",property = "id"),
            //添加一對一
            @Result(column = "item_id", property = "item", one = @One(select = "com.bilibili.mybatis.mapper.ItemMapper.queryItemById"))
    })
    List<OrderDetail> queryOrderDetailsByOrderId(@Param("oid") Integer oid);
}

註解延遲加載

在@One註解裏添加fetchType屬性:

@Select("select * from tb_order where order_number = #{orderNumber}")
@Results({@Result(column = "user_id", property = "user", one = @One(
        select = "com.bilibili.mybatis.mapper.UserMapper.queryUserById",
        //添加延遲加載屬性
        fetchType = FetchType.LAZY
))
})
Order queryOrderAndUserByOrderNumber(@Param("orderNumber") String orderNumber);

fetchType屬性會覆蓋全局屬性,可選值有:

  • FetchType.LAZY:延遲加載
  • FetchType.EAGER:當即加載
  • FetchType.DEFAULT:默認屬性(即全局屬性)

註解動態SQL

註解方式使用動態SQL的話須要一個類來專門構建SQL語句:

//用來構建SQL語句的類
public class UserSqlBuilder {
    //想要在匿名內部類中訪問須要將變量聲明爲爲final
    public String queryUserByConditions(final User user) {
        //第一種方式,直接SQL拼接
        StringBuilder sqlSb = new StringBuilder("SELECT * FROM tb_user WHERE 1=1 ");
        if (user.getUserName() != null && user.getUserName().trim().length() > 0) {
            sqlSb.append(" AND user_name like #{userName} ");
        }
        if (user.getSex() != null) {
            sqlSb.append(" AND sex = #{sex} ");
        }
        return sqlSb.toString();

        //第二種方式,使用mybatis提供的類
        //注意下面的SELECT、FROM等都是方法,而且是寫在構造代碼塊裏的(猛地一看還真沒看明白)
        String sql = new SQL() {{
            SELECT("*");
            FROM("tb_user");
            if (user.getUserName() != null && user.getUserName().trim().length() > 0){
                WHERE("user_name like #{userName}");
            }
            if (user.getSex() != null){
                WHERE("sex = #{sex} ");
            }
        }}.toString();

        return sql;
    }
}

接口中的方法爲:

public interface UserMapper {
    //只需將@Select()註解替換爲@SelectProvider()便可
    //type:提供SQL語句的類,method:提供SQL的方法
    @SelectProvider(type = UserSqlBuilder.class,method = "queryUserByConditions")
    List<User> queryUserByConditions(final User user);
}

除了SelectProvider還有:

  • @InsertProvider
  • @UpdateProvider
  • @DeleteProvider
  • @SelectProvider

按需添加便可。

  若是@SelectProvider描述的抽象方法沒有使用@Param給變量添加別名,而且聲明瞭多個變量,那麼提供SQL的類的方法參數須要和接口中的方法同樣,也就是聲明出全部的變量:

public interface UserMapper {
    @SelectProvider(type = UserSqlBuilder.class,method = "queryUserByConditions")
    List<User> queryUserByConditions(final User user, final String name);
}

提供SQL的類的方法也須要聲明爲接口一樣的參數:

public class UserSqlBuilder {
    //提供SQL的方法也須要聲明兩個變量
    public String queryUserByConditions(final User user, final String name){
        ...
    }
}

  若是接口中的抽象方法使用了@Param參數,那麼類中提供SQL的方法即可以用哪一個參數聲明哪一個參數:

//接口
public interface UserMapper {
    @SelectProvider(type = UserSqlBuilder.class,method = "queryUserByConditions")
    List<User> queryUserByConditions(@Param("user") final User user, @Param("user") final String name);
}

提供SQL的類中的方法即可以這樣寫:

public class UserSqlBuilder {
    //只須要聲明本身須要的類即可以了
    public String queryUserByConditions(@Param("user") final User user){
        ...
    }
}

若是Mybatis的版本在3.5.1之後,能夠將這樣簡化:

映射的接口:

public interface UserMapper {
    @SelectProvider(UserSqlProvider.class)
    List<User> queryUserByConditions(final User user);
}

提供SQL的類:

//提供類繼承一個超類:ProviderMethodResolver
class UserSqlProvider implements ProviderMethodResolver {
    //這裏的方法名須要和接口中的方法名同樣
    public static String queryUserByConditions(final User user){
        ...
    }
}

仍是直接看官方的文檔比較好🤣:Mybatis 官方中文文檔

相關文章
相關標籤/搜索