Mybatis 入門

學習來源: Bilibili 碰見狂神說 Mybatis最新完整教程IDEA版通俗易懂

環境搭建

Maven模塊導入

pom.xmlhtml

<!-- mysql-connector-java -->
<!-- mybatis -->
<!-- junit -->

<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-config.xmljava

<configuration>
    <!-- 加載類路徑下的屬性文件 -->
    <properties resource="db.properties"/>

    <!-- 默認鏈接環境配置 -->
    <environments default="mysql_developer">
        <!-- 鏈接環境信息 -->
        <environment id="mysql_developer">
            <!-- mybatis使用jdbc事務管理方式 -->
            <transactionManager type="jdbc"/>
            <!-- 使用鏈接池的方式來獲取鏈接 -->
            <dataSource type="pooled">
                <!-- 配置與數據庫交互的4個必要屬性 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3307/lpxz_blog?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Hongkong" />
                <property name="username" value="test" />
                <property name="password" value="1234" />
            </dataSource>
        </environment>
    </environments>
</configuration>

編寫 Mybatis 工具類獲取 sqlSession

MybatisUtil.javamysql

/* 加載 mybatis-config.xml 配置文件 */
static {
    try {
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

/**
     * 獲取SqlSession
     */
public static SqlSession getSqlSession() {
    // 從當前線程中獲取 SqlSession 對象
    SqlSession sqlSession = threadLocal.get();
    // 若是 SqlSession 對象爲空
    if (sqlSession == null) {
        // 在SqlSessionFactory 非空的狀況下,獲取 SqlSession 對象
        sqlSession = sqlSessionFactory.openSession();
        // 將 SqlSession 對象與當前線程綁定在⼀起
        threadLocal.set(sqlSession);
    }
    // 返回 SqlSession 對象
    return sqlSession;
}

SqlSessionFactoryBuildersql

​ 這個類能夠被實例化、使用和丟棄,一旦建立了 SqlSessionFactory,就再也不須要它了。 所以 SqlSessionFactoryBuilder 實例的最佳做用域是方法做用域(也就是局部方法變量)。 你能夠重用 SqlSessionFactoryBuilder 來建立多個 SqlSessionFactory 實例,但最好仍是不要一直保留着它,以保證全部的 XML 解析資源能夠被釋放給更重要的事情。數據庫

SqlSessionFactoryapache

​ SqlSessionFactory 一旦被建立就應該在應用的運行期間一直存在,沒有任何理由丟棄它或從新建立另外一個實例。 使用 SqlSessionFactory 的最佳實踐是在應用運行期間不要重複建立屢次,屢次重建 SqlSessionFactory 被視爲一種代碼「壞習慣」。所以 SqlSessionFactory 的最佳做用域是應用做用域。 有不少方法能夠作到,最簡單的就是使用單例模式或者靜態單例模式。編程

SqlSession緩存

​ 每一個線程都應該有它本身的 SqlSession 實例。SqlSession 的實例不是線程安全的,所以是不能被共享的,因此它的最佳的做用域是請求或方法做用域。 絕對不能將 SqlSession 實例的引用放在一個類的靜態域,甚至一個類的實例變量也不行。 也毫不能將 SqlSession 實例的引用放在任何類型的託管做用域中,好比 Servlet 框架中的 HttpSession。 若是你如今正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求類似的做用域中。 換句話說,每次收到 HTTP 請求,就能夠打開一個 SqlSession,返回一個響應後,就關閉它。 這個關閉操做很重要,爲了確保每次都能執行關閉操做,你應該把這個關閉操做放到 finally 塊中。 下面的示例就是一個確保 SqlSession 關閉的標準模式:安全

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的應用邏輯代碼
}

​ 在全部代碼中都遵循這種使用模式,能夠保證全部數據庫資源都能被正確地關閉。session

Dao 層

UserInfoDao.java

public interface UserInfoDao {
    List<UserInfo> getUserInfoList();
}

接口實現類

UserInfoMapper.xml

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

<!-- namespace=綁定一個對應的 Dao/Mapper 接口 -->
<mapper namespace="com.lpxz.lpxzblog.dao.UserInfoDao">
    <select id="getUserInfoList" resultType="com.lpxz.lpxzblog.entity.UserInfo">
        select * from user_info
    </select>
</mapper>

測試類

UserInfoDaoTest.java

@Test
public void findUserInfoById() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserInfoDao dao = sqlSession.getMapper(UserInfoDao.class);

    UserInfo userInfo = dao.getUserInfoById(1); // 參數爲 id
    System.out.println(userInfo);

    // 提交併關閉 SqlSession
    sqlSession.commit();
    sqlSession.close();
}

@Test
public void getUserInfoList() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserInfoDao dao = sqlSession.getMapper(UserInfoDao.class);

    List<UserInfo> userInfoList = dao.getUserInfoList();
    System.out.println(userInfoList);

    // 提交併關閉 SqlSession
    sqlSession.commit();
    sqlSession.close();
}

CRUD (Create, Retrieve, Update, Delete)

namespace

指定了類路徑下的 Dao/Mapper文件

select, insert, update, delete

==id== 對應的 namespace 中的方法名

==resultType== sql語句執行的返回值類型

==parameterType== 參數類型

Map 傳遞參數

模糊查詢(like 「%%」)

Configuration

類型別名(typeAliases)

方法一 能夠自定義別名

<typeAliases>
    <typeAlias type="com.lpxz.lpxzblog.entity.UserInfo" alias="UserInfo"/>
</typeAliases>

方法二 經過註解實現別名:@Alias(「${definedName}」)

<typeAliases>
    <package name="com.lpxz.lpxzblog.entity"/>
</typeAliases>

resultMap

結果集映射

UserInfo.java

@Data
@TableName("user_info") // @TableName中的值對應着表名
public class UserInfo {
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年齡
     */
    private Integer password; // 將 pwd 改成 passsword
}

UserInfoMapper.xml

<select id="getUserInfoList" resultMap="userMap">
    select * from user_info
</select>

<!-- 結果集映射 -->
<resultMap id="userMap" type="userInfo">
    <!-- column->數據庫中的字段 property->實體類中的屬性 --> 
    <result column="id" property="id"></result>
    <result column="name" property="name"></result>
    <result column="pwd" property="password"></result>
</resultMap>

複雜的使用尚未講

日誌工廠

若是一個數據庫操做出現了異常,咱們須要排錯,日誌是最好的助手。

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

Log4j

經過修改配置文件,控制每一條日誌的輸出格式,定義每一條日誌信息的級別,不須要修改應用的代碼。

日誌能夠輸出到控制檯、文件、GUI 組件

mybatis-config.xml

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

pom.xml

<!-- 日誌 Log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>${log4j-version}</version>
</dependency>

log4j.properties

# 日誌等級
log4j.rootLogger=DEBUG,console,file

# 控制檯輸出
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
#log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.out
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/lpxzLog.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

使用

logger.info("info");
logger.debug("debug");
logger.error("error!");

分頁

爲何要分頁?

  • 減小數據的處理量

Limit 分頁

SELECT * from user limit {startIndex}, {pageSize}; -- 開始位置和頁面容量
SELECT * from user limit {n}; -- [0, n]
<mapper>
    <!-- 分頁 -->
    <select id="getUserByLimit" parameterType="map" resultType="UserInfo">
        select * from user_info limit #{startIndex}, #{pageSize}
    </select>
</mapper>

UserInfoMapperTest.java

/**
 * 分頁
 */
@Test
public void getUserInfoByLimit() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);

    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("startIndex", 0);
    map.put("pageSize", 2);

    List<UserInfo> userInfoList = mapper.getUserByLimit(map);
    for (UserInfo userInfo : userInfoList) {
        System.out.println(userInfo);
    }

    sqlSession.close();
}

RouBounds 分頁

面向對象方法實現分頁,僅做了解

使用註解開發

==AOP 面向接口編程思想==

UserInfoMapper.java

// 查詢所有用戶信息 註解方式
@Select("select * from user_info")
List<UserInfo> getUserInfoListAOP();

mybatis-config.xml

<mappers>
    <!-- 綁定接口 -->
    <mapper class="com.lpxz.lpxzblog.dao.UserInfoMapper"/>
</mappers>
關於 @Param

@Param 註解用於給方法參數起一個名字。如下是總結的使用原則:

  • 在方法只接受一個參數的狀況下,能夠不使用 @Param。
  • 在方法接受多個參數的狀況下,建議必定要使用 @Param註解給參數命名。
  • 若是參數是 JavaBean , 則不能使用 @Param。
  • 不使用 @Param 註解時,參數只能有一個,而且是 Java Bean。
# 與 $ 的區別
  • #{} 的做用主要是替換預編譯語句(PrepareStatement)中的佔位符? 【推薦使用】

    INSERT INTO user (name) VALUES (#{name});
    INSERT INTO user (name) VALUES (?);
  • ${} 的做用是直接進行字符串替換

    INSERT INTO user (name) VALUES ('${name}');
    INSERT INTO user (name) VALUES ('kuangshen');

Lombok

==@Data== ==@AllArgsConstructor== ==@NoArgsConstructor==

...

多對一的處理

多對一的理解:

  • 多個學生對應一個老師

搭建測試環境

==建立實體類==

@Data 
//GET,SET,ToString,有參,無參構造
public class Teacher {
    private int id;
    private String name;
}

@Data
public class Student {
    private int id;
    private String name;
    //多個學生能夠是同一個老師,即多對一
    private Teacher teacher;
}

==編寫實體類對應的Mapper接口==

public interface StudentMapper {
}
public interface TeacherMapper {
}

==編寫Mapper接口對應的 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.kuang.mapper.StudentMapper"/>

<?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.kuang.mapper.TeacherMapper"/>

按查詢嵌套處理

StudentMapper.java

// 獲取全部學生及對應老師的信息
List<Student> getStudents();

StudentMapper.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.kuang.mapper.StudentMapper">
    <select id="getStudents" resultMap="StudentTeacher">
        select * from student
    </select>

    <resultMap id="StudentTeacher" type="Student">
        <!-- 對象:association     集合:collection -->
        <!--association關聯屬性 property屬性名 javaType屬性類型 column在多的一方的表中的列名-->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacherById"/>
    </resultMap>

    <select id="getTeacherById" resultType="Teacher">
        select * from teacher where id = #{id}
    </select>
</mapper>

StudentMapperTest.java

@Test
public void testGetStudents() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

    List<Student> students = mapper.getStudents();

    for (Student student : students) {
        System.out.println(student);
    }

    sqlSession.commit();
    sqlSession.close();
}

按結果嵌套處理

StudentMapper.java

List<Student> getStudents2();

StudentMapper.xml

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

<resultMap id="StudentTeacher2" type="Student">
    <id property="id" column="sid"/>
    <result property="name" column="sname"/>
    <!--關聯對象property 關聯對象在Student實體類中的屬性-->
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>

StudentMapperTest.java

@Test
public void testGetStudents2() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

    List<Student> students = mapper.getStudents2();

    for (Student student : students) {
        System.out.println(
            "學生名:" + student.getName()
            + "\t老師:" + student.getTeacher().getName());
    }
}

按照查詢進行嵌套處理就像SQL中的子查詢

按照結果進行嵌套處理就像SQL中的聯表查詢

一對多的處理

==修改實體類==

@Data
public class Teacher {
    private int id;
    private String name;
    private List<Student> studentList;
}

@Data
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

按照結果嵌套處理

TeacherMapper.java

Teacher getTeacherById(@Param("tId") int id);

TeacherMapper.xml

<select id="getTeacherById" resultMap="TeacherStudent">
    select s.id sId, s.name sName, t.name tName, t.id tId
    from student s, teacher t
    where s.tId = t.id and t.id = #{tId}
</select>
<resultMap id="TeacherStudent" type="Teacher">
    <result property="id" column="tId"/>
    <result property="name" column="tName"/>
    <!-- 集合:collection
        javaType="" 指定屬性的類型
        集合中的泛型信息,使用 ofType 獲取
        -->
    <collection property="studentList" ofType="Student">
        <result property="id" column="sId"/>
        <result property="name" column="sName"/>
        <result property="tId" column="tId"/>
    </collection>
</resultMap>

TeacherMapperTest.java

@Test
public void testGetTeacherById() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

    Teacher teacher = mapper.getTeacherById(1);

    System.out.println(teacher);

    sqlSession.commit();
    sqlSession.close();
}

按照查詢嵌套處理

TeacherMapper.java

Teacher getTeacherById2(@Param("tId") int id);

TeacherMapper.xml

<select id="getTeacherById2" resultMap="TeacherStudent2">
    select * from teacher where id = #{tId}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
    <collection property="studentList" ofType="Student" select="getStudentByTeacher" column="id"/>
</resultMap>
<select id="getStudentByTeacher" resultType="Student">
    select * from student where tId = #{tId}
</select>

TeacherMapperTest.java

@Test
public void testGetTeacherById2() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

    Teacher teacher = mapper.getTeacherById2(1);

    System.out.println(teacher);

    sqlSession.commit();
    sqlSession.close();
}

關聯 - association 多對一

集合 - collection 一對多

javaType 用來指定實體類中屬性的類型

ofType 用來指定映射到 List 或者集合中的 POJO 類型,泛型中的約束類型

動態 SQL

動態 SQL:指根據不一樣的條件生成不一樣的 SQL 語句

==if==

*Mapper.xml

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

==choose==

*Mapper.xml

<select id="queryBlogChoose" resultType="Blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                and title = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>
第一個以後的 when, otherwise 標籤中的語句前不加 and 會報錯

==set==

BlogMapper.xml

<update id="updateBlog" parameterType="Blog">
    update blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author}
        </if>
    </set>
    where id = #{id}
</update>
if 標籤中語句末不加逗號會報錯

==SQL 片斷==

有時候可能某個 sql 語句咱們用的特別多,爲了增長代碼的重用性,簡化代碼,咱們須要將這些代碼抽取出來,而後使用時直接調用。

提取 SQL 片斷:

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

引用 SQL 片斷:

<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <include refid="if-title-author"/>
    </where>
</select>

==Foreach==

將數據庫中前三個數據的 id 修改成 1,2,3;

需求:查詢 Blog 表中 id 分別爲 1,2,3 的博客信息

BlogMapper.java

List<Blog> queryBlogForeach(Map map);

*Mapper.xml

<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <!--
       collection:指定輸入對象中的集合屬性
       item:每次遍歷生成的對象
       open:開始遍歷時的拼接字符串
       close:結束時拼接的字符串
       separator:遍歷對象之間須要拼接的字符串
       select * from blog where 1=1 and (id=1 or id=2 or id=3)
     -->
        <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
            id = #{id}
        </foreach>
    </where>
</select>

BlogMapperTest.java

@Test
public void testQueryBlogForeach() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    List<String> ids = new ArrayList<>();
    ids.add("1");
    ids.add("2");
    ids.add("3");
    map.put("ids", ids);

    List<Blog> blogs = mapper.queryBlogForeach(map);

    System.out.println(blogs);

    sqlSession.commit();
    sqlSession.close();
}

緩存

Mybatis 緩存

  • MyBatis包含一個很是強大的查詢緩存特性,它能夠很是方便地定製和配置緩存。緩存能夠極大的提高查詢效率。
  • MyBatis系統中默認定義了兩級緩存:一級緩存二級緩存
    • 默認狀況下,只有一級緩存開啓(SqlSession 級別的緩存,也稱爲本地緩存)
    • 二級緩存須要手動開啓和配置,他是基於 namespace 級別的緩存
    • 爲了提升擴展性,MyBatis 定義了緩存接口 Cache。咱們能夠經過實現 Cache 接口來自定義二級緩存

一級緩存

一級緩存也叫本地緩存:

  • 與數據庫同一次會話期間查詢到的數據會放在本地緩存中。
  • 之後若是須要獲取相同的數據,直接從緩存中拿,沒必須再去查詢數據庫;
測試

一、在 Mybatis 中加入日誌,方便測試結果

二、編寫接口方法

//根據id查詢用戶
User queryUserById(@Param("id") int id);

三、接口對應的Mapper文件

<select id="queryUserById" resultType="user">
    select * from user where id = #{id}
</select>

四、測試

@Test
public void testQueryUserById() {
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);
    User user2 = mapper.queryUserById(1);
    System.out.println(user2);
    System.out.println(user == user2);

    session.close();
}

五、結果分析

img


一級緩存失效的四種狀況

一級緩存是 SqlSession 級別的緩存,是一直開啓的,咱們關閉不了它;

一級緩存失效狀況:沒有使用到當前的一級緩存,效果就是,還須要再向數據庫中發起一次查詢請求!

一、sqlSession不一樣

@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    SqlSession session2 = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    UserMapper mapper2 = session2.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);
    User user2 = mapper2.queryUserById(1);
    System.out.println(user2);
    System.out.println(user==user2);

    session.close();
    session2.close();
}

觀察結果:發現發送了兩條 SQL 語句!

結論:每一個 sqlSession 中的緩存相互獨立

二、sqlSession 相同,查詢條件不一樣

@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    UserMapper mapper2 = session.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);
    User user2 = mapper2.queryUserById(2);
    System.out.println(user2);
    System.out.println(user==user2);

    session.close();
}

觀察結果:發現發送了兩條 SQL 語句!很正常的理解

結論:當前緩存中,不存在這個數據

三、sqlSession 相同,兩次查詢之間執行了增刪改操做!

增長方法

//修改用戶
int updateUser(Map map);

編寫SQL

<update id="updateUser" parameterType="map">
    update user set name = #{name} where id = #{id}
</update>

測試

@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);

    HashMap map = new HashMap();
    map.put("name","kuangshen");
    map.put("id",4);
    mapper.updateUser(map);

    User user2 = mapper.queryUserById(1);
    System.out.println(user2);

    System.out.println(user==user2);

    session.close();
}

觀察結果:查詢在中間執行了增刪改操做後,從新執行了

結論:由於增刪改操做可能會對當前數據產生影響

四、sqlSession 相同,手動清除一級緩存

@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);

    session.clearCache();//手動清除緩存

    User user2 = mapper.queryUserById(1);
    System.out.println(user2);

    System.out.println(user==user2);

    session.close();
}
一級緩存就是一個 map

二級緩存

  • 二級緩存也叫全局緩存,一級緩存做用域過低了,因此誕生了二級緩存
  • 基於 namespace 級別的緩存,一個名稱空間,對應一個二級緩存;
  • 工做機制
    • 一個會話查詢一條數據,這個數據就會被放在當前會話的一級緩存中;
    • 若是當前會話關閉了,這個會話對應的一級緩存就沒了;可是咱們想要的是,會話關閉了,一級緩存中的數據被保存到二級緩存中;
    • 新的會話查詢信息,就能夠從二級緩存中獲取內容;
    • 不一樣的mapper查出的數據會放在本身對應的緩存(map)中;
使用步驟

一、開啓全局緩存 mybatis-config.xml

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

二、去每一個mapper.xml中配置使用二級緩存,這個配置很是簡單;*Mapper.xml

<cache eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>
<!-- 這個更高級的配置建立了一個 FIFO 緩存,每隔 60 秒刷新,最多能夠存儲結果對象或列表的 512 個引用,並且返回的對象被認爲是隻讀的,所以對它們進行修改可能會在不一樣線程中的調用者產生衝突。 -->

三、代碼測試

  • 全部的實體類先實現序列化接口
  • 測試代碼
@Test
public void testQueryUserById(){
    SqlSession session = MybatisUtils.getSession();
    SqlSession session2 = MybatisUtils.getSession();

    UserMapper mapper = session.getMapper(UserMapper.class);
    UserMapper mapper2 = session2.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);
    session.close();

    User user2 = mapper2.queryUserById(1);
    System.out.println(user2);
    System.out.println(user==user2);

    session2.close();
}
結論
  • 只要開啓了二級緩存,咱們在同一個Mapper中的查詢,能夠在二級緩存中拿到數據
  • 查出的數據都會被默認先放在一級緩存中
  • 只有會話提交或者關閉之後,一級緩存中的數據纔會轉到二級緩存中
緩存原理圖

img

EhCache

第三方緩存實現--EhCache: 查看百度百科

Ehcache是一種普遍使用的java分佈式緩存,用於通用緩存;

要在應用程序中使用Ehcache,須要引入依賴的jar包

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

在mapper.xml中使用對應的緩存便可

<mapper namespace = 「org.acme.FooMapper」 > 
    <cache type = 「org.mybatis.caches.ehcache.EhcacheCache」 /> 
</mapper>

編寫 ehcache.xml 文件,若是在加載時未找到 /ehcache.xml 資源或出現問題,則將使用默認配置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
      diskStore:爲緩存路徑,ehcache分爲內存和磁盤兩級,此屬性定義磁盤的緩存位置。參數解釋以下:
      user.home – 用戶主目錄
      user.dir – 用戶當前工做目錄
      java.io.tmpdir – 默認臨時文件路徑
    -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>

    <defaultCache
                  eternal="false"
                  maxElementsInMemory="10000"
                  overflowToDisk="false"
                  diskPersistent="false"
                  timeToIdleSeconds="1800"
                  timeToLiveSeconds="259200"
                  memoryStoreEvictionPolicy="LRU"/>

    <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
    <!--
      defaultCache:默認緩存策略,當ehcache找不到定義的緩存時,則使用這個緩存策略。只能定義一個。
    -->
    <!--
     name:緩存名稱。
     maxElementsInMemory:緩存最大數目
     maxElementsOnDisk:硬盤最大緩存個數。
     eternal:對象是否永久有效,一但設置了,timeout將不起做用。
     overflowToDisk:是否保存到磁盤,當系統當機時
     timeToIdleSeconds:設置對象在失效前的容許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。
     timeToLiveSeconds:設置對象在失效前容許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。
     diskPersistent:是否緩存虛擬機重啓期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
     diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每一個Cache都應該有本身的一個緩衝區。
     diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
     memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你能夠設置爲FIFO(先進先出)或是LFU(較少使用)。
     clearOnFlush:內存數量最大時是否清除。
     memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,默認策略)、FIFO(先進先出)、LFU(最少訪問次數)。
     FIFO,first in first out,這個是你們最熟的,先進先出。
     LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的將會被清出緩存。
     LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又須要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。
  -->
</ehcache>
相關文章
相關標籤/搜索