這或許是最詳細的Mybatis總結

完整代碼傳送門:gitee

1、Mybatis簡介

1.1、什麼是MyBatis

  • MyBatis 是一款優秀的持久層框架
  • MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集的過程
  • MyBatis 可以使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 實體類 【Plain Old Java Objects,普通的 Java對象】映射成數據庫中的記錄。
  • MyBatis 本是apache的一個開源項目ibatis, 2010年這個項目由apache 遷移到了google code,並且改名爲MyBatis
  • 2013年11月遷移到Github
  • Mybatis官方文檔 : https://mybatis.org/mybatis-3/zh/index.html
  • GitHub : https://github.com/mybatis/mybatis-3

1.2、持久化

持久化是將程序數據在持久狀態和瞬時狀態間轉換的機制

  • 即把數據(如內存中的對象)保存到可永久保存的存儲設備中(如磁盤)。持久化的主要應用是將內存中的對象存儲在數據庫中,或者存儲在磁盤文件中、XML數據文件中等等。
  • JDBC就是一種持久化機制。文件IO也是一種持久化機制。
  • 在生活中 : 將鮮肉冷藏,吃的時候再解凍的方法也是。將水果做成罐頭的方法也是。

爲什麼需要持久化服務呢?那是由於內存本身的缺陷引起的

  • 內存斷電後數據會丟失,但有一些對象是無論如何都不能丟失的,比如銀行賬號等,遺憾的是,人們還無法保證內存永不掉電。
  • 內存過於昂貴,與硬盤、光盤等外存相比,內存的價格要高2~3個數量級,而且維持成本也高,至少需要一直供電吧。所以即使對象不需要永久保存,也會因爲內存的容量限制不能一直呆在內存中,需要持久化來緩存到外存。

1.3、持久層

什麼是持久層?

  • 完成持久化工作的代碼塊 ----> dao層 【DAO (Data Access Object) 數據訪問對象】
  • 大多數情況下特別是企業級應用,數據持久化往往也就意味着將內存中的數據保存到磁盤上加以固化,而持久化的實現過程則大多通過各種關係數據庫來完成。
  • 不過這裏有一個字需要特別強調,也就是所謂的“層”。對於應用系統而言,數據持久功能大多是必不可少的組成部分。也就是說,我們的系統中,已經天然的具備了“持久層”概念?也許是,但也許實際情況並非如此。之所以要獨立出一個“持久層”的概念,而不是“持久模塊”,“持久單元”,也就意味着,我們的系統架構中,應該有一個相對獨立的邏輯層面,專注於數據持久化邏輯的實現.
  • 與系統其他部分相對而言,這個層面應該具有一個較爲清晰和嚴格的邏輯邊界。【說白了就是用來操作數據庫存在的!】

1.4、爲什麼需要Mybatis

  • Mybatis就是幫助程序猿將數據存入數據庫中 , 和從數據庫中取數據 .

  • 傳統的jdbc操作 , 有很多重複代碼塊 .比如 : 數據取出時的封裝 , 數據庫的建立連接等等… , 通過框架可以減少重複代碼,提高開發效率 .

  • MyBatis 是一個半自動化的ORM框架 (Object Relationship Mapping) -->對象關係映射

  • 所有的事情,不用Mybatis依舊可以做到,只是用了它,所有實現會更加簡單!技術沒有高低之分,只有使用這個技術的人有高低之別

  • MyBatis的優點

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

2、第一個Mybatis程序

思路流程:搭建環境–>導入Mybatis—>編寫代碼—>測試

代碼演示

2.1、搭建實驗數據庫

CREATE DATABASE `mybatis`;

USE `mybatis`;

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `user`(`id`,`name`,`pwd`) values (1,'godfrey','123456'),(2,'張三','abcdef'),(3,'李四','987654');

2.2、導入MyBatis相關 jar 包

<dependency>
	<groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.4</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>

2.3、編寫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核心配置文件-->
<configuration>
	<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://39.106.41.184:3306/mybatis?serverTimezone=GMT%2B8"/>
                <property name="username" value="mybatis"/>
                <property name="password" value="mybatis123"/>
            </dataSource>
        </environment>
    </environments>

    <!--每一個Mapper.xml都需要再Mybatis核心配置文件中註冊-->
    <mappers>
        <mapper resource="com/godfrey/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

2.4、編寫MyBatis工具類

package com.godfrey.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;

/**
 * description : sqlSessionFactory->sqlSession
 *
 * @author godfrey
 * @since 2020-05-18
 */
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //使用Mybatis第一步: 獲取sqlSessionFactory對象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

2.5、創建實體類

package com.godfrey.pojo;

import lombok.Data;

/**
 * description : 實體類
 *
 * @author godfrey
 * @since 2020-05-18
 */
@Data
public class User {
    private Integer id;
    private String name;
    private String pwd;
}

2.6、編寫Mapper接口類

package com.godfrey.mapper;

import com.godfrey.pojo.User;

import java.util.List;

/**
 * description : 持久層接口
 *
 * @author godfrey
 * @since 2020-05-18
 */
public interface UserMapper {
    List<User> getUserList();
}

2.7、編寫Mapper.xml配置文件

  • namespace 十分重要,不能寫錯!
<?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綁定一個對應的Mapper-->
<mapper namespace="com.godfrey.mapper.UserMapper">
    <select id="getUserList" resultType="com.godfrey.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

2.8、編寫測試類

package com.godfrey.mapper;

import com.godfrey.pojo.User;
import com.godfrey.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * description : 測試
 *
 * @author godfrey
 * @since 2020-05-18
 */
public class UserMapperTest {

    @Test
    public void test(){
        //獲取sqlSession對象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //方式一:getMapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }

        //方式二:舊版本,不推薦使用
        //List<User> userList = sqlSession.selectList("com.godfrey.mapper.UserMapper.getUserList");
        //
        //for (User user : userList) {
        //    System.out.println(user);
        //}

        //關閉sqlSession
        sqlSession.close();
    }
}

2.9、運行測試,成功的查詢出來的我們的數據,ok!

問題說明

可能出現問題說明:Maven靜態資源過濾問題

<resources>
	<resource>
    	<directory>src/main/java</directory>
        <includes>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
    </resource>
    <resource>
        <directory>src/main/resources</directory>
        <includes>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
    </resource>
</resources>

3、CRUD操作

3.1、namespace

配置文件中namespace中的名稱爲對應Mapper接口的完整包名,必須一致!

3.2、select

  • select標籤是mybatis中最常用的標籤之一

  • select語句有很多屬性可以詳細配置每一條SQL語句

    • SQL語句返回值類型。【完整的類名或者別名】
    • 傳入SQL語句的參數類型 。【萬能的Map,可以多嘗試使用】
    • 命名空間中唯一的標識符
    • 接口中的方法名與映射文件中的SQL語句ID 一一對應
    • id
    • parameterType
    • resultType

需求:根據id查詢用戶

1、在UserMapper中添加對應方法

public interface UserMapper {
   //查詢全部用戶
   List<User> selectUser();
   //根據id查詢用戶
   User selectUserById(int id);
}

2、在UserMapper.xml中添加Select語句

<select id="selectUserById" resultType="com.godfrey.pojo.User">
	select * from user where id = #{id};
</select>

3、測試類中測試

@Test
public void testGetUserById() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserById(1);
    System.out.println(user);
    sqlSession.close();
}

小思考:根據 密碼 和 名字 查詢用戶

思路一:直接在方法中傳遞參數

1、在接口方法的參數前加 @Param屬性

2、Sql語句編寫的時候,直接取@Param中設置的值即可,不需要單獨設置參數類型

//通過密碼和名字查詢用戶
User selectUserByNP(@Param("username") String username,@Param("pwd") String pwd);

/*
   <select id="selectUserByNP" resultType="com.godfrey.pojo.User">
       select * from user where name = #{username} and pwd = #{pwd};
   </select>
*/

思路二:使用萬能的Map(企業中常用)

1、在接口方法中,參數直接傳遞Map;

User selectUserByNP2(Map<String,Object> map);

2、編寫sql語句的時候,需要傳遞參數類型,參數類型爲map

<select id="selectUserByNP2" parameterType="map" resultType="com.godfrey.pojo.User">
	select * from user where name = #{username} and pwd = #{pwd};
</select>

3、在使用方法的時候,Map的 key 爲 sql中取的值即可,沒有順序要求!

Map<String, Object> map = new HashMap<String, Object>();
map.put("username","小明");
map.put("pwd","123456");
User user = mapper.selectUserByNP2(map);

總結:如果參數過多,我們可以考慮直接使用Map實現,如果參數比較少,直接傳遞參數即可

3.3、insert

我們一般使用insert標籤進行插入操作,它的配置和select標籤差不多!

需求:給數據庫增加一個用戶

1、在UserMapper接口中添加對應的方法

//添加一個用戶
int addUser(User user);

2、在UserMapper.xml中添加insert語句

<insert id="addUser" parameterType="com.godfrey.pojo.User">
    insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd});
</insert>

3、測試

@Test
public void testAddUser() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int res = mapper.addUser(new User(4, "哈哈", "1234"));
    if (res > 0) {
        System.out.println("插入成功!");
    }
    //提交事務,重點!不寫的話不會提交到數據庫
    sqlSession.commit();
    sqlSession.close();
}

注意點:增、刪、改操作需要提交事務!

3.4、update

我們一般使用update標籤進行更新操作,它的配置和select標籤差不多!

需求:修改用戶的信息

1、同理,編寫接口方法

//修改一個用戶
int updateUser(User user);

2、編寫對應的配置文件SQL

<update id="updateUser" parameterType="com.godfrey.pojo.User">
	update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id};
</update>

3、測試

@Test
public void testUpdateUser() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int res = mapper.updateUser(new User(4, "duiyi", "123"));
    if (res > 0) {
    System.out.println("更新成功!");
    }
    sqlSession.commit();
    sqlSession.close();
}

3.5、delete

我們一般使用delete標籤進行刪除操作,它的配置和select標籤差不多!

需求:根據id刪除一個用戶

1、同理,編寫接口方法

//根據id刪除用戶
int deleteUser(int id);

2、編寫對應的配置文件SQL

<delete id="deleteUser" parameterType="int">
    delete from mybatis.user where id = #{id};
</delete>

3、測試

@Test
public void testDeleteUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int res = mapper.deleteUser(4);
    if (res > 0) {
        System.out.println("刪除成功!");
    }
    sqlSession.commit();
    sqlSession.close();
}

小結:

  • 所有的增刪改操作都需要提交事務!
  • 接口所有的普通參數,儘量都寫上@Param參數,尤其是多個參數時,必須寫上!
  • 有時候根據業務的需求,可以考慮使用map傳遞參數
  • 爲了規範操作,在SQL的配置文件中,我們儘量將Parameter參數和resultType都寫上!

思考

模糊查詢like語句該怎麼寫?

第1種:在Java代碼中添加sql通配符

string wildcardname =%smi%;
list<name> names = mapper.selectlike(wildcardname);

<select id=”selectlike”>
	select * from foo where bar like #{value};
</select>

第2種:在sql語句中拼接通配符,會引起sql注入

string wildcardname = “smi”;
list<name> names = mapper.selectlike(wildcardname);

<select id=”selectlike”>
    select * from foo where bar like "%"#{value}"%"
</select>

4、配置解析

4.1、核心配置文件

  • mybatis-config.xml 系統核心配置文件
  • MyBatis 的配置文件包含了會深深影響 MyBatis 行爲的設置和屬性信息
  • 能配置的內容如下:
configuration(配置)
properties(屬性)
settings(設置)
typeAliases(類型別名)
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環境配置)
environment(環境變量)
transactionManager(事務管理器)
dataSource(數據源)
databaseIdProvider(數據庫廠商標識)
mappers(映射器)
<!-- 注意元素節點的順序!順序不對會報錯 -->

我們可以閱讀 mybatis-config.xml 上面的dtd的頭文件!

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的多套運行環境,將SQL映射到多個不同的數據庫上,儘管可以配置多個環境,但每個SqlSessionFactory實例只能選擇一種環境,必須指定其中一個爲默認運行環境(通過default指定)

  • 子元素節點:environment

    • dataSource 元素使用標準的 JDBC 數據源接口來配置 JDBC 連接對象的資源。

    • 數據源是必須配置的。

    • 有三種內建的數據源類型

      type="[UNPOOLED|POOLED|JNDI]")
      
    • unpooled:這個數據源的實現只是每次被請求時打開和關閉連接。

    • pooled:這種數據源的實現利用“池”的概念將 JDBC 連接對象組織起來 , 這是一種使得併發 Web 應用快速響應請求的流行處理方式。

    • jndi:這個數據源的實現是爲了能在如 Spring 或應用服務器這類容器中使用,容器可以集中或在外部配置數據源,然後放置一個 JNDI 上下文的引用。

    • 數據源也有很多第三方的實現,比如dbcp,c3p0,druid等等…

    • 這兩種事務管理器類型都不需要設置任何屬性。

    • 具體的一套環境,通過設置id進行區別,id保證唯一!

    • 子元素節點:transactionManager - [ 事務管理器 ]

      <!--語法-->
      <transactionManager type="[ JDBC | MANAGED ]"/>
      
    • 子元素節點:數據源(dataSource)

4.3、Properties優化

數據庫這些屬性都是可外部配置且可動態替換的,既可以在典型的 Java 屬性文件中配置,亦可通過 properties 元素的子元素來傳遞。具體的官方文檔

我們來優化我們的配置文件

第一步 ; 在資源目錄下新建一個db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://39.106.41.184:3306/mybatis?serverTimezone=GMT%2B8
username=mybatis
password=mybatis123

第二步 : 將文件導入properties 配置文件

<configuration>
	<!--導入properties文件-->
    <properties resource="db.properties"/>

    <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/UserMapper.xml"/>
    </mappers>
</configuration>

4.4、typeAliases優化

類型別名是爲 Java 類型設置一個短的名字。它只和 XML 配置有關,存在的意義僅在於用來減少類完全限定名的冗餘。

<!--配置別名,注意順序-->
<typeAliases>
	<typeAlias type="com.godfrey.pojo.User" alias="User"/>
</typeAliases>

當這樣配置時,User可以用在任何使用com.godfrey.pojo.User的地方。

也可以指定一個包名,MyBatis 會在包名下面搜索需要的 Java Bean,比如:

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

每一個在包 com.godfrey.pojo 中的 Java Bean,在沒有註解的情況下,會使用 Bean 的首字母小寫的非限定類名來作爲它的別名。

若有註解,則別名爲其註解值。見下面的例子:

@Alias("user")
public class User {
  ...
}

4.5、其他配置瀏覽

4.5.1、設置

  • 設置(settings)相關 => 查看幫助文檔

    • 懶加載
    • 日誌實現
    • 緩存開啓關閉
  • 一個配置完整的 settings 元素的示例如下:

    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="25"/>
        <setting name="defaultFetchSize" value="100"/>
        <setting name="safeRowBoundsEnabled" value="false"/>
        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <setting name="localCacheScope" value="SESSION"/>
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>
    

4.5.2、類型處理器

  • 無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,還是從結果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。
  • 可以重寫類型處理器或創建自己的類型處理器來處理不支持的或非標準的類型。【瞭解即可】

4.5.3、對象工程

  • MyBatis 每次創建結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)實例來完成。

  • 默認的對象工廠需要做的僅僅是實例化目標類,要麼通過默認構造方法,要麼在參數映射存在的時候通過有參構造方法來實例化。

  • 如果想覆蓋對象工廠的默認行爲,則可以通過創建自己的對象工廠來實現。【瞭解即可】

4.6、映射器:mappers元素

mappers

  • 映射器 : 定義映射SQL語句文件
  • 既然 MyBatis 的行爲其他元素已經配置完了,我們現在就要定義 SQL 映射語句了。但是首先我們需要告訴 MyBatis 到哪裏去找到這些語句。Java 在自動查找這方面沒有提供一個很好的方法,所以最佳的方式是告訴 MyBatis 到哪裏去找映射文件。你可以使用相對於類路徑的資源引用, 或完全限定資源定位符(包括 file:/// 的 URL),或類名和包名等。映射器是MyBatis中最核心的組件之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL語句都必須在xml文件中配置。而從MyBatis 3開始,還支持接口映射器,這種映射器方式允許以Java代碼的方式註解定義SQL語句,非常簡潔

引入資源方式

<!-- 使用相對於類路徑的資源引用(推薦使用) -->
<mappers>
	<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定資源定位符(URL) -->
<mappers>
	<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!--
使用映射器接口實現類的完全限定類名
需要配置文件名稱和接口名稱一致,並且位於同一目錄下
-->
<mappers>
	<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!--
將包內的映射器接口實現全部註冊爲映射器
但是需要配置文件名稱和接口名稱一致,並且位於同一目錄下
-->
<mappers>
	<package name="org.mybatis.builder"/>
</mappers>

Mapper文件

<?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.UserMapper">
   
</mapper>
  • namespace中文意思:命名空間,作用如下:

    • namespace的命名必須跟某個接口同名
    • 接口中的方法與映射文件中sql語句id應該一一對應
    1. namespace和子元素的id聯合保證唯一 , 區別不同的mapper
    2. 綁定DAO接口
    3. namespace命名規則 : 包名+類名

MyBatis 的真正強大在於它的映射語句,這是它的魔力所在。由於它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼。MyBatis 爲聚焦於 SQL 而構建,以儘可能地爲你減少麻煩。

4.7、生命週期和作用域

作用域(Scope)和生命週期

理解我們目前已經討論過的不同作用域和生命週期類是至關重要的,因爲錯誤的使用會導致非常嚴重的併發問題

先畫一個流程圖,分析一下Mybatis的執行過程!

作用域理解

SqlSessionFactoryBuilder:

  • 一旦創建了 SqlSessionFactory,就不再需要它了
  • SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域(也就是局部方法變量)

SqlSessionFactory:

  • 可以被認爲是一個數據庫連接池,它的作用是創建 SqlSession 接口對象
  • SqlSessionFactory 一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建另一個實例
  • 一般的應用中我們往往希望 SqlSessionFactory 作爲一個單例,讓它在應用中被共享。所以說 **SqlSessionFactory 的最佳作用域是應用作用域。**最簡單的就是使用單例模式或者靜態單例模式

SqlSession:

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

5、ResultMap

查詢爲null問題

要解決的問題:屬性名和字段名不一致

環境:新建一個項目,將之前的項目拷貝過來

1、查看之前的數據庫的字段名

.

2、Java中的實體類設計

package com.godfrey.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.ibatis.type.Alias;

/**
 * description : 實體類
 *
 * @author godfrey
 * @since 2020-05-18
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Alias("user")//取別名
public class User {
    private Integer id;
    private String name;
    private String password;   //密碼和數據庫不一樣!
}

3、接口

//根據id查詢用戶
User selectUserById(int id);

4、mapper映射文件

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

5、測試

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

結果:

  • User{id=1, name=‘godfrey’, password=‘null’}
  • 查詢出來發現 password 爲空 . 說明出現了問題!

分析:

  • select * from user where id = #{id}; 可以看做

    select id,name,pwd from user where id = #{id};

  • mybatis會根據這些查詢的列名(會將列名轉化爲小寫,數據庫不區分大小寫) , 去對應的實體類中查找相應列名的set方法設值 , 由於找不到setPwd() , 所以password返回null ; 【自動映射】

解決方案

方案一:爲列名指定別名 , 別名和java實體類的屬性名一致 .

<select id="selectUserById" resultType="User">
	select id, name, pwd as password from user where id = #{id};
</select>

方案二:使用結果集映射->ResultMap 【推薦】

<!--結果集映射-->
<resultMap id="UserMap" type="User">
    <!--id爲主鍵-->
    <id column="id" property="id"/>
    <!--column是數據庫表的列名,property是對應實體類的屬性名-->
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="UserMap">
    select * from user where id = #{id};
</select>

ResultMap

自動映射

  • resultMap 元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC ResultSets 數據提取代碼中解放出來。
  • 實際上,在爲一些比如連接的複雜語句編寫映射代碼的時候,一份 resultMap 能夠代替實現同等功能的長達數千行的代碼。
  • ResultMap 的設計思想是,對於簡單的語句根本不需要配置顯式的結果映射,而對於複雜一點的語句只需要描述它們的關係就行了。

你已經見過簡單映射語句的示例了,但並沒有顯式指定 resultMap。比如:

<select id="selectUserById" resultType="map">
	select id, name, pwd from user where id = #{id};
</select>

上述語句只是簡單地將所有的列映射到 HashMap 的鍵上,這由 resultType 屬性指定。雖然在大部分情況下都夠用,但是 HashMap 不是一個很好的模型。你的程序更可能會使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 對象)作爲模型。

ResultMap 最優秀的地方在於,雖然你已經對它相當瞭解了,但是根本就不需要顯式地用到他們。

手動映射

1、返回值類型爲resultMap

<select id="selectUserById" resultMap="UserMap">
	select id, name, pwd from user where id = #{id};
</select>

2、編寫resultMap,實現手動映射!

<resultMap id="UserMap" type="User">
	<!-- id爲主鍵 -->
	<id column="id" property="id"/>
	<!-- column是數據庫表的列名 , property是對應實體類的屬性名 -->
	<result column="name" property="name"/>
	<result column="pwd" property="password"/>
</resultMap>

如果世界總是這麼簡單就好了。但是肯定不是的,數據庫中,存在一對多,多對一的情況,我們之後會使用到一些高級的結果集映射,association,collection這些

6、日誌

6.1、日誌工廠

思考:我們在測試SQL的時候,要是能夠在控制檯輸出 SQL 的話,是不是就能夠有更快的排錯效率?

如果一個 數據庫相關的操作出現了問題,我們可以根據輸出的SQL語句快速排查問題。

對於以往的開發過程,我們會經常使用到debug模式來調節,跟蹤我們的代碼執行過程。但是現在使用Mybatis是基於接口,配置文件的源代碼執行過程。因此,我們必須選擇日誌工具來作爲我們開發,調節程序的工具。

Mybatis內置的日誌工廠提供日誌功能,具體的日誌實現有以下幾種工具:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

具體選擇哪個日誌實現工具由MyBatis的內置日誌工廠確定。它會使用最先找到的(按上文列舉的順序查找)。如果一個都未找到,日誌功能就會被禁用。

標準日誌實現

指定 MyBatis 應該使用哪個日誌記錄實現。如果此設置不存在,則會自動發現日誌記錄實現。

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

測試,可以看到控制檯有大量的輸出!我們可以通過這些輸出來判斷程序到底哪裏出了Bug

6.2、Log4j

簡介:

  • Log4j是Apache的一個開源項目
  • 通過使用Log4j,我們可以控制日誌信息輸送的目的地:控制檯,文本,GUI組件…
  • 我們也可以控制每一條日誌的輸出格式
  • 通過定義每一條日誌信息的級別,我們能夠更加細緻地控制日誌的生成過程。最令人感興趣的就是,這些可以通過一個配置文件來靈活地進行配置,而不需要修改應用的代碼

使用步驟:

1、導入log4j的包

<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.17</version>
</dependency>

2、配置文件編寫

#將等級爲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/godfrey.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

3、setting設置日誌實現

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

4、在程序中使用Log4j進行輸出!

//注意導包:org.apache.log4j.Logger
private static final Logger logger = Logger.getLogger(UserMapperTest.class);

@Test
public void testLog4j() {
    logger.info("info:進入testLog4j方法");
    logger.debug("debug:進入testLog4j方法");
    logger.error("error: 進入testLog4j方法");
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserById(1);
    System.out.println(user);
    sqlSession.close();
}

5、測試,看控制檯輸出!

  • 使用Log4j 輸出日誌
  • 可以看到還生成了一個日誌的文件 【需要修改file的日誌級別】

7、分頁的幾種方式

7.1、limit實現分頁

思考:爲什麼需要分頁?

在學習mybatis等持久層框架的時候,會經常對數據進行增刪改查操作,使用最多的是對數據庫進行查詢操作,如果查詢大量數據的時候,我們往往使用分頁進行查詢,也就是每次處理小部分數據,這樣對數據庫壓力就在可控範圍內。

使用Limit實現分頁

#語法
SELECT * FROM table LIMIT stratIndex,pageSize;

SELECT * FROM table LIMIT 5,10; // 檢索記錄行 6-15  

#爲了檢索從某一個偏移量到記錄集的結束所有的記錄行,可以指定第二個參數爲 -1:   
c // 檢索記錄行 96-last.  

#如果只給定一個參數,它表示返回最大的記錄行數目:   
SELECT * FROM table LIMIT 5; //檢索前 5 個記錄行  

#換句話說,LIMIT n 等價於 LIMIT 0,n。 

步驟:

1、修改Mapper文件

<select id="getUserByLimit" parameterType="map" resultType="user">
	select * from user limit #{startIndex}, #{pageSize};
</select>

2、Mapper接口,參數爲map

List<User> getUserByLimit(Map<String,Integer> map);

3、在測試類中傳入參數測試

  • 推斷:起始位置 = (當前頁面 - 1 ) * 頁面大小
//分頁查詢 , 兩個參數startIndex , pageSize
@Test
public void testGetUserByLimit() {
	SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);

    int currentPage = 1;  //第幾頁
    int pageSize = 2;  //每頁顯示幾個
    Map<String,Integer> map = new HashMap<String,Integer>();
    map.put("startIndex",(currentPage-1)*pageSize);
    map.put("pageSize",pageSize);

    List<User> users = mapper.selectUser(map);

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

    session.close();
}

7.2、RowBounds分頁

我們除了使用Limit在SQL層面實現分頁,也可以使用RowBounds在Java代碼層面實現分頁,當然此種方式作爲了解即可。我們來看下如何實現的!

步驟:

1、mapper接口

//選擇全部用戶RowBounds實現分頁
List<User> getUserByRowBounds();

2、mapper文件

<select id="getUserByRowBounds" resultType="user">
	select * from mybatis.user;
</select>

3、測試類

在這裏,我們需要使用RowBounds類

@Test
public  void testGetUserByRowBounds(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    
    int currentPage = 2;  //第幾頁
    int pageSize = 2;  //每頁顯示幾個
    RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);
    
    //通過session.**方法進行傳遞rowBounds,[此種方式現在已經不推薦使用了]
    List<User> userList = sqlSession.selectList("com.godfrey.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

7.3、PageHelper

瞭解即可,可以自己嘗試使用

官方文檔:https://pagehelper.github.io/

在MyBatisPlus中,也有很多分頁實現,看自己的理解和熟練程度進行掌握即可!

8、使用註解開發

  • mybatis最初配置信息是基於 XML ,映射語句(SQL)也是定義在 XML 中的。而到MyBatis 3提供了新的基於註解的配置。不幸的是,Java 註解的的表達力和靈活性十分有限。最強大的 MyBatis 映射並不能用註解來構建

  • sql 類型主要分成 :

    • @select ()
    • @update ()
    • @Insert ()
    • @delete ()

**注意:**利用註解開發就不需要mapper.xml映射文件了 .

1、我們在我們的接口中添加註解

//查詢全部用戶
@Select("select id,name,pwd password from user")
List<User> getAllUser();

2、在mybatis的核心配置文件中注入

<!--使用class綁定接口-->
<mappers>
	<mapper class="com.godfrey.mapper.UserMapper"/>
</mappers>

3、我們去進行測試

@Test
public void testGetAllUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.getAllUser();
    for (User user : users) {
        System.out.println(user);
    }
    sqlSession.close();
}

4、利用Debug查看本質

5、本質上利用了jvm的動態代理機制

6、Mybatis詳細的執行流程

.

8.1、註解CRUD

改造MybatisUtils工具類的getSession( ) 方法,重載實現。

//獲取SqlSession連接
public static SqlSession getSqlSession(){
	return sqlSessionFactory.openSession(true);//自動提交事務
}

public static SqlSession getSession(boolean flag){
	return sqlSessionFactory.openSession(flag);
}

【注意】確保實體類和數據庫字段對應

8.1.1、查詢

1、編寫接口方法註解

//根據id查詢用戶
@Select("select * from user where id = #{id}")
User selectUserById(@Param("id") int id);

2、測試

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

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

   session.close();
}

8.1.2、新增

1、編寫接口方法註解

//添加一個用戶
@Insert("insert into user (id,name,pwd) values (#{id},#{name},#{pwd})")
int addUser(User user);

2、測試

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

   User user = new User(6, "qaq", "123456");
   mapper.addUser(user);

   session.close();
}

8.1.3、修改

1、編寫接口方法註解

//修改一個用戶
@Update("update user set name=#{name},pwd=#{pwd} where id = #{id}")
int updateUser(User user);

2、測試

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

   User user = new User(6, "hah", "zxcvbn");
   mapper.updateUser(user);

   session.close();
}

8.1.4、刪除

1、編寫接口方法註解

//根據id刪除用
@Delete("delete from user where id = #{id}")
int deleteUser(@Param("id") int id);

2、測試

@Test
public void testDeleteUser() {
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   mapper.deleteUser(6);
   session.close();
}

【注意點:增刪改一定記得對事務的處理】

8.2、關於@Param

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

  • 基本類型的參數或者String類型,需要加上,引用類型不需要加

  • 在方法只接受一個參數的情況下,可以不使用@Param,但建議加上;在方法接受多個參數的情況下,建議一定要使用@Param註解給參數命名

  • 在SQL中引用的參數就是在@Param()中設定的屬性名!

8.3、#與$的區別

  • #{} 的作用主要是替換預編譯語句(PrepareStatement)中的佔位符? 【推薦使用】

    INSERT INTO user (name) VALUES (#{name});
    INSERT INTO user (name) VALUES (?);
    
  • ${} 的作用是直接進行字符串替換(SQL注入問題)

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

使用註解和配置文件協同開發,纔是MyBatis的最佳實踐!

使用註解開發可以提高我們的開發效率,可以合理使用

9、多對一的處理

多對一的理解:

  • 多個學生對應一個老師
  • 如果對於學生這邊,就是一個多對一的現象,即從學生這邊關聯一個老師!

數據庫設計

.

CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO teacher(`id`, `name`) VALUES (1, '狗老師');

CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;


INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小紅', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小張', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

搭建測試環境

1、IDEA安裝Lombok插件

2、引入Maven依賴

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

3、在代碼中增加註解

@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;
}

4、編寫實體類對應的Mapper接口 【兩個】

  • 無論有沒有需求,都應該寫上,以備後來之需!
public interface StudentMapper {
}
public interface TeacherMapper {
}

5、編寫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.godfrey.mapper.StudentMapper">

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

</mapper>

9.1、按查詢嵌套處理

1、給StudentMapper接口增加方法

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

Teacher getTeacher();

2、編寫對應的Mapper文件

<?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.godfrey.mapper.StudentMapper">

   <!--
   需求:獲取所有學生及對應老師的信息
   思路:
       1. 獲取所有學生的信息
       2. 根據獲取的學生信息的老師ID->獲取該老師的信息
       3. 思考問題,這樣學生的結果集中應該包含老師,該如何處理呢,數據庫中我們一般使用關聯查詢?
           1. 做一個結果集映射:StudentTeacher
           2. StudentTeacher結果集的類型爲 Student
           3. 學生中老師的屬性爲teacher,對應數據庫中爲tid。
              多個 [1,...)學生關聯一個老師=> 一對一,一對多
           4. 查看官網找到:association – 一個複雜類型的關聯;使用它來處理關聯查詢
   -->
   <select id="getStudents" resultMap="StudentTeacher">
       select * from student;
   </select>
   <resultMap id="StudentTeacher" type="Student">
       <!--association關聯屬性 property屬性名 javaType屬性類型 column在多的一方的表中的列名-->
       <association property="teacher"  column="tid" javaType="Teacher" select="getTeacher"/>
   </resultMap>
   <!--
   這裏傳遞過來的id,只有一個屬性的時候,下面可以寫任何值
   association中column多參數配置:
       column="{key=value,key=value}"
       其實就是鍵值對的形式,key是傳給下個sql的取值名稱,value是片段一中sql查詢的字段名。
   -->
   <select id="getTeacher" resultType="teacher">
       select * from teacher where id = #{id};
   </select>

</mapper>

3、編寫完畢去Mybatis配置文件中,註冊Mapper!

4、測試

@Test
public void testGetStudents() {
	SqlSession session = MybatisUtils.getSqlSession();
	StudentMapper mapper = session.getMapper(StudentMapper.class);
	List<Student> students = mapper.getStudents();

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

9.2、按結果嵌套處理

除了上面這種方式,還有其他思路嗎?

我們還可以按照結果進行嵌套處理;

1、接口方法編寫

List<Student> getStudents2();

2、編寫對應的mapper文件

<!--
按查詢結果嵌套處理
思路:直接查詢出結果,進行結果集的映射
-->
<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>

3、去mybatis-config文件中注入【此處應該處理過了】

4、測試

@Test
public void testGetStudents2() {
    SqlSession session = MybatisUtils.getSqlSession();
    StudentMapper mapper = session.getMapper(StudentMapper.class);
    List<Student> students = mapper.getStudents2();
    for (Student student : students) {
        System.out.println("學生名:" + student.getName() + "\t老師:" + student.getTeacher().getName());
    }
}

小結

按照查詢進行嵌套處理就像SQL中的子查詢,效率低,不推薦使用

按照結果進行嵌套處理就像SQL中的聯表查詢,效率高,推薦使用

10、一對多的處理

一對多的理解:

  • 一個老師擁有多個學生
  • 如果對於老師這邊,就是一個一對多的現象,即從一個老師下面擁有一羣學生(集合)!

實體類編寫

@Data
public class Student {
   private int id;
   private String name;
   private int tid;
}
@Data
public class Teacher {
   private int id;
   private String name;
   //一個老師多個學生
   private List<Student> students;
}

… 和之前一樣,搭建測試的環境!

10.1、按結果嵌套處理

1、TeacherMapper接口編寫方法

//獲取指定老師,及老師下的所有學生
public Teacher getTeacher((@Param("tid") int id);

2、編寫接口對應的Mapper配置文件

<mapper namespace="com.kuang.mapper.TeacherMapper">

   <!--
   思路:
       1. 從學生表和老師表中查出學生id,學生姓名,老師姓名
       2. 對查詢出來的操作做結果集映射
           1. 集合的話,使用collection!
               JavaType和ofType都是用來指定對象類型的
               JavaType是用來指定pojo中屬性的類型
               ofType指定的是映射到list集合屬性中pojo的類型。
   -->
    <!--按結果嵌套處理-->
    <select id="getTeacher" 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 property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>
</mapper>

3、將Mapper文件註冊到MyBatis-config文件中

<mappers>
	<package name="com.godfrey.mapper"/>
</mappers>

4、測試

@Test
public void testGetTeacher() {
    SqlSession session = MybatisUtils.getSqlSession();
    TeacherMapper mapper = session.getMapper(TeacherMapper.class);
    Teacher teacher = mapper.getTeacher(1);
    System.out.println(teacher.getName());
    System.out.println(teacher.getStudents());
}

10.2、按查詢嵌套處理

1、TeacherMapper接口編寫方法

Teacher getTeacher2(@Param("tid") int id);

2、編寫接口對應的Mapper配置文件

<!--按查詢嵌套處理-->
<select id="getTeacher2" resultMap="TeacherStudent2">
	select * from teacher where id = #{tid};
</select>

<resultMap id="TeacherStudent2" type="Teacher">
	<!--column是一對多的外鍵 , 寫的是一的主鍵的列名-->
	<collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>

<select id="getStudentByTeacherId" resultType="Student">
	select * from student where tid = #{tid};
</select>

3、將Mapper文件註冊到MyBatis-config文件中

4、測試

@Test
public void testGetTeacher2() {
   SqlSession session = MybatisUtils.getSession();
   TeacherMapper mapper = session.getMapper(TeacherMapper.class);
   Teacher teacher = mapper.getTeacher2(1);
   System.out.println(teacher.getName());
   System.out.println(teacher.getStudents());
}

小結

  1. 關聯-association
  2. 集合-collection
  3. 所以association是用於一對一和多對一,而collection是用於一對多的關係
  4. JavaType和ofType都是用來指定對象類型的
    • JavaType是用來指定pojo中屬性的類型
    • ofType指定的是映射到list集合屬性中pojo的類型。

注意說明:

  1. 保證SQL的可讀性,儘量通俗易懂

  2. 根據實際要求,儘量編寫性能更高的SQL語句

  3. 注意屬性名和字段不一致的問題

  4. 注意一對多和多對一 中:字段和屬性對應的問題

  5. 儘量使用Log4j,通過日誌來查看自己的錯誤

11、動態SQL

什麼是動態SQL:動態SQL指的是根據不同的查詢條件 , 生成不同的Sql語句

官網描述:
MyBatis 的強大特性之一便是它的動態 SQL。如果你有使用 JDBC 或其它類似框架的經驗,你就能體會到根據不同條件拼接 SQL 語句的痛苦。例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL 這一特性可以徹底擺脫這種痛苦。
雖然在以前使用動態 SQL 並非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射語句中的強大的動態 SQL 語言得以改進這種情形。
動態 SQL 元素和 JSTL 或基於類似 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多元素需要花時間瞭解。MyBatis 3 大大精簡了元素種類,現在只需學習原來一半的元素便可。MyBatis 採用功能強大的基於 OGNL 的表達式來淘汰其它大部分元素。

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

我們之前寫的 SQL 語句都比較簡單,如果有比較複雜的業務,我們需要寫複雜的 SQL 語句,往往需要拼接,而拼接 SQL ,稍微不注意,由於引號,空格等缺失可能都會導致錯誤。

那麼怎麼去解決這個問題呢?這就要使用 mybatis 動態SQL,通過 if, choose, when, otherwise, trim, where, set, foreach等標籤,可組合成非常靈活的SQL語句,從而在提高 SQL 語句的準確性的同時,也大大提高了開發人員的效率。

搭建環境

新建一個數據庫表:blog

字段:id,title,author,create_time,views

CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客標題',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '創建時間',
`views` int(30) NOT NULL COMMENT '瀏覽量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

1、創建Mybatis基礎工程

.

2、IDutil工具類

public class IDUtil {
    public static String getId() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

3、實體類編寫 【注意set方法作用】

import java.util.Date;

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
}

4、編寫Mapper接口及xml文件

public interface BlogMapper {
}
<?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.godfrey.mapper.BlogMapper">

</mapper>

5、mybatis核心配置文件,下劃線駝峯自動轉換

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

6、插入初始數據

編寫接口

//新增一個博客
int addBlog(Blog blog);

sql配置文件

<insert id="addBlog" parameterType="blog">
	insert into blog (id, title, author, create_time, views)
	values (#{id}, #{title}, #{author}, #{createTime}, #{views});
</insert>

初始化博客方法

public class BlogMapperTest {
    @Test
    public void addInitBlog(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        Blog blog = new Blog();
        blog.setId(IDUtil.getId());
        blog.setTitle("Mybatis如此簡單");
        blog.setAuthor("godfrey");
        blog.setCreateTime(new Date());
        blog.setViews(9999);

        mapper.addBlog(blog);

        blog.setId(IDUtil.getId());
        blog.setTitle("Java如此簡單");
        mapper.addBlog(blog);

        blog.setId(IDUtil.getId());
        blog.setTitle("Spring如此簡單");
        mapper.addBlog(blog);

        blog.setId(IDUtil.getId());
        blog.setTitle("微服務如此簡單");
        mapper.addBlog(blog);

        sqlSession.close();
    }
}

初始化數據完畢!

11.1、if 語句

需求:根據作者名字和博客名字來查詢博客!如果作者名字爲空,那麼只根據博客名字查詢,反之,則根據作者名來查詢

1、編寫接口類

//需求1
List<Blog> queryBlogIf(Map map);

2、編寫SQL語句

<!--需求1:
根據作者名字和博客名字來查詢博客!
如果作者名字爲空,那麼只根據博客名字查詢,反之,則根據作者名來查詢
select * from blog where title = #{title} and author = #{author}
-->
<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>
</select>

3、測試

@Test
public void testQueryBlogIf() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap<String, String> map = new HashMap<String, String>();
    
    map.put("title", "Mybatis如此簡單");
    map.put("author", "godfrey");
    List<Blog> blogs = mapper.queryBlogIf(map);
    
    System.out.println(blogs);
    sqlSession.close();
}

這樣寫我們可以看到,如果 author 等於 null,那麼查詢語句爲 select * from user where title=#{title},但是如果title爲空呢?那麼查詢語句爲 select * from user where and author=#{author},這是錯誤的 SQL 語句,如何解決呢?請看下面的 where 語句!

11.2、Where

1、編寫接口類

List<Blog> queryBlogWhere(Map map);

2、編寫SQL語句

<select id="queryBlogWhere" 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>

3、測試

@Test
public void testQueryBlogWhere() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap<String, String> map = new HashMap<String, String>();
    
    map.put("title", "Mybatis如此簡單");
    map.put("author", "godfrey");
    List<Blog> blogs = mapper.queryBlogWhere(map);
    
    System.out.println(blogs);
    sqlSession.close();
}

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

11.3、Set

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

1、編寫接口方法

int updateBlog(Map map);

2、sql配置文件

<!--注意set是用的逗號隔開-->
<update id="updateBlog" parameterType="map">
  update blog
     <set>
         <if test="title != null">
            title = #{title},
         </if>
         <if test="author != null">
            author = #{author}
         </if>
     </set>
  where id = #{id};
</update>

3、測試

@Test
public void testUpdateBlog(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    
    HashMap<String, String> map = new HashMap<String, String>();
    map.put("title","動態SQL");
    map.put("author","秦疆");
    map.put("id","9d6a763f5e1347cebda43e2a32687a77");
    
    mapper.updateBlog(map);
    
    sqlSession.close();
}

11.4、choose語句

有時候,我們不想用到所有的查詢條件,只想選擇其中的一個,查詢條件有一個滿足即可,使用 choose 標籤可以解決此類問題,類似於 Java 的 switch 語句

1、編寫接口方法

List<Blog> queryBlogChoose(Map map);

2、sql配置文件

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                and author = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

3、測試類

@Test
public void testQueryBlogChoose(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("title","Java如此簡單");
    map.put("author","godfrey");
    map.put("views",9999);
    List<Blog> blogs = mapper.queryBlogChoose(map);
    
    System.out.println(blogs);
    sqlSession.close();
}

11.5、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>
    	<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那麼需要在前面加上 namespace -->
        <include refid="if-title-author"></include>
        <!-- 在這裏還可以引用其他的 sql 片段 -->
    </where>
</select>

注意:

①、最好基於 單表來定義 sql 片段,提高片段的可重用性

②、在 sql 片段中不要包括 where

11.6、Foreach

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

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

1、編寫接口

List<Blog> queryBlogForeach(Map map);

2、編寫SQL語句

<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>

3、測試

@Test
public void testQueryBlogForeach(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    
    HashMap map = new HashMap();
    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(2);
    ids.add(3);
    map.put("ids",ids);
    
    List<Blog> blogs = mapper.queryBlogForeach(map);
    System.out.println(blogs);
    sqlSession.close();
}

小結:其實動態 sql 語句的編寫往往就是一個拼接的問題,爲了保證拼接準確,我們最好首先要寫原生的 sql 語句出來,然後在通過 mybatis 動態sql 對照着改,防止出錯

12、緩存

12.1、簡介

  1. 什麼是緩存 [ Cache ]?

    • 存在內存中的臨時數據
    • 將用戶經常查詢的數據放在緩存(內存)中,用戶去查詢數據就不用從磁盤上(關係型數據庫數據文件)查詢,從緩存中查詢,從而提高查詢效率,解決了高併發系統的性能問題
  2. 爲什麼使用緩存?

    • 減少和數據庫的交互次數,減少系統開銷,提高系統效率
  3. 什麼樣的數據能使用緩存?

    • 經常查詢並且不經常改變的數據

12.2、Mybatis緩存

  • MyBatis包含一個非常強大的查詢緩存特性,它可以非常方便地定製和配置緩存。緩存可以極大的提升查詢效率。

  • MyBatis系統中默認定義了兩級緩存:一級緩存二級緩存

    • 默認情況下,只有一級緩存開啓。(SqlSession級別的緩存,也稱爲本地緩存)
    • 二級緩存需要手動開啓和配置,他是基於namespace級別的緩存。
    • 爲了提高擴展性,MyBatis定義了緩存接口Cache。我們可以通過實現Cache接口來自定義二級緩存

12.3、一級緩存

一級緩存也叫本地緩存:

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

測試

1、在mybatis中加入日誌,方便測試結果

2、編寫接口方法

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

3、接口對應的Mapper文件

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

4、測試

@Test
    public void testQueryUserById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.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);

        sqlSession.close();
    }
}

5、結果分析

一級緩存失效的四種情況

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

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

1、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中的緩存相互獨立

2、sqlSession相同,查詢條件不同

@Test
public void testQueryUserById2() {
    SqlSession session = MybatisUtils.getSqlSession();
    SqlSession session2 = MybatisUtils.getSqlSession();
    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語句!很正常的理解

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

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

增加方法

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

編寫SQL

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

測試

@Test
public void testQueryUserById3(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.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);
    sqlSession.close();
}

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

結論:因爲增刪改操作可能會對當前數據產生影響

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

@Test
public void testQueryUserById3(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUserById(1);
    System.out.println(user);
    
    session.clearCache();//手動清除緩存
    
    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);
    sqlSession.close();
}

一級緩存就是一個map

12.4、二級緩存

  • 二級緩存也叫全局緩存,一級緩存作用域太低了,所以誕生了二級緩存

  • 基於namespace級別的緩存,一個名稱空間,對應一個二級緩存;

  • 工作機制

    • 一個會話查詢一條數據,這個數據就會被放在當前會話的一級緩存中;
    • 如果當前會話關閉了,這個會話對應的一級緩存就沒了;但是我們想要的是,會話關閉了,一級緩存中的數據被保存到二級緩存中;
    • 新的會話查詢信息,就可以從二級緩存中獲取內容;
    • 不同的mapper查出的數據會放在自己對應的緩存(map)中;

使用步驟

1、開啓全局緩存 【mybatis-config.xml】

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

2、去每個mapper.xml中配置使用二級緩存,這個配置非常簡單;【xxxMapper.xml】

<cache/>

官方示例=====>查看官方文檔
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>
這個更高級的配置創建了一個 FIFO 緩存,每隔 60 秒刷新,最多可以存儲結果對象或列表的 512 個引用,而且返回的對象被認爲是隻讀的,因此對它們進行修改可能會在不同線程中的調用者產生衝突。

3、代碼測試

  • 所有的實體類先實現序列化接口
  • 測試代碼
@Test
public void testQueryUserById3() {
    SqlSession session = MybatisUtils.getSqlSession();
    SqlSession session2 = MybatisUtils.getSqlSession();
    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中的查詢,可以在二級緩存中拿到數據
  • 查出的數據都會被默認先放在一級緩存中
  • 只有會話提交或者關閉以後,一級緩存中的數據纔會轉到二級緩存中

12.5、緩存原理圖

緩存順序:

  1. 先看二級緩存有沒有
  2. 再看一級緩存有沒有
  3. 查詢數據庫

12.6、EhCache

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

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

<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>

合理的使用緩存,可以讓我們程序的性能大大提升!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章