1. 程式人生 > 實用技巧 >Mybatis自學之路(狂神說)

Mybatis自學之路(狂神說)

MyBatis自學之路

1 Mybatis簡介

1.1 mybatis是什麼?

  1. MyBatis 是一款優秀的持久層框架,它支援自定義 SQL、儲存過程以及高階對映。MyBatis 免除了幾乎所有的 JDBC 程式碼以及設定引數和獲取結果集的工作。MyBatis 可以通過簡單的 XML 或註解來配置和對映原始型別、介面和 Java POJO(Plain Old Java Objects,普通老式 Java 物件)為資料庫中的記錄。
  2. MyBatis 本是apache的一個開源專案iBatis, 2010年這個專案由apache software foundation 遷移到了google code,並且改名為MyBatis 。2013年11月遷移到Github。

1.2 myBatis優點

  • 簡單易學:本身就很小且簡單。沒有任何第三方依賴,最簡單安裝只要兩個jar檔案+配置幾個sql對映檔案易於學習,易於使用,通過文件和原始碼,可以比較完全的掌握它的設計思路和實現。
  • 靈活:mybatis不會對應用程式或者資料庫的現有設計強加任何影響。 sql寫在xml裡,便於統一管理和優化。通過sql語句可以滿足操作資料庫的所有需求。
  • 解除sql與程式程式碼的耦合:通過提供DAO層,將業務邏輯和資料訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。sql和程式碼的分離,提高了可維護性。
  • 提供對映標籤,支援物件與資料庫的orm欄位關係對映
  • 提供物件關係對映標籤,支援物件關係組建維護
  • 提供xml標籤,支援編寫動態sql

1.3 持久化

資料持久化

  • 持久化就是將程式的資料在持久狀態和瞬時狀態轉化的過程
  • 記憶體:斷電即失
  • 資料庫(Jdbc),io檔案持久化。

1.4 持久層

Dao層、Service層、Controller層

  • 完成持久化工作的程式碼塊
  • 層界限十分明顯

技術沒有高低之分,只有使用技術的人有高低之分

2 Mybatis第一個程式

2.1 環境搭建

  1. 準備資料庫

    create database mybatis;
    use mybatis;
    create table user(
        id int(10) primary key,
        `name` varchar(30),
        `password` varchar(30)
    )engine=INNODB default charset = utf8;
    insert into user values
                            (1,'zhangsan','123'),
                            (2,'lisi','123'),
                            (3,'wangwu','123');
    
  2. 新增環境依賴並建立子模組

        <dependencies>
            <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.4.6</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
  3. 從 XML 中構建 SqlSessionFactory

    • 每個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的例項為核心的。SqlSessionFactory 的例項可以通過 SqlSessionFactoryBuilder 獲得。而 SqlSessionFactoryBuilder 則可以從 XML 配置檔案或一個預先配置的 Configuration 例項來構建出 SqlSessionFactory 例項。

    • 建立mybatis-configuration.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>
          <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"/>
                  <dataSource type="POOLED">
                      <property name="driver" value="com.mysql.jdbc.Driver"/>
                      <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;characterEncoding=utf-8&amp;useUnicode=true"/>
                      <property name="username" value="root"/>
                      <property name="password" value="123456"/>
                  </dataSource>
              </environment>
          </environments>
          <mappers>
              <mapper resource="org/mybatis/example/BlogMapper.xml"/>
          </mappers>
      </configuration>
      
    • 建立MybatisUtil工具類

      public class MybatisUtil {
          //建立SqlSessionFactory例項
          private static  SqlSessionFactory sqlSessionFactory = null;
          static {
              String resource = "mybatis-configuration.xml";
              SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
              try {
                  sqlSessionFactory = factoryBuilder.build(Resources.getResourceAsStream(resource));
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
          //既然有了 SqlSessionFactory,顧名思義,我們可以從中獲得 SqlSession 的例項.
          // SqlSession 提供了在資料庫執行 SQL 命令所需的所有方法。
          public static SqlSession getSqlSession(){
              return sqlSessionFactory.openSession();
          }
      }
      

2.2 編寫查詢語句程式碼

  1. 準備實體類User

  2. 編寫dao介面

    public interface UserDao {
        List<User> getUserList();
    }
    
  3. 編寫dao介面對應的mapper.xml配置檔案

    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace 對應的 dao介面 id 方法名, 返回值的型別:填寫list中傳入的泛型-->
    <mapper namespace="com.iandf.dao.UserDao">
        <select id="getUserList" resultType="com.iandf.pojo.User">
            select *
            from mybatis.user
        </select>
    </mapper>
    
  4. 新增pom.xml新增配置,防止java目錄下的資源配置檔案匯出失敗

        <build>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                        <include>**/*.properties</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
            </resources>
        </build>
    
  5. 使用junit測試

        @org.junit.Test
        public void Test(){
            SqlSession sqlSession = MybatisUtil.getSqlSession();
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            List<User> userList = mapper.getUserList();
            for (User user : userList) {
                System.out.println(user.toString());
            }
        }
    

2.3 遇到的錯誤

  1. 類找不到,在終端執行 mvn idea:idea指令之後就能解決
  2. 配置檔案mybatis-configuration.xml找不到,沒有配置mapper 或者 resources資料夾沒有被標記為資原始檔夾

2.4 CRUD

  1. UserMapper介面類

    public interface UserMapper {
        List<User> getUserList();
        //insert
        int addUser(User user);
        //delete
        int deleteUserById(int id);
        //update
        int updateUserById(User user);
        //select
        User queryUserById(int id);
    }
    
  2. mapper.xml配置檔案

    <mapper namespace="com.iandf.dao.UserMapper">
        <select id="getUserList" resultType="com.iandf.pojo.User">
            select *
            from mybatis.user
        </select>
    
        <insert id="addUser"  parameterType="com.iandf.pojo.User">
           #  #{user.id}也可以省去物件名稱
           insert into mybatis.user(id, name, password) VALUES (#{user.id},#{name},#{password})
        </insert>
    
        <delete id="deleteUserById"  parameterType="int">
            delete from mybatis.user where id = #{id}
        </delete>
    
        <update id="updateUserById" parameterType="com.iandf.pojo.User">
            update mybatis.user set name = #{name},password = #{password} where id = #{id}
        </update>
    
        <select id="queryUserById" parameterType="int" resultType="com.iandf.pojo.User">
            select * from mybatis.user where id = #{id}
        </select>
    </mapper>
    
  3. 測試檔案

        @Test
        public void deleteUserByIdTest(){
            SqlSession sqlSession = MybatisUtil.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            int nums = mapper.deleteUserById(4);
            System.out.println(nums);
            sqlSession.commit();
        }
        
        @Test
        public void queryUserByIdTest(){
            try(SqlSession sqlSession = MybatisUtil.getSqlSession()) {
                UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                User user = mapper.queryUserById(4);
                System.out.println(user.toString());
            }
        }
    

note:

  1. 增刪改需要提交事務
  2. sqlSession相當於connection mapper相當於statement
  3. #{xxx} xxx必須與編寫的欄位和引數名相同 

2.5 使用萬能的map傳遞引數

使用map插入一條記錄

  1. dao介面

        int addUserByMap(Map<String,Object> map);
    
  2. mapper.xml

        <insert id="addUserByMap"  parameterType="map">
            insert into mybatis.user(id, name, password) VALUES (#{userID},#{userName},#{UserPassword})
        </insert>
    
  3. 測試案例

        @Test
        public void addUserTestByMap(){
            try(SqlSession sqlSession = MybatisUtil.getSqlSession()){
                UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                HashMap<String, Object> map = new HashMap<>();
                map.put("userID",5);
                map.put("userName","王五");
                map.put("UserPassword","123456");
                mapper.addUserByMap(map);
                sqlSession.commit();
            }
        }
    

NOTE:

  1. ​ 傳遞多個引數可以使用物件,也可以使用map
  2. ​ 傳遞一個引數,直接使用方法形參即可

2.6 模糊查詢

  1. 在傳遞引數時使用萬用字元,在sql語句中寫死

    1. mapper.xml

          <select id="queryUserByLikeName" resultType="com.iandf.pojo.User">
              select * from mybatis.user where name like #{value}
          </select>
      
    2. dao介面和測試程式碼

          //select
          List<User> queryUserByLikeName(String value);
          @Test
          public void queryUserByLikeNameTest(){
              try(SqlSession sqlSession = MybatisUtil.getSqlSession()) {
                  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                  List<User> users = mapper.queryUserByLikeName("%n%");
                  for (User user : users) {
                      System.out.println(user.toString());
                  }
              }
          }
      
  2. 在sql語句中進行拼接,傳遞引數時只需要傳使用者輸入部分,這樣sql在進行拼接,容易導致sql注入

    1. mapper.xml

          <select id="queryUserByLikeName2" parameterType="String" resultType="com.iandf.pojo.User">
              select * from mybatis.user where name like "%"#{value}"%"
          </select>
      
    2. dao介面和測試程式碼

          List<User> queryUserByLikeName2(String value);
              @Test
          public void queryUserByLikeName2Test(){
              try(SqlSession sqlSession = MybatisUtil.getSqlSession()) {
                  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                  List<User> users = mapper.queryUserByLikeName2("n");
                  for (User user : users) {
                      System.out.println(user.toString());
                  }
              }
          }
      

3 配置解析

3.1 核心配置檔案

Mybatis的配置檔案包含了會深深影響MyBatis行為的設定和屬性資訊。

configuration(配置)
    properties(屬性)
    settings(設定)
    typeAliases(類型別名)
    typeHandlers(型別處理器)
    objectFactory(物件工廠)
    plugins(外掛)
    environments(環境配置)
    	environment(環境變數)
    		transactionManager(事務管理器)
    		dataSource(資料來源)
    databaseIdProvider(資料庫廠商標識)
    mappers(對映器)

3.2 環境配置 environments

MyBatis 可以配置成適應多種環境

不過要記住:儘管可以配置多個環境,但每個 SqlSessionFactory 例項只能選擇一種環境

MyBatis預設的事務管理器就是JDBC ,連線池:POOLED

3.3 屬性 properties

我們可以通過properties屬性來實現引用配置檔案

這些屬性可以在外部進行配置,並可以進行動態替換。你既可以在典型的 Java 屬性檔案中配置這些屬性,也可以在 properties 元素的子元素中設定。【db.poperties】

  1. 編寫一個配置檔案

    db.properties

    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username=root
    password=root
    1234
    
  2. 在核心配置檔案中引入

    <!--引用外部配置檔案-->
    <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </properties>
    12345
    
    • 可以直接引入外部檔案
    • 可以在其中增加一些屬性配置
    • 如果兩個檔案有同一個欄位,優先使用外部配置檔案的

3.4 類型別名 typeAliases

  • 類型別名可為 Java 型別設定一個縮寫名字。 它僅用於 XML 配置.
  • 意在降低冗餘的全限定類名書寫。
<!--可以給實體類起別名-->
<typeAliases>
    <typeAlias type="com.kuang.pojo.User" alias="User"/>
</typeAliases>

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

<typeAliases>
    <package name="com.kuang.pojo"/>
</typeAliases>

在實體類比較少的時候,使用第一種方式。如果實體類十分多,建議用第二種掃描包的方式。

第一種可以DIY別名,第二種不行,如果非要改,需要在實體上增加註解。

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

3.5 對映器 mappers

MapperRegistry:註冊繫結我們的Mapper檔案;

方式一:【推薦使用】

<!--每一個Mapper.xml都需要在MyBatis核心配置檔案中註冊-->
<mappers>
    <mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>
1234

方式二:使用class檔案繫結註冊

<!--每一個Mapper.xml都需要在MyBatis核心配置檔案中註冊-->
<mappers>
    <mapper class="com.kuang.dao.UserMapper"/>
</mappers>

方式三:使用包掃描進行注入

<mappers>
    <package name="com.kuang.dao"/>
</mappers>

方法二和方法三的注意點:

  • 介面和他的Mapper配置檔案必須同名
  • 介面和他的Mapper配置檔案必須在同一個包下

3.6 作用域和生命週期

宣告週期和作用域是至關重要的,因為錯誤的使用會導致非常嚴重的併發問題

SqlSessionFactoryBuilder:

  • 一旦建立了SqlSessionFactory,就不再需要它了
  • 區域性變數

SqlSessionFactory:

  • 說白了就可以想象為:資料庫連線池
  • SqlSessionFactory一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立一個例項。
  • 因此SqlSessionFactory的最佳作用域是應用作用域(ApplocationContext)。
  • 最簡單的就是使用單例模式或靜態單例模式。

SqlSession:

  • 連線到連線池的一個請求
  • SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。
  • 用完之後需要趕緊關閉,否則資源被佔用!

4、解決屬性名和欄位名不一致的問題

1. 問題

資料庫中的欄位

實體類欄位

public class User {
    private int id;
    private String name;
    private String pwd;
}
<select id="queryUserById" parameterType="int" resultType="user">
    select * from mybatis.user where id = #{user.id}
</select>

上面這個類有 3 個屬性:id,name 和 pwd。這些屬性會對應到 select 語句中的列名。這些屬性會對應到 select 語句中的列名。但是類的名字和資料庫的欄位要一致。這樣的一個 JavaBean 可以被對映到 ResultSet

上面案例中,pwd和password不同所以測試結果顯示pwd為null

解決方法:

  • 起別名

    <select id="getUserById" resultType="com.kuang.pojo.User">
        select id,name,password as pwd from USER where id = #{id}
    </select>
    

    MyBatis 會在幕後自動建立一個 ResultMap,再根據屬性名來對映列到 JavaBean 的屬性上。如果列名和屬性名不能匹配上,可以在 SELECT 語句中設定列別名(這是一個基本的 SQL 特性)來完成匹配

2. resultMap

結果集對映

<resultMap id="resultMap" type="user">
    <result column="password" property="pwd"/>
</resultMap>
<select id="queryUserById" parameterType="int" resultMap="resultMap">
    #也可以省去物件名稱
    select * from mybatis.user where id = #{user.id}
</select>
  • resultMap 元素是 MyBatis 中最重要最強大的元素。
  • ResultMap 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。
  • ResultMap 的優秀之處——你完全可以不用顯式地配置它們。

5. 日誌

掌握 stdout_logging,log4j

5.1 STDOUT_LOGGING

在mybatis_config.xml檔案中新增設定

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

測試結果

5.2 LOG4J

簡介

Log4j是Apache的一個開源專案,通過使用Log4j,我們可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件,甚至是套介面伺服器、NT的事件記錄器、UNIX Syslog守護程序等;我們也可以控制每一條日誌的輸出格式;通過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程。最令人感興趣的就是,這些可以通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。

程式碼測試

  1. 新增設定

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    
  2. 匯入log4j的依賴包

    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>
    
  3. 編寫log4j.properties

    #將等級為DEBUG的日誌資訊輸出到console和file這兩個目的地,console和file的定義在下面的程式碼
    log4j.rootLogger=DEBUG,console,file
    
    #控制檯輸出的相關設定
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
    #檔案輸出的相關設定
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/rzp.log
    log4j.appender.file.MaxFileSize=10mb
    log4j.appender.file.Threshold=DEBUG
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
    #日誌輸出級別
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sq1.PreparedStatement=DEBUG
    
  4. 測試結果

  5. 簡單使用

    Logger logger = Logger.getLogger(UserDaoTest.class);
    logger.debug("debug");
    logger.error("error");
    logger.info("info");
    

6. Mybatis的執行流程

  1. 建立SqlSessionFactoryBuilder

  2. 使用建造者模式構建SqlSessionFactory

    1. 原始碼解析,使用配置檔案的輸入流構建一個XMLConfigBuilder物件,使用XMLConfigBuilder為引數構建DefaultSqlSessionFactory

      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
              SqlSessionFactory var5;
              try {
                 
                  XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
                  var5 = this.build(parser.parse());
              } catch (Exception var14) {
                  ....
              }
              return var5;
      }
      
      public SqlSessionFactory build(Configuration config) {
              return new DefaultSqlSessionFactory(config);
      }
      
    2. 執行結果

    3. 使用SqlSessionFactory建立sqlSession物件

      1. 原始碼分析,使用使用transaction作為引數生成了executor,configuration,executor生成了sqlSession

        private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
                Transaction tx = null;
                DefaultSqlSession var8;
                try {
                    Environment environment = this.configuration.getEnvironment();
                    TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
                    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
                    Executor executor = this.configuration.newExecutor(tx, execType);
                    var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
                } catch (Exception var12) {
        			...
                } 
                return var8;
            }
        
      2. 執行結果

    4. 實現CRUD

NOTE:

  1. mybatis執行流程使用了大量的反射,例如它使用反射知道我們所返回的型別是列表
  2. 使用了建造者和工廠設計模式

流程圖如下:

7. 使用註解執行CRUD

  1. mapper介面

    1. //login
      @Select("select * from mybatis.user where name=#{name} and password = #{pwd}")
      User login(@Param("name") String name, @Param("pwd") String password);
      
  2. 使用class=xxx編寫mybatis_configuration.xml檔案

        <mappers>
            <!--<mapper resource="com/iandf/dao/UserDaoMapper.xml"/>-->
            <mapper class="com.iandf.dao.UserMapper"/>
        </mappers>
    
  3. 測試

關於@param("xxx")註解:

  1. 基本型別和String都要加上,引用型別沒必要加
  2. 在sql引用上使用的就是param的屬性名
  3. 基本型別只有一個的時候可以加也可以不加,建議都加上

8. 負責查詢語句

8.1 多對一連表查詢

查詢語句 (查詢學生和學生對應的老師的資訊)

select s.id, s.name ,t.name from student s,teacher t where s.tid = t.id;

程式實現

1.按照查詢巢狀實現

  1. mapper介面

  2. mapper.xml

    <select id="getStudentList" resultMap="studentMap">
        select * from mybatis.student
    </select>
    <resultMap id="studentMap" type="com.iandf.pojo.Student">
        <association property="teacher" column="tid" javaType="com.iandf.pojo.Teacher" select="getTeacher"/>
    </resultMap>
    <select id="getTeacher" resultType="teacher">
        #\#{id}中的名字可以隨便取,他的值就是列名為column="tid"的值
        select * from mybatis.teacher where id = #{id}
    </select>
    
  3. 測試程式碼

  1. 按照結果巢狀的方式實現
  1. mapper介面

  2. mapper.xml

        <select id="getStudentList2" resultMap="studentMap2">
            select s.id sId, s.name sName, t.name tName, t.id tId from mybatis.student s, mybatis.teacher t where s.tid = t.id;
        </select>
        <resultMap id="studentMap2" type="student">
            <result property="id" column="sID"/>
            <result property="name" column="sName"/>
            <association property="teacher" javaType="Teacher">
                <result property="id" column="tID"/>
                <result property="name" column="tName"/>
            </association>
        </resultMap>
    
  3. 測試程式碼

8.2 一對多連表查詢

查詢老師和老師交的所有學生

select t.id tId ,t.name tName,s.id sId, s.name sName from mybatis.student s,mybatis.teacher t where s.tid = t.id and t.id = 1;

按照結果巢狀方式實現

  1. mapper介面

  2. mapper.xml

    <select id="getTeacherInfoById" resultMap="teacherMap" parameterType="int">
       select t.id tId ,t.name tName,s.id sId, s.name sName from mybatis.student s,mybatis.teacher t where s.tid = t.id and t.id = #{id};
    </select>
    <resultMap id="teacherMap" type="teacher">
        <result column="tId" property="id"/>
        <result column="tName" property="name"/>
        <collection property="students" ofType="student">
            <result property="id" column="sId"/>
            <result property="name" column="sName"/>
        </collection>
    </resultMap>
    
  3. 測試程式碼

按照查詢巢狀方式實現

mapper.xml

    <select id="getTeacherInfoById2" resultMap="teacherMap2" parameterType="int">
        select * from mybatis.teacher
    </select>
    <resultMap id="teacherMap2" type="teacher">
        <collection property="students" column="id" ofType="Student" select="getStudents"/>
    </resultMap>
    <select id="getStudents" resultType="student">
        select * from mybatis.student where tid = #{id}
    </select>

8.3 總結

多對一使用association

association – 一個複雜型別的關聯;許多結果將包裝成這種型別

  • 巢狀結果對映 – 關聯可以是 resultMap 元素,或是對其它結果對映的引用

一對多使用collection

collection – 一個複雜型別的集合

  • 巢狀結果對映 – 集合可以是 resultMap 元素,或是對其它結果對映的引用
  • ofType代表集合的泛型 javaType指的是實體類中的型別

9. 動態SQL

9.1 簡介

動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記新增必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。

使用動態 SQL 並非一件易事,但藉助可用於任何 SQL 對映語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

9.2 IF

需求:根據作者名字和部落格名字來查詢部落格!如果作者名字為空,那麼只根據部落格名字查詢,反之,則根據作者名來查詢

  1. mapper介面

    List<Blog> queryBlogByIf(Map<String,String> map);
    
  2. mapper.xml

    <select id="queryBlogByIf" parameterType="map" resultType="Blog">
        select * from mybatis.blog where 1=1
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </select>
    
  3. 測試

1=1在專案中是不允許這樣使用的,mybatis為我們提供了where標籤來解決and或者or加不加的問題

改進後:

<select id="queryBlogByIf" parameterType="map" resultType="Blog">
    select * from mybatis.blog
    <where>
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

這個“where”標籤會知道如果它包含的標籤中有返回值的話,它就插入一個‘where’。此外,如果標籤返回的內容是以AND 或OR 開頭的,則它會剔除掉。

9.3 set

同理,上面的對於查詢 SQL 語句包含 where 關鍵字,如果在進行更新操作的時候,含有 set 關鍵詞,我們怎麼處理呢?

需求:修改部落格的title或者author

  1. mapper介面

         void updateBlogBySet(Map<String,String> map);
    
  2. mapper.xml

        <update id="updateBlogBySet" parameterType="map">
            update mybatis.blog
            <set>
                <if test="title != null">
                    title = #{title},
                </if>
                <if test="author != null">
                    author = #{author}
                </if>
            </set>
            where id = #{id}
        </update>
    
  3. 測試

9.4 choose、when、otherwise

有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。

需求:傳入了 “title” 就按 “title” 查詢,傳入了 “author” 就按 “author” 查詢的情形。若兩者都沒有傳入,就返回所有的 BLOG

  1. mapper介面

    List<Blog> queryBlogByChoose(Map<String,String> map);
    
  2. mapper.xml

    <select id="queryBlogByChoose" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <where>
            <choose>
                <when test="title != null">
                    title = #{title}
                </when>
                <when test="author != null">
                    and author = #{author}
                </when>
            </choose>
        </where>
    </select>
    
  3. 測試

9.5 sql片段

有時候可能某個 sql 語句我們用的特別多,為了增加程式碼的重用性,簡化程式碼,我們需要將這些程式碼抽取出來,然後使用時直接呼叫。

提取sql片段

<sql id="if-title-author">
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

引用sql片段

<select id="queryBlogByIf" parameterType="map" resultType="Blog">
    select * from mybatis.blog
    <where>
        <include refid="if-title-author"/>
        <!-- 在這裡還可以引用其他的 sql 片段 -->
    </where>
</select>

NOTE:

  1. 最好是基於單表查詢,可以提高片段的可重用性
  2. sql片段中不要包含where,最好只有幾個簡單的if

9.6 foreach

需求:我們需要查詢 blog 表中 id 分別為1,2,3的部落格資訊

sql語句

select * from blog where id in (1,2,3);

程式碼實現

  1. mapper介面

    List<Blog> queryBlogByForeach(Map<String,Object> map);
    
  2. mapper.xml

    <select id="queryBlogByForeach" resultType="blog" parameterType="map">
        select * from mybatis.blog
        <where>
            <foreach collection="ids" item="id" open="id in (" close=")" separator=",">
                #{id}
            </foreach>
        </where>
    </select>
    
  3. 測試

    public void queryBlogByForeach(){
        try(SqlSession sqlSession = MybatisUtil.getSqlSession()){
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            HashMap<String, Object> map = new HashMap<>();
            List<Integer> ids = new ArrayList<>();
            ids.add(1);
            ids.add(2);
            ids.add(3);
            map.put("ids",ids);
            List<Blog> blogs = mapper.queryBlogByForeach(map);
            for (Blog blog : blogs) {
                System.out.println(blog.toString());
            }
        }
    }
    

10. Mybatis快取

10.1 簡介

查詢 : 連線資料庫 ,耗資源!
一次查詢的結果,給他暫存在一個可以直接取到的地方!--> 記憶體 : 快取

我們再次查詢相同資料的時候,直接走快取,就不用走資料庫了

  1. 什麼是快取 [ Cache ]?

    • 存在記憶體中的臨時資料。
    • 將使用者經常查詢的資料放在快取(記憶體)中,使用者去查詢資料就不用從磁碟上(關係型資料庫資料檔案)查詢,從快取中查詢,從而提高查詢效率,解決了高併發系統的效能問題。
  2. 為什麼使用快取?

    • 減少和資料庫的互動次數,減少系統開銷,提高系統效率。
  3. 什麼樣的資料能使用快取?

    • 經常查詢並且不經常改變的資料。【可以使用快取】

10.2、Mybatis快取

  • MyBatis包含一個非常強大的查詢快取特性,它可以非常方便地定製和配置快取。快取可以極大的提升查詢效率。
  • MyBatis系統中預設定義了兩級快取:一級快取二級快取
    • 預設情況下,只有一級快取開啟。(SqlSession級別的快取,也稱為本地快取)
    • 二級快取需要手動開啟和配置,他是基於namespace級別的快取。
    • 為了提高擴充套件性,MyBatis定義了快取介面Cache。我們可以通過實現Cache介面來自定義二級快取

10.3、一級快取

  • 一級快取也叫本地快取: SqlSession
    • 與資料庫同一次會話期間查詢到的資料會放在本地快取中。
    • 以後如果需要獲取相同的資料,直接從快取中拿,沒必須再去查詢資料庫;

測試步驟:

  1. 開啟日誌!
  2. 測試在一個Sesion中查詢兩次相同記錄
  3. 檢視日誌輸出

快取失效的情況:

  1. 查詢不同的東西

  2. 增刪改操作,可能會改變原來的資料,所以必定會重新整理快取!

  3. 查詢不同的Mapper.xml

  4. 手動清理快取!

    sqlsession.clearCache();
    

小結:一級快取預設是開啟的,只在一次SqlSession中有效,也就是拿到連線到關閉連線這個區間段!

一級快取就是一個Map。

10.4、二級快取

  • 二級快取也叫全域性快取,一級快取作用域太低了,所以誕生了二級快取
  • 基於namespace級別的快取,一個名稱空間,對應一個二級快取;
  • 工作機制
    • 一個會話查詢一條資料,這個資料就會被放在當前會話的一級快取中;
    • 如果當前會話關閉了,這個會話對應的一級快取就沒了;但是我們想要的是,會話關閉了,一級快取中的資料被儲存到二級快取中;
    • 新的會話查詢資訊,就可以從二級快取中獲取內容;
    • 不同的mapper查出的資料會放在自己對應的快取(map)中;

步驟:

  1. 開啟全域性快取

    <!--顯示的開啟全域性快取-->
    <setting name="cacheEnabled" value="true"/>
    
  2. 在要使用二級快取的Mapper中開啟

    <!--在當前Mapper.xml中使用二級快取-->
    <cache/>
    

    也可以自定義引數

    <!--在當前Mapper.xml中使用二級快取-->
    <cache  eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>
    
  3. 測試

    1. 問題:我們需要將實體類序列化!否則就會報錯!

      Caused by: java.io.NotSerializableException: com.kuang.pojo.User
      

小結:

  • 只要開啟了二級快取,在同一個Mapper下就有效
  • 所有的資料都會先放在一級快取中;
  • 只有當會話提交,或者關閉的時候,才會提交到二級快取中!

10.5、快取原理