1. 程式人生 > >MyBatis的使用和SSM框架基礎

MyBatis的使用和SSM框架基礎

1. MyBatis與Hibernate

1.1 Hibernate 簡介

Hibernate對資料庫結構提供了較為完整的封裝,Hibernate的O/R Mapping實現了POJO 和資料庫表之間的對映,以及SQL 的自動生成和執行。程式設計師往往只需定義好了POJO 到資料庫表的對映關係,即可通過Hibernate 提供的方法完成持久層操作。程式設計師甚至不需要對SQL 的熟練掌握, Hibernate/OJB 會根據制定的儲存邏輯,自動生成對應的SQL 並呼叫JDBC 介面加以執行。

1.2 MyBatis簡介

MyBatis的著力點,則在於POJO 與SQL之間的對映關係。然後通過對映配置檔案,將SQL所需的引數,以及返回的結果欄位對映到指定POJO。 相對Hibernate“O/R”而言,MyBatis是一種“Sql Mapping”的ORM實現。

1.3 MyBatis與Hibernate之間的區別

1.3.1 兩者相同點

Hibernate與MyBatis都可以是通過SessionFactoryBuider由XML配置檔案生成SessionFactory,然後由SessionFactory 生成Session,最後由Session來開啟執行事務和SQL語句。其中SessionFactoryBuider,SessionFactory,Session的生命週期都是差不多的。
Hibernate和MyBatis都支援JDBC和JTA事務處理。

1.3.2 Mybatis優勢

MyBatis可以進行更為細緻的SQL優化,可以減少查詢欄位。
MyBatis容易掌握,而Hibernate門檻較高。

1.3.3 Hibernate優勢

Hibernate的DAO層開發比MyBatis簡單,Mybatis需要維護SQL和結果對映。
Hibernate對物件的維護和快取要比MyBatis好,對增刪改查的物件的維護要方便。
Hibernate資料庫移植性很好,MyBatis的資料庫移植性不好,不同的資料庫需要寫不同SQL。
Hibernate有更好的二級快取機制,可以使用第三方快取。MyBatis本身提供的快取機制不佳。

1.3.4 他人總結

Hibernate功能強大,資料庫無關性好,O/R對映能力強,如果你對Hibernate相當精通,而且對Hibernate進行了適當的封裝,那麼你的專案整個持久層程式碼會相當簡單,需要寫的程式碼很少,開發速度很快,非常爽。 
Hibernate的缺點就是學習門檻不低,要精通門檻更高,而且怎麼設計O/R對映,在效能和物件模型之間如何權衡取得平衡,以及怎樣用好Hibernate方面需要你的經驗和能力都很強才行。 
MyBatis入門簡單,即學即用,提供了資料庫查詢的自動物件繫結功能,而且延續了很好的SQL使用經驗,對於沒有那麼高的物件模型要求的專案來說,相當完美。 
MyBatis的缺點就是框架還是比較簡陋,功能尚有缺失,雖然簡化了資料繫結程式碼,但是整個底層資料庫查詢實際還是要自己寫的,工作量也比較大,而且不太容易適應快速資料庫修改。

2. MyBatis實現CRUD

MyBatis實現CRUD有配置檔案和註解兩種方式。

2.1 Mapping配置檔案實現CRUD

1、定義sql對映xml檔案
  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">
<!-- 為這個mapper指定一個唯一的namespace,namespace的值習慣上設定成包名+sql對映檔名,這樣就能夠保證namespace的值是唯一的
例如namespace="me.gacl.mapping.userMapper"就是me.gacl.mapping(包名)+userMapper(userMapper.xml檔案去除字尾)
 -->
<mapper namespace="me.gacl.mapping.userMapper">
    <!-- 在select標籤中編寫查詢的SQL語句, 設定select標籤的id屬性為getUser,id屬性值必須是唯一的,不能夠重複
    使用parameterType屬性指明查詢時使用的引數型別,resultType屬性指明查詢返回的結果集型別
    resultType="me.gacl.domain.User"就表示將查詢結果封裝成一個User類的物件返回
    User類就是users表所對應的實體類
    -->
    <!-- 
        根據id查詢得到一個user物件
     -->
    <select id="getUser" parameterType="int" 
        resultType="me.gacl.domain.User">
        select * from users where id=#{id}
    </select>
    
    <!-- 建立使用者(Create) -->
    <insert id="addUser" parameterType="me.gacl.domain.User">
        insert into users(name,age) values(#{name},#{age})
    </insert>
    
    <!-- 刪除使用者(Remove) -->
    <delete id="deleteUser" parameterType="int">
        delete from users where id=#{id}
    </delete>
    
    <!-- 修改使用者(Update) -->
    <update id="updateUser" parameterType="me.gacl.domain.User">
        update users set name=#{name},age=#{age} where id=#{id}
    </update>
    
    <!-- 查詢全部使用者-->
    <select id="getAllUsers" resultType="me.gacl.domain.User">
        select * from users
    </select>
    
</mapper>

2.  定義pojo類作為實體

User類的內容如下:

package me.gacl.domain;

/**
 * @author gacl
 * users表所對應的實體類
 */
public class User {

    //實體類的屬性和表的欄位名稱一一對應
    private int id;
    private String name;
    private int age;

    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 int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
}

3. 定義conf.xml配置對映檔案

conf.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" />
                <property name="username" value="root" />
                <property name="password" value="XDP" />
            </dataSource>
        </environment>
    </environments>
    
</configuration>

4. 測試類呼叫方法

package me.gacl.test;

import java.util.List;
import me.gacl.domain.User;
import me.gacl.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class TestCRUDByXmlMapper {

    @Test
    public void testAdd(){
        //SqlSession sqlSession = MyBatisUtil.getSqlSession(false);
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);
        /**
         * 對映sql的標識字串,
         * me.gacl.mapping.userMapper是userMapper.xml檔案中mapper標籤的namespace屬性的值,
         * addUser是insert標籤的id屬性值,通過insert標籤的id屬性值就可以找到要執行的SQL
         */
        String statement = "me.gacl.mapping.userMapper.addUser";//對映sql的標識字串
        User user = new User();
        user.setName("使用者孤傲蒼狼");
        user.setAge(20);
        //執行插入操作
        int retResult = sqlSession.insert(statement,user);
        //手動提交事務
        //sqlSession.commit();
        //使用SqlSession執行完SQL之後需要關閉SqlSession
        sqlSession.close();
        System.out.println(retResult);
    }
    
    @Test
    public void testUpdate(){
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);
        /**
         * 對映sql的標識字串,
         * me.gacl.mapping.userMapper是userMapper.xml檔案中mapper標籤的namespace屬性的值,
         * updateUser是update標籤的id屬性值,通過update標籤的id屬性值就可以找到要執行的SQL
         */
        String statement = "me.gacl.mapping.userMapper.updateUser";//對映sql的標識字串
        User user = new User();
        user.setId(3);
        user.setName("孤傲蒼狼");
        user.setAge(25);
        //執行修改操作
        int retResult = sqlSession.update(statement,user);
        //使用SqlSession執行完SQL之後需要關閉SqlSession
        sqlSession.close();
        System.out.println(retResult);
    }
    
    @Test
    public void testDelete(){
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);
        /**
         * 對映sql的標識字串,
         * me.gacl.mapping.userMapper是userMapper.xml檔案中mapper標籤的namespace屬性的值,
         * deleteUser是delete標籤的id屬性值,通過delete標籤的id屬性值就可以找到要執行的SQL
         */
        String statement = "me.gacl.mapping.userMapper.deleteUser";//對映sql的標識字串
        //執行刪除操作
        int retResult = sqlSession.delete(statement,5);
        //使用SqlSession執行完SQL之後需要關閉SqlSession
        sqlSession.close();
        System.out.println(retResult);
    }
    
    @Test
    public void testGetAll(){
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        /**
         * 對映sql的標識字串,
         * me.gacl.mapping.userMapper是userMapper.xml檔案中mapper標籤的namespace屬性的值,
         * getAllUsers是select標籤的id屬性值,通過select標籤的id屬性值就可以找到要執行的SQL
         */
        String statement = "me.gacl.mapping.userMapper.getAllUsers";//對映sql的標識字串
        //執行查詢操作,將查詢結果自動封裝成List<User>返回
        List<User> lstUsers = sqlSession.selectList(statement);
        //使用SqlSession執行完SQL之後需要關閉SqlSession
        sqlSession.close();
        System.out.println(lstUsers);
    }
}


用來獲取Session的工具類如下:

package me.gacl.util;

import java.io.InputStream;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisUtil {

    /**
     * 獲取SqlSessionFactory
     * @return SqlSessionFactory
     */
    public static SqlSessionFactory getSqlSessionFactory() {
        String resource = "conf.xml";
        InputStream is = MyBatisUtil.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        return factory;
    }
    
    /**
     * 獲取SqlSession
     * @return SqlSession
     */
    public static SqlSession getSqlSession() {
        return getSqlSessionFactory().openSession();
    }
    
    /**
     * 獲取SqlSession
     * @param isAutoCommit 
     *         true 表示建立的SqlSession物件在執行完SQL之後會自動提交事務
     *         false 表示建立的SqlSession物件在執行完SQL之後不會自動提交事務,這時就需要我們手動呼叫sqlSession.commit()提交事務
     * @return SqlSession
     */
    public static SqlSession getSqlSession(boolean isAutoCommit) {
        return getSqlSessionFactory().openSession(isAutoCommit);
    }
}

就此實現了基於配置檔案方式的CRUD操作。

2.2 註解方式實現CRUD

1. 定義sql對映的介面

UserMapperI介面的程式碼如下:

package me.gacl.mapping;

import java.util.List;
import me.gacl.domain.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

/**
 * @author gacl
 * 定義sql對映的介面,使用註解指明方法要執行的SQL
 */
public interface UserMapperI {

    //使用@Insert註解指明add方法要執行的SQL
    @Insert("insert into users(name, age) values(#{name}, #{age})")
    public int add(User user);
    
    //使用@Delete註解指明deleteById方法要執行的SQL
    @Delete("delete from users where id=#{id}")
    public int deleteById(int id);
    
    //使用@Update註解指明update方法要執行的SQL
    @Update("update users set name=#{name},age=#{age} where id=#{id}")
    public int update(User user);
    
    //使用@Select註解指明getById方法要執行的SQL
    @Select("select * from users where id=#{id}")
    public User getById(int id);
    
    //使用@Select註解指明getAll方法要執行的SQL
    @Select("select * from users")
    public List<User> getAll();
}

需要說明的是,我們不需要針對UserMapperI介面去編寫具體的實現類程式碼,這個具體的實現類由MyBatis幫我們動態構建出來,我們只需要直接拿來使用即可。

2. 在conf.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" />
                <property name="username" value="root" />
                <property name="password" value="XDP" />
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <!-- 註冊userMapper.xml檔案, 
        userMapper.xml位於me.gacl.mapping這個包下,所以resource寫成me/gacl/mapping/userMapper.xml-->
        <mapper resource="me/gacl/mapping/userMapper.xml"/>
        <!-- 註冊UserMapper對映介面-->
        <mapper class="me.gacl.mapping.UserMapperI"/>
    </mappers>
    
</configuration>

3.  測試類呼叫方法

package me.gacl.test;

import java.util.List;
import me.gacl.domain.User;
import me.gacl.mapping.UserMapperI;
import me.gacl.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class TestCRUDByAnnotationMapper {

    @Test
    public void testAdd(){
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);
        //得到UserMapperI介面的實現類物件,UserMapperI介面的實現類物件由sqlSession.getMapper(UserMapperI.class)動態構建出來
        UserMapperI mapper = sqlSession.getMapper(UserMapperI.class);
        User user = new User();
        user.setName("使用者xdp");
        user.setAge(20);
        int add = mapper.add(user);
        //使用SqlSession執行完SQL之後需要關閉SqlSession
        sqlSession.close();
        System.out.println(add);
    }
    
    @Test
    public void testUpdate(){
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);
        //得到UserMapperI介面的實現類物件,UserMapperI介面的實現類物件由sqlSession.getMapper(UserMapperI.class)動態構建出來
        UserMapperI mapper = sqlSession.getMapper(UserMapperI.class);
        User user = new User();
        user.setId(3);
        user.setName("孤傲蒼狼_xdp");
        user.setAge(26);
        //執行修改操作
        int retResult = mapper.update(user);
        //使用SqlSession執行完SQL之後需要關閉SqlSession
        sqlSession.close();
        System.out.println(retResult);
    }
    
    @Test
    public void testDelete(){
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);
        //得到UserMapperI介面的實現類物件,UserMapperI介面的實現類物件由sqlSession.getMapper(UserMapperI.class)動態構建出來
        UserMapperI mapper = sqlSession.getMapper(UserMapperI.class);
        //執行刪除操作
        int retResult = mapper.deleteById(7);
        //使用SqlSession執行完SQL之後需要關閉SqlSession
        sqlSession.close();
        System.out.println(retResult);
    }
    
    @Test
    public void testGetUser(){
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //得到UserMapperI介面的實現類物件,UserMapperI介面的實現類物件由sqlSession.getMapper(UserMapperI.class)動態構建出來
        UserMapperI mapper = sqlSession.getMapper(UserMapperI.class);
        //執行查詢操作,將查詢結果自動封裝成User返回
        User user = mapper.getById(8);
        //使用SqlSession執行完SQL之後需要關閉SqlSession
        sqlSession.close();
        System.out.println(user);
    }
    
    @Test
    public void testGetAll(){
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //得到UserMapperI介面的實現類物件,UserMapperI介面的實現類物件由sqlSession.getMapper(UserMapperI.class)動態構建出來
        UserMapperI mapper = sqlSession.getMapper(UserMapperI.class);
        //執行查詢操作,將查詢結果自動封裝成List<User>返回
        List<User> lstUsers = mapper.getAll();
        //使用SqlSession執行完SQL之後需要關閉SqlSession
        sqlSession.close();
        System.out.println(lstUsers);
    }
}

3. MyBatis使用問題

3.1 屬性名不一致

當實體類中的屬性名和表中的欄位名不一致時,使用MyBatis進行查詢操作時無法查詢出相應的結果的問題以及針對問題採用的兩種辦法:  

解決辦法一: 通過在查詢的sql語句中定義欄位名的別名,讓欄位名的別名和實體類的屬性名一致,這樣就可以表的欄位名和實體類的屬性名一一對應上了,這種方式是通過在sql語句中定義別名來解決欄位名和屬性名的對映關係的。  

解決辦法二: 通過<resultMap>來對映欄位名和實體類屬性名的一一對應關係。這種方式是使用MyBatis提供的解決方式來解決欄位名和屬性名的對映關係的。

    <!-- 
    根據id查詢得到一個order物件,使用這個查詢是可以正常查詢到我們想要的結果的,
    這是因為我們通過<resultMap>對映實體類屬性名和表的欄位名一一對應關係 -->
    <select id="selectOrderResultMap" parameterType="int" resultMap="orderResultMap">
        select * from orders where order_id=#{id}
    </select>
    <!--通過<resultMap>對映實體類屬性名和表的欄位名對應關係 -->
    <resultMap type="me.gacl.domain.Order" id="orderResultMap">
        <!-- 用id屬性來對映主鍵欄位 -->
        <id property="id" column="order_id"/>
        <!-- 用result屬性來對映非主鍵欄位 -->
        <result property="orderNo" column="order_no"/>
        <result property="price" column="order_price"/>
    </resultMap>

3.2 關聯查詢

3.2.1 一對一關聯

一對一關聯查詢可以採用巢狀結果和巢狀查詢兩種方式,巢狀結果基於SQL語句的方式,巢狀查詢是MyBatis提供的方式。

    <!-- 
    方式一:巢狀結果:使用巢狀結果對映來處理重複的聯合結果的子集
             封裝聯表查詢的資料(去除重複的資料)
        select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=1
    -->
    <select id="getClass" parameterType="int" resultMap="ClassResultMap">
        select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
    </select>
    <!-- 使用resultMap對映實體類和欄位之間的一一對應關係 -->
    <resultMap type="me.gacl.domain.Classes" id="ClassResultMap">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" javaType="me.gacl.domain.Teacher">
            <id property="id" column="t_id"/>
            <result property="name" column="t_name"/>
        </association>
    </resultMap>
    
    <!-- 
    方式二:巢狀查詢:通過執行另外一個SQL對映語句來返回預期的複雜型別
        SELECT * FROM class WHERE c_id=1;
        SELECT * FROM teacher WHERE t_id=1   //1 是上一個查詢得到的teacher_id的值
    -->
     <select id="getClass2" parameterType="int" resultMap="ClassResultMap2">
        select * from class where c_id=#{id}
     </select>
     <!-- 使用resultMap對映實體類和欄位之間的一一對應關係 -->
     <resultMap type="me.gacl.domain.Classes" id="ClassResultMap2">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" select="getTeacher"/>
     </resultMap>
     
     <select id="getTeacher" parameterType="int" resultType="me.gacl.domain.Teacher">
        SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
     </select>


<resultmap>用於對對映進行一一對應,當使用巢狀結果時需要使用<association>標籤指明內層結果的對應關係,巢狀查詢使用<association>標籤定義,其中“select”項為巢狀的查詢操作。

3.2.2 一對多關聯

MyBatis中使用association標籤來解決一對一的關聯查詢,association標籤可用的屬性如下:

  1. property:物件屬性的名稱
  2. javaType:物件屬性的型別
  3. column:所對應的外來鍵欄位名稱
  4. select:使用另一個查詢封裝的結果
    <!-- 
    方式一: 巢狀結果: 使用巢狀結果對映來處理重複的聯合結果的子集
    SELECT * FROM class c, teacher t,student s WHERE c.teacher_id=t.t_id AND c.C_id=s.class_id AND  c.c_id=1
     -->
    <select id="getClass3" parameterType="int" resultMap="ClassResultMap3">
        select * from class c, teacher t,student s where c.teacher_id=t.t_id and c.C_id=s.class_id and  c.c_id=#{id}
    </select>
    <resultMap type="me.gacl.domain.Classes" id="ClassResultMap3">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher">
            <id property="id" column="t_id"/>
            <result property="name" column="t_name"/>
        </association>
        <!-- ofType指定students集合中的物件型別 -->
        <collection property="students" ofType="me.gacl.domain.Student">
            <id property="id" column="s_id"/>
            <result property="name" column="s_name"/>
        </collection>
    </resultMap>
    
    <!-- 
        方式二:巢狀查詢:通過執行另外一個SQL對映語句來返回預期的複雜型別
            SELECT * FROM class WHERE c_id=1;
            SELECT * FROM teacher WHERE t_id=1   //1 是上一個查詢得到的teacher_id的值
            SELECT * FROM student WHERE class_id=1  //1是第一個查詢得到的c_id欄位的值
     -->
     <select id="getClass4" parameterType="int" resultMap="ClassResultMap4">
        select * from class where c_id=#{id}
     </select>
     <resultMap type="me.gacl.domain.Classes" id="ClassResultMap4">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher" select="getTeacher2"></association>
        <collection property="students" ofType="me.gacl.domain.Student" column="c_id" select="getStudent"></collection>
     </resultMap>

MyBatis中使用collection標籤來解決一對多的關聯查詢,ofType屬性指定集合中元素的物件型別。

4. MyBatis快取介紹

正如大多數持久層框架一樣,MyBatis 同樣提供了一級快取和二級快取的支援
一級快取: 基於PerpetualCache 的 HashMap本地快取,其儲存作用域為 Session,當 Session flush 或 close 之後,該Session中的所有 Cache 就將清空。
二級快取與一級快取其機制相同,預設也是採用 PerpetualCache,HashMap儲存,不同在於其儲存作用域為 Mapper(Namespace),並且可自定義儲存源,如 Ehcache。


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

4.1 一級快取

第一次發出一個查詢sql,sql查詢結果寫入sqlsession的一級快取中,快取使用的資料結構是一個map<key,value>。
key:hashcode+sql+sql輸入引數+輸出引數(sql的唯一標識)
value:使用者資訊
同一個sqlsession再次發出相同的sql,就從快取中取,而不走資料庫。如果兩次中間出現commit操作(修改、新增、刪除),本sqlsession中的一級快取區域全部清空,下次再去快取中查詢不到所以要從資料庫查詢,從資料庫查詢到再寫入快取。即對於查詢操作,每次查詢都先從快取中查詢,如果快取中查詢到資料則將快取資料直接返回,如果快取中查詢不到就從資料庫查詢。

mybatis預設支援一級快取不需要配置。

package me.gacl.test;

import me.gacl.domain.User;
import me.gacl.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

/**
 * @author gacl
 * 測試一級快取
 */
public class TestOneLevelCache {
    
    /*
     * 一級快取: 也就Session級的快取(預設開啟)
     */
    @Test
    public void testCache1() {
        SqlSession session = MyBatisUtil.getSqlSession();
        String statement = "me.gacl.mapping.userMapper.getUser";
        User user = session.selectOne(statement, 1);
        System.out.println(user);
        
        /*
         * 一級快取預設就會被使用
         */
        user = session.selectOne(statement, 1);
        System.out.println(user);
        session.close();
        /*
         1. 必須是同一個Session,如果session物件已經close()過了就不可能用了 
         */
        session = MyBatisUtil.getSqlSession();
        user = session.selectOne(statement, 1);
        System.out.println(user);
        
        /*
         2. 查詢條件是一樣的
         */
        user = session.selectOne(statement, 2);
        System.out.println(user);
        
        /*
         3. 沒有執行過session.clearCache()清理快取
         */
        //session.clearCache(); 
        user = session.selectOne(statement, 2);
        System.out.println(user);
        
        /*
         4. 沒有執行過增刪改的操作(這些操作都會清理快取)
         */
        session.update("me.gacl.mapping.userMapper.updateUser",
                new User(2, "user", 23));
        user = session.selectOne(statement, 2);
        System.out.println(user);
        
    }
}


注意:mybatis和spring整合後進行mapper代理開發,不支援一級快取,mybatis和spring整合,spring按照mapper的模板去生成mapper代理物件,模板中在最後統一關閉sqlsession。

4.2 二級快取原理

二級快取的範圍是mapper級別(mapper同一個名稱空間),mapper以名稱空間為單位建立快取資料結構,結構是map<key、value>。

過程:每次查詢先看是否開啟二級快取,如果開啟從二級快取的資料結構中取快取資料,如果從二級快取沒有取到,再從一級快取中找,如果一級快取也沒有,從資料庫查詢。

不像一級快取那樣mybatis自動開啟一級快取,mybatis是預設關閉二級快取的,所以我們需要需要進行兩個操作才能開啟二級快取:

1. 在核心配置檔案SqlMapperConfig.xml中加入:

<mapper namespace="me.gacl.mapping.userMapper">
<!-- 開啟二級快取 -->
<cache/>

屬性值cacheEnabled表示對在此配置檔案下的所有cache 進行全域性性開/關設定,它的可選值為true|false,預設值為true.
2.在你的Mapper對映檔案中新增一行:<cache /> ,表示此mapper開啟二級快取。

package me.gacl.test;

import me.gacl.domain.User;
import me.gacl.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;

/**
 * @author gacl
 * 測試二級快取
 */
public class TestTwoLevelCache {
    
    /*
     * 測試二級快取
     * 使用兩個不同的SqlSession物件去執行相同查詢條件的查詢,第二次查詢時不會再發送SQL語句,而是直接從快取中取出資料
     */
    @Test
    public void testCache2() {
        String statement = "me.gacl.mapping.userMapper.getUser";
        SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
        //開啟兩個不同的SqlSession
        SqlSession session1 = factory.openSession();
        SqlSession session2 = factory.openSession();
        //使用二級快取時,User類必須實現一個Serializable介面===> User implements Serializable
        User user = session1.selectOne(statement, 1);
        session1.commit();//不懂為啥,這個地方一定要提交事務之後二級快取才會起作用
        System.out.println("user="+user);
        
        //由於使用的是兩個不同的SqlSession物件,所以即使查詢條件相同,一級快取也不會開啟使用
        user = session2.selectOne(statement, 1);
        //session2.commit();
        System.out.println("user2="+user);
    }
}

5. Spring+MyBatis+ehcache整合

分散式管理快取資料有助於提升查詢效能,可以把快取資料的管理託管給分散式快取框架。MyBatis自己的二級快取,它在自己內部提供了一個cache介面,我們只要實現了cache介面就可以把快取資料靈活的管理起來。步驟可以分為如下的四步。
1. spring載入ehcache配置檔案

   <!-- 使用ehcache快取 -->    
   <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">  
     <property name="configLocation" value="classpath:ehcache.xml" />  
   </bean>

2. 配置ehcache.xml配置檔案

<?xml version="1.0" encoding="UTF-8"?>  
<ehcache>
  <!-- 
    maxElementsInMemory:快取中最大允許建立的物件數
    maxInMemory:設定記憶體中建立物件的最大值。
    eternal:設定元素(譯註:記憶體中物件)是否永久駐留。如果是,將忽略超時限制且元素永不消亡。
    timeToIdleSeconds:設定某個元素消亡前的停頓時間。
    timeToLiveSeconds:為元素設定消亡前的生存時間. 
    overflowToDisk:設定當記憶體中快取達到 maxInMemory 限制時元素是否可寫到磁碟上。
    memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。
    diskPersistent:重啟時記憶體不持久化到硬碟。
   -->

    <diskStore path="java.io.tmpdir"/>
    <defaultCache maxElementsInMemory="10000" memoryStoreEvictionPolicy="LRU" eternal="false"
    timeToIdleSeconds="300" timeToLiveSeconds="300" overflowToDisk="false" diskPersistent="false" />

    <cache name="districtDataCache"
       maxElementsInMemory="4000"
       eternal="true"
       overflowToDisk="false"
       diskPersistent="false"
       memoryStoreEvictionPolicy="LRU"/>    
</ehcache>

 diskStore:指定資料在磁碟中的儲存位置。
 defaultCache:當藉助CacheManager.add("demoCache")建立Cache時,EhCache便會採用<defalutCache/>指定的的管理策略
以下屬性是必須的:
 maxElementsInMemory - 在記憶體中快取的element的最大數目 
 maxElementsOnDisk - 在磁碟上快取的element的最大數目,若是0表示無窮大
 eternal - 設定快取的elements是否永遠不過期。如果為true,則快取的資料始終有效,如果為false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷
 overflowToDisk - 設定當記憶體快取溢位的時候是否將過期的element快取到磁碟上
以下屬性是可選的:
 timeToIdleSeconds - 當快取在EhCache中的資料前後兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些資料便會刪除,預設值是0,也就是可閒置時間無窮大
 timeToLiveSeconds - 快取element的有效生命期,預設是0.,也就是element存活時間無窮大
diskSpoolBufferSizeMB 這個引數設定DiskStore(磁碟快取)的快取區大小.預設是30MB.每個Cache都應該有自己的一個緩衝區.
 diskPersistent - 在VM重啟的時候是否啟用磁碟儲存EhCache中的資料,預設是false。
 diskExpiryThreadIntervalSeconds - 磁碟快取的清理執行緒執行間隔,預設是120秒。每個120s,相應的執行緒會進行一次EhCache中資料的清理工作
 memoryStoreEvictionPolicy - 當記憶體快取達到最大,有新的element加入的時候, 移除快取中element的策略。預設是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出)
 

3:修改相應的mapper.xml

在對應的mapper.xml裡面加上:

  <cache readOnly="true">
  <property name="timeToIdleSeconds" value="3600"/><!--1 hour-->  
    <property name="timeToLiveSeconds" value="3600"/><!--1 hour-->  
    <property name="maxEntriesLocalHeap" value="1000"/>  
    <property name="maxEntriesLocalDisk" value="10000000"/>  
    <property name="memoryStoreEvictionPolicy" value="LRU"/>  
  </cache>

(1)property引數配置不加也可以,都會有一個預設值,大家也可以查查一共有哪些配置,然後根據自己的需要來配置,然後這個配置是會帶上cache執行的日誌,如果不要帶日誌可以把LogginEhcache改成EhcacheCache。 
(2)如果readOnly為false,此時要結果集物件必須是可序列化的。需要將實體物件implements Serializable
4:useCache開關
在mapper.xml這樣設定了預設是全部操作都會執行快取策略,如果有某些sql不需要執行,可以把useCache設定為false。

<select id="selectUser" resultMap="BaseResultMap" parameterType="XX.XX.XX.XX.User" useCache="false" >


侷限性:

  mybatis二級快取對細粒度的資料級別的快取實現不好,比如如下需求:對商品資訊進行快取,由於商品資訊查詢訪問量大,但是要求使用者每次都能查詢最新的商品資訊,此時如果使用mybatis的二級快取就無法實現當一個商品變化時只重新整理該商品的快取資訊而不重新整理其它商品的資訊,因為mybaits的二級快取區域以mapper為單位劃分,當一個商品資訊變化會將所有商品資訊的快取資料全部清空。解決此類問題需要在業務層根據需求對資料有針對性快取。

6. MyBatis Generator的使用

MyBatis Generator是MyBatis提供的逆向工具,可自動通過資料表生成pojo類、Mapping和Dao介面。

在Eclipse中可以使用MyBatis Generator外掛完成,或者使用命令列生成(差距不大),核心在於配置檔案和依賴jar包。

generatorConfig.xml

<?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>
    <!--資料庫驅動-->
    <classPathEntry location="mysql-connector-java-5.0.8-bin.jar"/><!--和當前配置檔案處在同一資料夾-->
    <context id="DB2Tables"    targetRuntime="MyBatis3">
        <commentGenerator>
            <property name="suppressDate" value="true"/><!-- 是否生成註釋代時間戳 -->
            <property name="suppressAllComments" value="true"/><!-- 是否取消註釋 -->
        </commentGenerator>
        <!--資料庫連結地址賬號密碼-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost/ironblog" userId="root" password="password">
        </jdbcConnection>
        <!--生成Model類存放位置-->
        <javaModelGenerator targetPackage="com.zenhobby.pojo" targetProject="Ironblog\src\main\java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!--生成對映檔案存放位置-->
        <sqlMapGenerator targetPackage="com.zenhobby.mapping" targetProject="Ironblog\src\main\java">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!--生成Dao類存放位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.zenhobby.dao" targetProject="Ironblog\src\main\java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!--生成對應表及類名-->
        <table tableName="article" domainObjectName="Article" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table><!-- domainObjectName配置生成的pojo的型別 -->
    </context>
</generatorConfiguration>

按照此配置需要把資料庫連線jar包、mybatis generator包和mybatis驅動包放置在相同路徑下,使用如下命令生成:

java -jar mybatis-generator-core-1.3.2.jar -configfile generatorConfig.xml -overwrite