1. 程式人生 > >MyBatis學習總結(四)——MyBatis快取與程式碼生成

MyBatis學習總結(四)——MyBatis快取與程式碼生成

 一、MyBatis快取

快取可以提高系統性能,可以加快訪問速度,減輕伺服器壓力,帶來更好的使用者體驗。快取用空間換時間,好的快取是快取命中率高的且資料量小的。快取是一種非常重要的技術。

1.0、再次封裝SqlSessionFactoryUtils

為了配置快取的學習我們將工具類再次封裝。

原SqlSessionFactoryUtil工具類如下:

package com.zhangguo.mybatis03.utils;

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; /** * MyBatis 會話工具類 * */ public class SqlSessionFactoryUtil { /** * 獲得會話工廠 * * */ public static SqlSessionFactory getFactory(){ InputStream inputStream
= null; SqlSessionFactory sqlSessionFactory=null; try{ //載入conf.xml配置檔案,轉換成輸入流 inputStream = SqlSessionFactoryUtil.class.getClassLoader().getResourceAsStream("mybatisCfg.xml"); //根據配置檔案的輸入流構造一個SQL會話工廠 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); }
finally { if(inputStream!=null){ try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return sqlSessionFactory; } /** * 獲得sql會話,是否自動提交 * */ public static SqlSession openSession(boolean isAutoCommit){ return getFactory().openSession(isAutoCommit); } /** * 關閉會話 * */ public static void closeSession(SqlSession session){ if(session!=null){ session.close(); } } }
View Code

上面的程式碼中當我們每次獲取SQLSession時都要例項化sqlSessionFactory,效率不高。可以使用單例改進:

package com.zhangguo.mybatis03.utils;

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;

/**
 * MyBatis會話工具類
 * */
public class SqlSessionFactoryUtils {

    /**會話工廠*/
    private static SqlSessionFactory factory;

    static {
        try {
            /*獲得配置檔案的檔案流*/
           InputStream inputStream=Resources.getResourceAsStream("mybatisCfg.xml");
           //初始化工廠
            factory=new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲得會話物件
     * 指定是否自動提交
     * */
    public static SqlSession openSqlSession(boolean isAutoCommit){
        return getFactory().openSession(isAutoCommit);
    }

    public static SqlSessionFactory getFactory() {
        return factory;
    }
    public static void setFactory(SqlSessionFactory factory) {
        SqlSessionFactoryUtils.factory = factory;
    }

    /**
     * 關閉會話
     * */
    public static void closeSession(SqlSession session){
        if(session!=null){
            session.close();
        }
    }
}

1.1、MyBatis快取概要

在一個系統中查詢的頻次遠遠高於增刪改,據三方統計不同系統比例在9:1-7:3之間。正如大多數持久層框架一樣,MyBatis 同樣提供了一級快取二級快取的支援

(1)、一級快取基於PerpetualCache 的 HashMap本地快取,其儲存作用域為 Session,當 Session flush close 之後,該Session中的所有 Cache 就將清空

(2)、二級快取與一級快取其機制相同,預設也是採用 PerpetualCache,HashMap儲存,不同在於其儲存作用域為 Mapper(Namespace)並且可自定義儲存源,如 Ehcache。

(3)、對於快取資料更新機制,當某一個作用域(一級快取Session/二級快取Namespaces)的進行了 C/U/D 操作後,預設該作用域下所有 select 中的快取將被clear。

1.2、預設MyBatis的一級快取是開啟的

測試用例:

    /**快取測試*/
    @Test
    public void cacheTest(){
        //開啟一個會話,不自動提交
        SqlSession session1 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器
        StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
        //查詢單個物件通過編號
        Student student1 = mapper1.selectStudentById(1);
        System.out.println(student1);
        
        Student student2 = mapper1.selectStudentById(1);
        System.out.println(student2);
        //關閉
        SqlSessionFactoryUtils.closeSession(session1);
    }

結果:

雖然查詢了二次,但只向資料庫傳送了一次SQL請求,因為第二次是在快取中獲得的資料。

1.3、一級快取僅在同一個會話(SQLSession)中有效

測試用例:

    /**快取測試*/
    @Test
    public void cacheTest(){
        //開啟一個會話1,不自動提交
        SqlSession session1 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器
        StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
        //查詢單個物件通過編號
        Student student1 = mapper1.selectStudentById(1);
        System.out.println(student1);

        //開啟一個會話2,不自動提交
        SqlSession session2 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器
        StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
        Student student2 = mapper2.selectStudentById(1);
        System.out.println(student2);
        //關閉
        SqlSessionFactoryUtils.closeSession(session1);
    }

結果:

從上圖可以看出此時並沒有使用快取,向資料庫查詢了二次,因為第二次查詢使用的是新的會話,而一級快取必須在同一個會話中。

1.4、清空一級快取

(1)、當對錶執行增刪改時快取將清空

測試用例:

    /**快取測試*/
    @Test
    public void cacheTest(){
        //開啟一個會話1,不自動提交
        SqlSession session1 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器
        StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
        //查詢單個物件通過編號
        Student student1 = mapper1.selectStudentById(1);
        System.out.println(student1);

        //執行更新
        Student lili=new Student();
        lili.setId(5);
        lili.setSex("girl");
        mapper1.updateStudent(lili);

        Student student2 = mapper1.selectStudentById(1);
        System.out.println(student2);

        SqlSessionFactoryUtils.closeSession(session1);
    }

結果:

從日誌中可以看出第二次查詢也傳送了sql到資料庫中,並沒有使用快取,是因為執行了更新操作快取已被清空。

此時資料庫中的資料其實並未真的更新,如下所示:

因為沒有手動提交,可以設定自動提交

/**快取測試*/
    @Test
    public void cacheTest(){
        //開啟一個會話1,不自動提交
        SqlSession session1 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器
        StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
        //查詢單個物件通過編號
        Student student1 = mapper1.selectStudentById(1);
        System.out.println(student1);

        //執行更新
        Student lili=new Student();
        lili.setId(5);
        lili.setSex("girl");
        mapper1.updateStudent(lili);

        Student student2 = mapper1.selectStudentById(1);
        System.out.println(student2);

        //提交
        session1.commit();
        SqlSessionFactoryUtils.closeSession(session1);
    }
View Code

提交後的結果

(2)、手動清空

測試用例:

   /**快取測試*/
    @Test
    public void cacheTest(){
        //開啟一個會話1,不自動提交
        SqlSession session1 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器
        StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
        //查詢單個物件通過編號
        Student student1 = mapper1.selectStudentById(1);
        System.out.println(student1);

        //執行手動更新
        session1.clearCache();

        Student student2 = mapper1.selectStudentById(1);
        System.out.println(student2);

        //提交
        session1.commit();
        SqlSessionFactoryUtils.closeSession(session1);
    }

結果:

從日誌中可以看到第二次查詢並未使用快取因為執行了手動清空快取,沒有快取可用則再次查詢資料庫。

小結:當Session flush或close之後,該Session中的所有 Cache 就將清空;執行CUD也將會自動清空;手動清空;

1.5、開啟二級快取

預設二級快取是不開啟的,需要手動進行配置

1.5.1、全域性開關

預設是true,如果它配成false,其餘各個Mapper XML檔案配成支援cache也沒用。

    <settings>
        <!--設定是否允許快取-->
        <setting name="cacheEnabled" value="true"/>
        <!--設定日誌輸出的目標-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

1.5.2、單個Mapper XML對映檔案開關

預設Mapper XML對映檔案是不採用cache。在配置檔案加一行就可以支援cache:

<?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.zhangguo.mybatis03.dao.StudentMapper">

    <cache/>
    
    <select id="selectStudentById" resultType="Student">
        SELECT id,name,sex from student where id=#{id}
    </select>
    
</mapper>

可以在開啟二級快取時候,手動配置一些屬性

<cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true"/>

各個屬性意義如下:

  • eviction:快取回收策略- LRU:最少使用原則,移除最長時間不使用的物件- FIFO:先進先出原則,按照物件進入快取順序進行回收- SOFT:軟引用,移除基於垃圾回收器狀態和軟引用規則的物件- WEAK:弱引用,更積極的移除移除基於垃圾回收器狀態和弱引用規則的物件
  • flushInterval:重新整理時間間隔,單位為毫秒,這裡配置的100毫秒。如果不配置,那麼只有在進行資料庫修改操作才會被動重新整理快取區
  • size:引用額數目,代表快取最多可以儲存的物件個數
  • readOnly:是否只讀,如果為true,則所有相同的sql語句返回的是同一個物件(有助於提高效能,但併發操作同一條資料時,可能不安全),如果設定為false,則相同的sql,後面訪問的是cache的clone副本。

1.5.3、Mapper statement開關

Mapper XML檔案配置支援cache後,檔案中所有的Mapper statement就支援了。此時要個別對待某條,需要:
<select id="selectStudentById" resultType="Student" useCache="false">
    SELECT id,name,sex from student where id=#{id}
</select>

可以在Mapper的具體方法下設定對二級快取的訪問意願:

  • useCache配置

    ​ 如果一條語句每次都需要最新的資料,就意味著每次都需要從資料庫中查詢資料,可以把這個屬性設定為false,如:

<select id="selectAll" useCache="false">
  • 重新整理快取(就是清空快取)

    ​ 二級快取預設會在insert、update、delete操作後重新整理快取,可以手動配置不更新快取,如下:

<update id="updateById" flushCache="false" />

1.5.4、實現可序列化介面

 如果未實現可序列化介面,會引發異常。

修改POJO物件,增加實現可序列化介面:

package com.zhangguo.mybatis03.entities;

import java.io.Serializable;

/**
 * 學生實體
 */
public class Student implements Serializable {
    private int id;
    private String name;
    private String sex;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}
View Code

1.5.5、注意事項

1、如果readOnly為false,此時要結果集物件是可序列化的。
<cache readOnly="false"/>
2、在SqlSession未關閉之前,如果對於同樣條件進行重複查詢,此時採用的是local session cache,而不是上面說的這些cache。 3、MyBatis快取查詢到的結果集物件,而非結果集資料,是將對映的POJO物件集合快取起來。 4、面對一定規模的資料量,內建的cache方式就派不上用場了; 5、對查詢結果集做快取並不是MyBatis框架擅長的,它專心做的應該是sql mapper。採用此框架的Application去構建快取更合理,比如採用Redis、Ehcache、OSCache、Memcached等

當我們的配置檔案配置了cacheEnabled=true時,就會開啟二級快取,二級快取是mapper級別的,也就說不同的sqlsession使用同一個mapper查詢是,查詢到的資料可能是另一個sqlsession做相同操作留下的快取。

查詢資料的順序為:二級快取 -> 一級快取 -> 資料庫

1.6、二級快取測試

預設情況下一級快取只在同一個會話中有效:

用例:

    /**快取測試*/
    @Test
    public void cacheTest(){
        //開啟一個會話1,不自動提交
        SqlSession session1 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器1
        StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
        //查詢單個物件通過編號
        Student student1 = mapper1.selectStudentById(1);
        System.out.println(student1);
        
        //開啟一個會話2,不自動提交
        SqlSession session2 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器2
        StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
        //查詢單個物件通過編號
        Student student2 = mapper2.selectStudentById(1);
        System.out.println(student2);

        SqlSessionFactoryUtils.closeSession(session1);
        SqlSessionFactoryUtils.closeSession(session2);
    }

結果:

如果需要將範圍擴大到同一個namespace中有效可以使用二級快取:

用例:

    /**快取測試*/
    @Test
    public void cacheTest(){
        //開啟一個會話1,不自動提交
        SqlSession session1 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器1
        StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
        //查詢單個物件通過編號
        Student student1 = mapper1.selectStudentById(1);
        System.out.println(student1);

        //必須手動提交,否則無效
        session1.commit();

        //開啟一個會話2,不自動提交
        SqlSession session2 = SqlSessionFactoryUtils.openSqlSession(false);
        //獲得一個對映器2
        StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
        //查詢單個物件通過編號
        Student student2 = mapper2.selectStudentById(1);
        System.out.println(student2);

        SqlSessionFactoryUtils.closeSession(session1);
        SqlSessionFactoryUtils.closeSession(session2);
    }

結果:

如果不手動提交查詢結果也不會快取成功。

使用兩個不同的SqlSession物件去執行相同查詢條件的查詢,第二次查詢時不會再發送SQL語句,而是直接從快取中取出資料

1.7、二級快取小結

  1. 對映語句檔案中的所有select語句將會被快取。

  2. 對映語句檔案中的所有insert,update和delete語句會重新整理快取。

  3. 快取會使用Least Recently Used(LRU,最近最少使用的)演算法來收回。

  4. 快取會根據指定的時間間隔來重新整理。

  5. 快取會儲存1024個物件

cache標籤常用屬性:

<cache 
eviction="FIFO"  <!--回收策略為先進先出-->
flushInterval="60000" <!--自動重新整理時間60s-->
size="512" <!--最多快取512個引用物件-->
readOnly="true"/> <!--只讀-->

二、MyBatis-Generator程式碼生成

2.1、在Intellij IDEA建立maven專案

這裡建立了一個Maven專案,未使用骨架。

2.2、新增依賴

在maven專案的pom.xml 新增mybatis-generator-maven-plugin 外掛

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zhangguo.mybatis06</groupId>
    <artifactId>MyBatis06</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!--MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <!--MySql資料庫驅動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
        <!-- JUnit單元測試工具 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <!-- mybatis-generator-core 反向生成java程式碼-->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.5</version>
        </dependency>
    </dependencies>


    <!--mybatis 程式碼生成外掛-->
    <build>
        <finalName>MyBatis06</finalName>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2.3、配置生成引數

在maven專案下的src/main/resources 目錄下建立名為 generatorConfig.xml的配置檔案,作為mybatis-generator-maven-plugin 外掛的執行目標:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

    <!--匯入屬性配置 -->
    <properties resource="db.properties"></properties>

    <!--指定特定資料庫的jdbc驅動jar包的位置 -->
    <classPathEntry location="${mysql.driverLocation}"/>

    <context id="default" targetRuntime="MyBatis3">

        <!-- optional,旨在建立class時,對註釋進行控制 -->
        <commentGenerator>
            <property name="suppressDate" value="true" />
        </commentGenerator>

        <!--jdbc的資料庫連線 -->
        <jdbcConnection driverClass="${mysql.driver}" connectionURL="${mysql.url}" userId="${mysql.username}" password="${mysql.password}">
        </jdbcConnection>

        <!-- 非必需,型別處理器,在資料庫型別和java型別之間的轉換控制-->
        <javaTypeResolver >
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- Model模型生成器,用來生成含有主鍵key的類,記錄類 以及查詢Example類
            targetPackage     指定生成的model生成所在的包名
            targetProject     指定在該專案下所在的路徑
        -->
        <javaModelGenerator targetPackage="com.zhangguo.mybatis06.entities" targetProject="src/main/java">
            <!-- 是否對model新增 建構函式 -->
            <property name="constructorBased" value="true"/>

            <!-- 是否允許子包,即targetPackage.schemaName.tableName -->
            <property name="enableSubPackages" value="false"/>

            <!-- 建立的Model物件是否 不可改變  即生成的Model物件不會有 setter方法,只有構造方法 -->
            <property name="immutable" value="true"/>

            <!-- 給Model新增一個父類 -->
            <property name="rootClass" value="com.zhangguo.mybatis06.entities.BaseEntity"/>

            <!-- 是否對類CHAR型別的列的資料進行trim操作 -->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!--Mapper對映檔案生成所在的目錄 為每一個數據庫的表生成對應的SqlMap檔案 -->
        <sqlMapGenerator targetPackage="com.zhangguo.mybatis06.mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>


        <!-- 客戶端程式碼,生成易於使用的針對Model物件和XML配置檔案 的程式碼
                type="ANNOTATEDMAPPER",生成Java Model 和基於註解的Mapper物件
                type="MIXEDMAPPER",生成基於註解的Java Model 和相應的Mapper物件
                type="XMLMAPPER",生成SQLMap XML檔案和獨立的Mapper介面
        -->
        <javaClientGenerator targetPackage="com.zhangguo.mybatis06.dao" targetProject="src/main/java" type="MIXEDMAPPER">
            <property name="enableSubPackages" value=""/>
            <!--
                    定義Maper.java 原始碼中的ByExample() 方法的可視性,可選的值有:
                    public;
                    private;
                    protected;
                    default
                    注意:如果 targetRuntime="MyBatis3",此引數被忽略
             -->
            <property name="exampleMethodVisibility" value=""/>
            <!--
               方法名計數器
              Important note: this property is ignored if the target runtime is MyBatis3.
             -->
            <property name="methodNameCalculator" value=""/>

            <!--