mybatis學習

Ghost-programmer發表於2020-12-24

1、什麼是MyBatis

  • MyBatis是一款優秀的持久層框架,它支援自定義SQL、儲存過程以及高階對映。
  • MyBatis 免除了幾乎所有的 JDBC 程式碼以及設定引數和獲取結果集的工作
  • MyBatis 可以通過簡單的 XML 或註解來配置和對映原始型別、介面和 Java POJO(Plain Old Java Objects,普通老式 Java 物件)為資料庫中的記錄
  • MyBatis 是一個半自動化的ORM框架 (Object Relationship Mapping) -->物件關係對映
  • MyBatis官方文件地址:https://mybatis.org/mybatis-3/zh/index.html
  • GitHub專案地址:https://github.com/mybatis/mybatis-3

1.1、什麼是持久化

持久化(Persistence),即把資料(如記憶體中的物件)儲存到可永久儲存的儲存裝置中(如磁碟)。持久化的主要應用是將記憶體中的物件儲存在資料庫中,或者儲存在磁碟檔案中、XML資料檔案中等等。

持久化是將程式資料在持久狀態和瞬時狀態間轉換的機制。

通俗的講,就是瞬時資料(比如記憶體中的資料,是不能永久儲存的)持久化為持久資料(比如持久化至資料庫中,能夠長久儲存)。

1.2、什麼是持久層

持久層用來固化資料,如常說的DAO層,運算元據庫將資料入庫。

持久層,就是把持久的動作封裝成一個獨立的層,這是為了降低功能程式碼之間的關聯。建立一個更清晰的抽象,提高程式碼的內聚力,降低程式碼的耦合度,從而增強程式碼的要勞動局生和可重用性。

MyBatis 是一款優秀的持久層框架。

2、MyBatis入門

  • 建立maven專案

  • 匯入MyBatis的jar包

    		<dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.6</version>
            </dependency>
    
  • 匯入MySQL的jar包

    		<dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.21</version>
            </dependency>
    
  • 匯入JUnit測試包

    		<dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
    
  • 建立MyBatis工具類

    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    public class MybatisUtil {
    
        private static SqlSessionFactory sqlSessionFactory;
    
        static {
            try {
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static SqlSession getSession() {
            // 開啟事務自動提交
            return sqlSessionFactory.openSession(true);
            // 事務不會自動提交
    //        return sqlSessionFactory.openSession();
        }
    }
    
  • 建立MyBatis核心配置檔案mybatis-config.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="properties/db.properties"/>
    
    <settings>
        <!--設定日誌-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--設定資料來源-->
    <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>

    <!--設定對映器(mapper)-->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.50.129:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=mybatis
password=123456
  • 編寫實體類
import java.util.Date;

public class User {
    private int id;

    private String user_name;

    private String password;

    private String sex;

    private Date create_time;

    private String birthday;

    public User() {
    }

    public User(int id, String user_name, String password, String sex, String birthday) {
        this.id = id;
        this.user_name = user_name;
        this.password = password;
        this.sex = sex;
        this.birthday = birthday;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUser_name() {
        return user_name;
    }

    public void setUser_name(String user_name) {
        this.user_name = user_name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getCreate_time() {
        return create_time;
    }

    public void setCreate_time(Date create_time) {
        this.create_time = create_time;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", user_name='" + user_name + '\'' +
                ", password='" + password + '\'' +
                ", sex='" + sex + '\'' +
                ", create_time=" + create_time +
                ", birthday='" + birthday + '\'' +
                '}';
    }
}
  • 編寫Mapper介面類
import com.ghost.mybatis.pojo.User;

import java.util.List;

public interface UserMapper {

    List<User> selectUser();

    User selectUserById(int id);

    int addUser(User user);

    int updateUser(User user);

    int deleteUser(int id);
}
  • 編寫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.ghost.mybatis.mapper.UserMapper">
    <select id="selectUser" resultType="com.ghost.mybatis.pojo.User">
        select *
        from user
    </select>

    <select id="selectUserById" parameterType="int" resultType="user">
        select *
        from user
        where id = #{id};
    </select>

    <insert id="addUser" parameterType="com.ghost.mybatis.pojo.User">
        insert into user(id, user_name, password, sex, birthday)
        values (#{id}, #{user_name}, #{password}, #{sex}, #{birthday});
    </insert>

    <update id="updateUser" parameterType="com.ghost.mybatis.pojo.User">
        update user
        set user_name = #{user_name},
            password  = #{password},
            sex       = #{sex},
            birthday  = #{birthday}
        where id = #{id};
    </update>

    <delete id="deleteUser" parameterType="int">
        delete
        from user
        where id = #{id};
    </delete>
</mapper>
  • 編寫測試類
import com.ghost.mybatis.mapper.UserMapper;
import com.ghost.mybatis.pojo.User;
import com.ghost.mybatis.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class MyTest {

    @Test
    public void selectUser() {
        // 獲取SqlSession
        SqlSession session = MybatisUtil.getSession();
        //方法一:
        //List<User> users = session.selectList("com.ghost.mybatis.mapper.UserMapper.selectUser");
        //方法二:
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.selectUser();

        for (User user: users){
            System.out.println(user);
        }
        session.close();
    }

    @Test
    public void selectUserById() {
        SqlSession session = MybatisUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.selectUserById(1);
        System.out.println(user);
        session.close();
    }

    @Test
    public void addUser() {
        SqlSession session = MybatisUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        mapper.addUser(new User(6, "李四", "12334", "女", "2020-02"));
//        session.commit();
        session.close();
    }

    @Test
    public void updateUser() {
        SqlSession session = MybatisUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        mapper.updateUser(new User(1, "張三", "8899993", "男", "1990-09"));
//        session.commit();
        session.close();
    }

    @Test
    public void deleteUser() {
        SqlSession session = MybatisUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        mapper.deleteUser(3);
//        session.commit();
        session.close();
    }
}
  • 執行測試查詢,查詢出結果,OK!!!

注意

  • 如果將Mapper.xml檔案寫入src/main/java下,但是在執行時,target資料夾下沒有該檔案則,則需要在pom.xml 檔案中加入如下配置(Maven靜態資源過濾問題
	<build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
  • 出現如下錯誤,則需要在MyBatis核心配置檔案中設定對映器
org.apache.ibatis.binding.BindingException: Type interface com.ghost.mybatis.dao.UserMapper is not known to the MapperRegistry.

在這裡插入圖片描述

  • 使用高版本MySQL時需要在URL中設定時區:serverTimezone=Asia/Shanghai
    在這裡插入圖片描述

  • 配置檔案中namespace中的名稱為對應Mapper介面或者Dao介面的完整包名,必須一致

  • 標籤不能對應錯

  • resource繫結mapper需要使用路徑

  • 程式配置檔案必須符合規範

3、作用域(Scope)和生命週期

不同作用域和生命週期類別是至關重要的,因為錯誤的使用會導致非常嚴重的併發問題

建立順序:SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession

3.1 SqlSessionFactoryBuilder

  • 這個類可以被例項化、使用和丟棄,一旦建立了 SqlSessionFactory,就不再需要它了
  • 最佳作用域:區域性變數

3.2 SqlSessionFactory

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

3.3 SqlSession

  • 每個執行緒都應該有它自己的 SqlSession 例項
  • SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域
  • 每次使用後確保執行關閉操作,否則資源被佔用

3.4 從 SqlSessionFactory 中獲取 SqlSession方式

  • 方式一:(不推薦,型別不安全)
try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}
  • 方式二:
try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}
  • 每個執行緒都應該有它自己的 SqlSession 例項。SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。
  • 確保每次都能執行關閉操作

3.5 對映器

  • 對映器是一些繫結對映語句的介面(定義對映SQL語句檔案)
  • 既然 MyBatis 的行為已經由上述元素配置完了,我們現在就要來定義 SQL 對映語句了。 但首先,我們需要告訴 MyBatis 到哪裡去找到這些語句。 在自動查詢資源方面,Java 並沒有提供一個很好的解決方案,所以最好的辦法是直接告訴 MyBatis 到哪裡去找對映檔案。 你可以使用相對於類路徑的資源引用,或完全限定資源定位符(包括 file:/// 形式的 URL),或類名和包名等
<!-- 使用相對於類路徑的資源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定資源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用對映器介面實現類的完全限定類名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 將包內的對映器介面實現全部註冊為對映器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
  • MapperRegistry:註冊繫結我們的Mapper檔案
  • 在使用“使用對映器介面實現類的完全限定類名”和“將包內的對映器介面實現全部註冊為對映器”引入對映器介面時,需要注意如下問題:
    • 介面和他的Mapper配置檔案必須同名
    • 介面和他的Mapper配置檔案必須在同一個包下

3.6 對映器例項

  • 對映器介面的例項是從 SqlSession 中獲得的
  • 方法作用域才是對映器例項的最合適的作用域,對映器例項應該在呼叫它們的方法中被獲取,使用完畢之後即可丟棄
  • 最好將對映器放在方法作用域內
try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的應用邏輯程式碼
}

4、XML配置

MyBatis 的配置檔案包含了會深深影響 MyBatis 行為的設定和屬性資訊,核心配置檔案一般為:mybatis-config.xml

4.1 屬性(properties)

  • 我們可以通過Properties屬性來實現應用配置檔案
  • 這些屬性可以在外部進行配置,並可以進行動態替換。你既可以在典型的 Java 屬性檔案中配置這些屬性,也可以在 properties 元素的子元素中設定db.properties
  • 在核心配置檔案中引入外部配置檔案
<!-- 可以直接引入外部檔案 -->
<!-- 可以在其中增加一些屬性配置 -->
<!-- 如果兩個檔案中有同一個欄位,優先使用配置檔案的 -->
<properties resource="properties/db.properties"/>
  • 在XML中,所有的標籤都可以規定其順序

4.2 環境配置(environments)

<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>
  • MyBatis 可以配置成適應多種環境
  • 儘管可以配置多個環境,但每個 SqlSessionFactory 例項只能選擇一種環境
  • Mybatis預設的事務管理器是JDBC,連線池:POOLED

4.3 型別別名(typeAliases)

  • 型別別名可為 Java 型別設定一個縮寫名字。 它僅用於 XML 配置,意在降低冗餘的全限定類名書寫
  • 案例:
<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
  • 也可以指定一個包名,MyBatis 會在包名下面搜尋需要的 Java Bean
<!-- 在沒有註解的情況下,會使用 Bean 的首字母小寫的非限定類名來作為它的別名 -->
<!-- 若有註解,則別名為其註解值 -->
<typeAliases>
  <package name="domain.blog"/>
</typeAliases>
  • 第一種可以使用DIY原則,第二種則不行,如果要使用則需要在實體類上增加註解
  • 在實體類比較少的時候,可以使用第一種方式,如果實體類十分多,建議使用第二種

4.4 設定(settings)

  • cacheEnabled - 全域性性地開啟或關閉所有對映器配置檔案中已配置的任何快取
  • lazyLoadingEnabled - 延遲載入的全域性開關。當開啟時,所有關聯物件都會延遲載入。 特定關聯關係中可通過設定 fetchType 屬性來覆蓋該項的開關狀態
  • useGeneratedKeys - 允許 JDBC 支援自動生成主鍵,需要資料庫驅動支援。如果設定為 true,將強制使用自動生成主鍵。儘管一些資料庫驅動不支援此特性,但仍可正常工作(如 Derby)
  • mapUnderscoreToCamelCase - 是否開啟駝峰命名自動對映,即從經典資料庫列名 A_COLUMN 對映到經典 Java 屬性名 aColumn
  • logImpl - 指定 MyBatis 所用日誌的具體實現,未指定時將自動查詢(SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING(標準日誌輸出) | NO_LOGGING)

4.5 日誌工廠

  • 如果一個資料庫操作出現了異常,我們需要排錯。日誌就是最好的助手。
  • 在mybatis中具體使用那一個日誌實現,在設定中設定。
  • 配置資訊
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  • LOG4J

    • 什麼是log4j
      • Log4j是Apache的一個開源專案,通過使用Log4j,我們可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件
      • 可以控制每一條日誌的輸出格式
      • 通過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程
      • 可以通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼
    • maven專案中匯入jar包
    • 設定日誌格式及其他配置資訊
    #將等級為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/kuang.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.sql.PreparedStatement=DEBUG
    
    • 在要使用Log4j的類中,匯入包 import org.apache.log4j.Logger;
    • 日誌物件,引數設定為當前類的class
    • 日誌級別:INFO|DEBUG|ERROR

4.6 其他配置

  • typeHandlers(型別處理器)
  • objectFactory(物件工廠)
  • plugins(外掛)
    • mybatis-generator 自動生成MyBatis程式碼
    • mybatis-plus 可以提高mybatis的效率
    • PageHelper MyBatis分頁外掛
    • 通用mapper

5、XML 對映器

MyBatis 的真正強大在於它的語句對映,這是它的魔力所在。

SQL 對映檔案只有很少的幾個頂級元素(按照應被定義的順序列出)

  • cache – 該名稱空間的快取配置。
  • cache-ref – 引用其它名稱空間的快取配置。
  • resultMap – 描述如何從資料庫結果集中載入物件,是最複雜也是最強大的元素。
  • sql – 可被其它語句引用的可重用語句塊。
  • insert – 對映插入語句。
  • update – 對映更新語句。
  • delete – 對映刪除語句。
  • select – 對映查詢語句。

增刪改需要提交事務

5.1 模糊查詢

  • java程式碼執行的時候,傳遞萬用字元%

  • 在SQL拼接中使用萬用字元

5.2 字串替換

  • 預設情況下,使用 #{} 引數語法時,MyBatis 會建立 PreparedStatement 引數佔位符,並通過佔位符安全地設定引數(就像使用 ? 一樣)
  • #{}可以防止SQL隱碼攻擊
  • 使用${}引數語法時,MyBatis 就不會修改或轉義該字串了
    • 用這種方式接受使用者的輸入,並用作語句引數是不安全的,會導致潛在的 SQL 注入攻擊。
    • 因此,要麼不允許使用者輸入這些欄位,要麼自行轉義並檢驗這些引數。

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

  • 別名:查詢SQL中使用別名
  • 結果集對映:resultMap
    • resultMap 元素是 MyBatis 中最重要最強大的元素
    • ResultMap 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了
    • ResultMap 的優秀之處——你完全可以不用顯式地配置它們

如果這個世界總是這麼簡單就好了

5.4 分頁

  • 為什麼要分頁 - 減少資料的處理量

  • 使用Limit分頁

    select * from user limit startIndex,pageSize;
    
  • 使用mybatis實現分頁,核心是SQL

  • 使用RowBounds分頁,不在使用SQL實現分頁(瞭解即可,不推薦使用)

  • 分頁外掛

    • Mybatis-PageHelper

5.5 高階結果對映

5.5.1 多對一處理

  • 物件:association - 關聯【多對一】
  • 按照查詢巢狀處理
  • 按結果巢狀處理
  • MySQL多對一查詢方式
    • 子查詢
    • 聯表查詢
  • JavaType:用來指定實體類中屬性的型別
  • ofType:用來指定對映到List或者集合中的POJO型別,泛型中的約束型別

5.5.2 練習

  • MyBatis工具類
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MybatisUtil {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSession() {
        // 開啟事務自動提交
        return sqlSessionFactory.openSession(true);
        // 事務不會自動提交
//        return sqlSessionFactory.openSession();
    }
}
  • 配置檔案
<?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="properties/db.properties"/>

    <settings>
        <!--設定日誌-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <typeAlias type="com.ghost.mybatis.pojo.Teacher" alias="Teacher" />
        <typeAlias type="com.ghost.mybatis.pojo.Student" alias="Student" />
    </typeAliases>

    <!--設定資料連線-->
    <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="mapper/StudentMapper.xml"/>
        <mapper resource="mapper/TeacherMapper.xml"/>
    </mappers>
</configuration>
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.50.129:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=mybatis
password=123456
  • 實體類
import lombok.Data;

@Data
public class Student {

    private int id;

    private String name;

    private Teacher teacher;
}
import lombok.Data;

@Data
public class Teacher {

    private int id;

    private String name;
}
  • Mapper介面
import com.ghost.mybatis.pojo.Student;

import java.util.List;

public interface StudentMapper {

    List<Student> selectStudentInNestedQuery();

    List<Student> selectStudentInResultsTheNested();
}
import com.ghost.mybatis.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface TeacherMapper {

}
  • Mapper.xml檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ghost.mybatis.mapper.StudentMapper">

    <!-- 子查詢方式 -->
    <select id="selectStudentInNestedQuery" resultMap="StudentInNestedQuery">
        select *
        from mybatis.student
    </select>

    <!-- 子查詢方式結果集 -->
    <resultMap id="StudentInNestedQuery" type="Student">
        <association property="teacher" column="tid" javaType="Teacher" select="selectTeacher"/>
    </resultMap>

    <!-- 子查詢方式 - 子查詢方法 -->
    <select id="selectTeacher" parameterType="int" resultType="Teacher">
        select *
        from mybatis.teacher
        where id = #{tid}
    </select>

    <!-- 結果集查詢方式 -->
    <select id="selectStudentInResultsTheNested" resultMap="StudentInResultsTheNested">
        select s.id sid, s.name sname, t.id tid, t.name tname
        from mybatis.student s,
             mybatis.teacher t
        where s.tid = t.id
    </select>

    <!-- 結果集查詢方式結果集 -->
    <resultMap id="StudentInResultsTheNested" type="Student">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="Teacher">
            <id property="id" column="tid"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ghost.mybatis.mapper.TeacherMapper">

</mapper>
  • 測試類
import com.ghost.mybatis.mapper.StudentMapper;
import com.ghost.mybatis.mapper.TeacherMapper;
import com.ghost.mybatis.pojo.Student;
import com.ghost.mybatis.pojo.Teacher;
import com.ghost.mybatis.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class MyTest {

    /*
    聯合查詢
     */
    @Test
    public void selectStudentInNestedQuery() {
        SqlSession sqlSession = MybatisUtil.getSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectStudentInNestedQuery();
        for (Student student : students) {
            System.out.println(student);
        }
        sqlSession.close();
    }

    /*
    結果集查詢方式
     */
    @Test
    public void selectStudentInResultsTheNested() {
        SqlSession sqlSession = MybatisUtil.getSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectStudentInResultsTheNested();
        for (Student student : students) {
            System.out.println(student);
        }
        sqlSession.close();
    }
}

5.5.3 一對多處理

  • 集合:collection - 集合【一對多】
  • 按照查詢巢狀處理
  • 按結果巢狀處理

5.5.4 練習

  • mybatis工具類和配置檔案與“多對一查詢”一樣
  • 實體類
import lombok.Data;

@Data
public class Student {

    private int id;

    private String name;

    private int tid;
}

import lombok.Data;

import java.util.List;

@Data
public class Teacher {

    private int id;

    private String name;

    List<Student> studentList;
}

  • Mapper介面
public interface StudentMapper {

}
import com.ghost.mybatis.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface TeacherMapper {

    @Select("select * from teacher where id = #{tid}")
    Teacher selectTeacherById(@Param("tid") int id);

    List<Teacher> selectTeacherInNestedQuery();

    List<Teacher> selectTeacherInResultsTheNested();
}

  • Mapper.xml檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ghost.mybatis.mapper.StudentMapper">

</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ghost.mybatis.mapper.TeacherMapper">

    <select id="selectTeacherInNestedQuery" resultMap="TeacherInNestedQueryMap">
        select *
        from mybatis.teacher
    </select>

    <resultMap id="TeacherInNestedQueryMap" type="Teacher">
        <collection property="studentList" ofType="Student" select="selectStudent" column="id">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

    <select id="selectStudent" parameterType="int" resultType="Student">
        select *
        from mybatis.student
        where tid = #{id}
    </select>

    <select id="selectTeacherInResultsTheNested" resultMap="TeacherInResultsTheNested">
        select t.id, t.name tname, s.id sid, s.name sname, s.tid
        from mybatis.teacher t,
             mybatis.student s
        where t.id = s.tid
    </select>

    <resultMap id="TeacherInResultsTheNested" type="Teacher">
        <id property="id" column="id"/>
        <result property="name" column="tname"/>
        <collection property="studentList" ofType="Student">
            <id property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>
</mapper>
  • 測試類
import com.ghost.mybatis.mapper.TeacherMapper;
import com.ghost.mybatis.pojo.Teacher;
import com.ghost.mybatis.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class MyTest {

    @Test
    public void selectTeacherById() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.selectTeacherById(1);
        System.out.println(teacher);
        sqlSession.close();
    }

    @Test
    public void selectTeacherInNestedQuery() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        List<Teacher> teachers = mapper.selectTeacherInNestedQuery();
        for (Teacher teacher : teachers) {
            System.out.println(teacher);
        }
        sqlSession.close();
    }

    @Test
    public void selectTeacherInResultsTheNested() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        List<Teacher> teachers = mapper.selectTeacherInResultsTheNested();
        for (Teacher teacher : teachers) {
            System.out.println(teacher);
        }
        sqlSession.close();
    }
}

5.5.5 注意點

  • 保證SQL的可讀性,儘量保證通俗易懂
  • 注意一對多和多對一中,屬性名和欄位的問題
  • 如果問題不好排查,可以使用日誌,建議使用Log4j

6、使用註解開發

6.1 面向介面程式設計

  • 什麼是面向介面程式設計
    • 根本原因:解耦
    • 關於介面的理解
      • 介面從更深層次的理解,應是定義(規範,約束)與實現(名實分離的原則)的分離。
      • 介面的本身反映了系統設計人員對系統的抽象理解。
      • 介面應有兩類:第一類是對一個個體的抽象,它可對應為一個抽象體(abstract class);第二類是對一個個體某一方面的抽象,即形成一個抽象面(interface)
      • 一個體有可能有多個抽象面;抽象體與抽象面是有區別的
    • 介面是一種規範和約束,更高層的抽象更像是一類行為,面向介面程式設計只是程式碼層體現的一種格式體現而已,真正的面向介面設計更貼近面向行為程式設計
  • 三個面向區別
    • 物件導向是指,我們考慮問題時,以物件為單位,考慮它的屬性及方法
    • 程式導向是指,我們考慮問題時,以一個具體的流程(事務過程)為單位,考慮它的實現 .
    • 介面設計與非介面設計是針對複用技術而言的,與物件導向(過程)不是一個問題.更多的體現就是對系統整體的架構
  • 面向介面程式設計的優點
    • 解耦
    • 可擴充套件性高
    • 提高複用
    • 分層開發中,上層不用管具體的實現,大家都遵守共同的標準,使得開發變得容易,規範性更好
    • 符合“開放-關閉原則”(對擴充套件開放,對修改關閉)

6.2 使用註解開發

  • 本質:反射機制實現
  • 底層:動態代理
  • 註解在介面上實現
  • 需要在核心配置檔案中繫結介面

6.3 MyBatis詳細執行過程

6.4 關於@Param()註解

  • 基本型別的引數或者String型別,都需要加上
  • 引用型別不需要加
  • 如果只有一個基本型別的話,可以省略,但是建議大家加上
  • 我們在SQL中引用的就是我們這裡的@Param中設定的屬性名

7、Lombok

7.1 使用步驟

  • 在IDEA安裝lombok外掛
  • 在專案中匯入lombok的jar包(最好使用高版本,低版本的有時會出現“com.sun.tools.javac.code.TypeTags”錯誤)
  • 在實體類加註解即可

7.2 部分註解

  • @Data
    • 無參構造器
    • getter
    • setter
    • toString
    • hashcode
    • equals
  • @AllArgsConstructor - 全參構造器
  • @NoArgsConstructor - 無參構造器
  • pom檔案匯入
	<dependencies>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
    </dependencies>

8、動態SQL

8.1 什麼是動態SQL

動態SQL就是指根據不同的條件生成不同的SQL語句;

所謂的動態SQL,本質還是SQL語句,只是我們可以在SQL層面,去執行一個邏輯程式碼;

動態SQL就是在拼接SQL語句,我們只要保證SQL的正確性,按照SQL的格式,去排列組合就行了

首先要寫原生的 sql 語句出來,然後在通過 mybatis 動態sql 對照著改,防止出錯

8.2 IF

8.3 choose、when、otherwise

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

8.4 trim、where、set

  • where
    • 若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除
    • where 元素只會在子元素返回任何內容的情況下才插入 “WHERE” 子句
  • set
    • 用於動態更新語句的類似解決方案叫做 set
    • set 元素可以用於動態包含需要更新的列,忽略其它不更新的列
    • set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號
  • trim
    • prefixOverrides 屬性會忽略通過管道符分隔的文字序列
    • 插入 prefix 屬性中指定的內容(字首值)
    • suffixOverrides(字尾值)

8.5 SQL片段

  • 有的時候我們會將一些功能抽取出來方便複用
  • sql標籤 - 公共SQL語句
  • include標籤 - 引入公共SQL片段
  • 注意事項
    • 最好基於單表來定義SQL片段
    • 不要存在where標籤

8.6 foreach

  • foreach 元素的功能非常強大,它允許你指定一個集合,宣告可以在元素體內使用的集合項(item)和索引(index)變數
  • 它也允許你指定開頭與結尾的字串以及集合項迭代之間的分隔符

8.7 練習

  • 建立MyBatis工具類
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtil {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession(true);
    }
}
  • 建立UUID工具類
import java.util.UUID;

public class UUIDUtil {

    public static String getUUID() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}
  • 建立Mybatis配置檔案
<?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檔案-->
    <properties resource="properties/db.properties"/>

    <settings>
        <!--開啟駝峰命名自動對映,即從經典資料庫列名 A_COLUMN 對映到經典 Java 屬性名 aColumn-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--開啟日誌-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <!--別名-->
        <typeAlias type="com.ghost.mybatis.pojo.Book" alias="Book"/>
    </typeAliases>

    <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="com/ghost/mybatis/mapper/BookMapper.xml"/>
    </mappers>
</configuration>
  • 建立資料庫配置檔案
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.50.129:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=mybatis
password=123456
  • 新建Book表
create table book
(
	id varchar(32) not null
		primary key,
	book_name varchar(100) null,
	author varchar(50) null,
	publication_time datetime default CURRENT_TIMESTAMP not null
);
  • 建立實體類
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {

    public Book(String id, String bookName, String author) {
        this.id = id;
        this.bookName = bookName;
        this.author = author;
    }

    private String id;

    private String bookName;

    private String author;

    private Date publicationTime;
}
  • 建立Mapper介面
import com.ghost.mybatis.pojo.Book;
import org.apache.ibatis.annotations.Insert;

import java.util.List;

public interface BookMapper {

    @Insert("insert into book(id, book_name, author) values (#{id}, #{bookName}, #{author})")
    int addBook(Book book);

    List<Book> selectBook(Book book);

    List<Book> selectBookByChoose(Book book);

    int updateBook(Book book);
}
  • 建立Mapper.xml檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ghost.mybatis.mapper.BookMapper">

    <sql id="selectSQL" >
        <if test="id != null">
            id = #{id}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
        <if test="bookName != null">
            and book_name = #{bookName}
        </if>
    </sql>

    <select id="selectBook" parameterType="Book" resultType="Book">
        select * from mybatis.book
        <trim prefix="where" prefixOverrides="and | or">
            <include refid="selectSQL"></include>
        </trim>
    </select>

    <select id="selectBookByChoose" parameterType="Book" resultType="Book">
        select * from mybatis.book
        <where>
            <choose>
                <when test="id != null">
                    id = #{id}
                </when>
                <when test="author != null">
                    and author = #{author}
                </when>
                <otherwise>
                    and book_name = "測試書籍"
                </otherwise>
            </choose>
        </where>
    </select>

    <update id="updateBook" parameterType="Book">
        update mybatis.book
        <set>
            <if test="author != null">author = #{author},</if>
            <if test="bookName != null">book_name = #{bookName},</if>
        </set>
        where id = #{id}
    </update>

</mapper>
  • 建立測試類
import com.ghost.mybatis.mapper.BookMapper;
import com.ghost.mybatis.pojo.Book;
import com.ghost.mybatis.utils.MyBatisUtil;
import com.ghost.mybatis.utils.UUIDUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class MyTest {

    @Test
    public void getUUID() {
        System.out.println(UUIDUtil.getUUID());
    }

    @Test
    public void addBook() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        BookMapper mapper = sqlSession.getMapper(BookMapper.class);
        mapper.addBook(new Book(UUIDUtil.getUUID(), "測試書籍1", "ghost"));
        mapper.addBook(new Book(UUIDUtil.getUUID(), "測試書籍2", "ghost"));
        mapper.addBook(new Book(UUIDUtil.getUUID(), "測試書籍3", "ghost"));
        mapper.addBook(new Book(UUIDUtil.getUUID(), "測試書籍4", "ghost"));
        mapper.addBook(new Book(UUIDUtil.getUUID(), "測試書籍5", "ghost"));
        sqlSession.close();
    }

    @Test
    public void selectBook() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        BookMapper mapper = sqlSession.getMapper(BookMapper.class);
        try {
            Book book = new Book();
            List<Book> books = mapper.selectBook(book);
            for (Book book1 : books) {
                System.out.println(book1);
            }
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void selectBookByChoose() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        BookMapper mapper = sqlSession.getMapper(BookMapper.class);
        Book book = new Book();
        book.setId("0cfd6fb6aa414df7b1483f66d0862b40");
        List<Book> books = mapper.selectBookByChoose(book);
        for (Book book1 : books) {
            System.out.println(book1);
        }
        sqlSession.close();
    }

    @Test
    public void updateBook() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        BookMapper mapper = sqlSession.getMapper(BookMapper.class);
        mapper.updateBook(new Book("9e07c2afff7b471186116a99355ce965", "測試修改書籍", "ghost"));
        sqlSession.close();
    }
}

9、快取

資料庫優化:讀寫分離、主從複製

9.1 什麼是快取【Cache】

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

9.2 為什麼使用快取

  • 減少和資料庫的互動次數
  • 減少系統開銷
  • 提高系統效率

9.3 什麼樣的資料能夠使用快取

  • 經常查詢並且不經常改變的資料

9.4 MyBatis快取

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

9.5 一級快取

  • 一級快取也叫本地快取
    • 與資料庫同一次會話期間查詢到的資料會放在本地快取中
    • 以後如果需要獲取相同的資料,直接從快取中拿,沒必須再去查詢資料庫
  • 一級快取是SqlSession級別的快取,是一直開啟的,我們關閉不了它
  • 一級快取失效情況:沒有使用到當前的一級快取,效果就是,還需要再向資料庫中發起一次查詢請求
  • 一級快取失效的四種情況
    • sqlSession不同(每個sqlSession中的快取相互獨立)
    • sqlSession相同,查詢條件不同(當前快取中,不存在這個資料)
    • sqlSession相同,兩次查詢之間執行了增刪改操作(因為增刪改操作可能會對當前資料產生影響)
    • sqlSession相同,手動清除一級快取(一級快取就是一個map)

9.6 二級快取

  • 二級快取也叫全域性快取,一級快取作用域太低了,所以誕生了二級快取

  • 基於namespace級別的快取,一個名稱空間,對應一個二級快取

  • 工作機制

    • 一個會話查詢一條資料,這個資料就會被放在當前會話的一級快取中
    • 如果當前會話關閉了,這個會話對應的一級快取就沒了;但是我們想要的是,會話關閉了,一級快取中的資料被儲存到二級快取中
    • 新的會話查詢資訊,就可以從二級快取中獲取內容
    • 不同的mapper查出的資料會放在自己對應的快取(map)中
  • 使用步驟

    • 開啟全域性快取 【mybatis-config.xml】
    <setting name="cacheEnabled" value="true"/>
    
    • 去每個mapper.xml中配置使用二級快取,這個配置非常簡單;【xxxMapper.xml】
    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    
  • 需要將實體類序列化

9.7 MyBatis一級快取和二級快取總結

  • 只要開啟了二級快取,我們在同一個Mapper中的查詢,可以在二級快取中拿到資料
  • 查出的資料都會被預設先放在一級快取中
  • 查出的資料都會被預設先放在一級快取中

9.8 其他快取

  • 自定義快取 - EhCache
  • Redis

相關文章