1、MyBatis認識與使用(超詳細)


一、MyBatis的映射文件的概述

image

image

二、MyBatis**常用配置解析

1.environments標簽

image

  • 事務管理器(transactionManager)類型有兩種:
    • JDBC:這個配置就是直接使用了JDBC 的提交和回滾設置,它依賴於從數據源得到的連接來管理事務作用域。
    • MANAGED:這個配置幾乎沒做什么。它從來不提交或回滾一個連接,而是讓容器來管理事務的整個生 命周期(比如 JEE 應用服務器的上下文)。 默認情況下它會關閉連接,然而一些容器並不希望這樣,因 此需要將 closeConnection 屬性設置為 false 來阻止它默認的關閉行為。
  • 數據源(dataSource)類型有三種:
    • UNPOOLED:這個數據源的實現只是每次被請求時打開和關閉連接。
    • POOLED:這種數據源的實現利用“池”的概念將 JDBC 連接對象組織起來
    • JNDI:這個數據源的實現是為了能在如 EJB 或應用服務器這類容器中使用,容器可以集中或在外部配 置數據源,然后放置一個 JNDI 上下文的引用

2.mapper標簽

該標簽的作用是加載映射的,加載方式有如下幾種:

•使用相對於類路徑的資源引用,例如:
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
•使用完全限定資源定位符(URL),例如:
<mapper url="file:///var/mappers/AuthorMapper.xml"/> 
•使用映射器接口實現類的完全限定類名,例如:
<mapper class="org.mybatis.builder.AuthorMapper"/> 
•將包內的映射器接口實現全部注冊為映射器,例如:
<package name="org.mybatis.builder"/>

3.Properties標簽

實際開發中,習慣將數據源的配置信息單獨抽取成一個properties文件,該標簽可以加載額外配置的properties文件

image

4.typeAliases標簽

類型別名是為Java 類型設置一個短的名字。原來的類型名稱配置如下

image

配置typeAliases,為com.lagou.domain.User定義別名為user
image

批量取別名
<!--給實體類的全限定名給別名-->
<typeAliases>
  <!-- 給單個實體類取別名 -->
  <!--  <typeAlias type="com.wuzx.pojo.User" alias="xxuser"></typeAlias>-->
  <!--批量取別名 ,別名就是類型,別名不區分大小寫-->
  <package name="com.wuzx.pojo"/>
</typeAliases>

上面我們是自定義的別名,mybatis框架已經為我們設置好的一些常用的類型的別名

image

三、MyBatis復雜映射

實現復雜關系映射之前我們可以在映射文件中通過配置來實現,使用注解開發后,我們可以使用 @Results注解,@Result注解,@One注解,@Many注解組合完成復雜關系的配置

image

一對一查詢


public interface OrderMapper {
    @Select("select * from orders")
    @Results({
            @Result(id=true,property = "id",column = "id"),
            @Result(property = "ordertime",column = "ordertime"),
            @Result(property = "total",column = "total"),
            @Result(property = "user",column = "uid",
                    javaType = User.class,
                    one = @One(select =
"com.lagou.mapper.UserMapper.findById"))
})
    List<Order> findAll();
}

一對多查詢

 
public interface UserMapper {
    @Select("select * from user")
    @Results({
            @Result(id = true,property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "password",column = "password"),
            @Result(property = "birthday",column = "birthday"),
            @Result(property = "orderList",column = "id",
                    javaType = List.class,
                    many = @Many(select =
"com.lagou.mapper.OrderMapper.findByUid"))
})
    List<User> findAllUserAndOrder();
}
public interface OrderMapper {
    @Select("select * from orders where uid=#{uid}")
    List<Order> findByUid(int uid);
}

多對多查詢

 
public interface UserMapper {
    @Select("select * from user")
    @Results({
        @Result(id = true,property = "id",column = "id"),
        @Result(property = "username",column = "username"),
        @Result(property = "password",column = "password"),
        @Result(property = "birthday",column = "birthday"),
        @Result(property = "roleList",column = "id",
                javaType = List.class,
                many = @Many(select =
"com.lagou.mapper.RoleMapper.findByUid"))
})
List<User> findAllUserAndRole();}
public interface RoleMapper {
    @Select("select * from role r,user_role ur where r.id=ur.role_id and
ur.user_id=#{uid}")
    List<Role> findByUid(int uid);
}

四、Mybatis緩存

緩存就是內存中的數據,常常來自對數據庫中查詢結果的保存,使用緩存,我們可以避免頻繁與數據庫進行交互,進而提高響應速度

mybatis也提供了對緩存的支持,分為一級緩存二級緩存

image

  • 一級緩存是SqlSession緩存。在操作數據庫時需要構造sqlSession對象。在對象中有一個數據結構(HashMap)用於存儲緩存數據。不同的sqlSession之間的緩存區域(HashMap)是互不影響的
  • 二級緩存mapper級別的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個sqlsession可以公用二級緩存,二級緩存是跨SqlSession的。

一級緩存

image

1、第一次發起查詢用戶id為1的用戶信息,先去找緩存中是否有id為1的用戶信息,如果沒有,從 數據

庫查詢用戶信息。得到用戶信息,將用戶信息存儲到一級緩存中。

2、 如果中間sqlSession去執行commit操作(執行插入、更新、刪除),則會清空SqlSession中的 一 級緩存,這樣做的目的為了讓緩存中存儲的是最新的信息,避免臟讀。

3、 第二次發起查詢用戶id為1的用戶信息,先去找緩存中是否有id為1的用戶信息,緩存中有,直 接從 緩存中獲取用戶信息

一級緩存原理探究與源碼分析

image

調研了一圈,發現上述所有方法中,好像只有clearCache()和緩存沾點關系,那么就直接從這個方 法入 手吧,分析源碼時,我們要看它(此類)是誰,它的父類和子類分別又是誰,對如上關系了解了,你才 會 對這個類有更深的認識,分析了一圈,你可能會得到如下這個流程圖

image

再深入分析,流程走到Perpetualcache中的clear()方法之后,會調用其cache.clear()方法,那 么這個 cache是什么東⻄呢?點進去發現,cache其實就是private Map cache = new

HashMap();也就是一個Map,所以說cache.clear()其實就是map.clear(),也就是說,緩存其實就是 本地存放的一個map對象,每一個SqISession都會存放一個map對象的引用,那么這個cache是何 時創 建的呢

你覺得最有可能創建緩存的地方是哪里呢?我覺得是Executor,為什么這么認為?因為Executor是 執 行器,用來執行SQL請求,而且清除緩存的方法也在Executor中執行,所以很可能緩存的創建也很 有可 能在Executor中,看了一圈發現Executor中有一個createCacheKey方法,這個方法很像是創 建緩存的 方法啊,跟進去看看,你發現createCacheKey方法是由BaseExecutor執行的,代碼如下

CacheKey cacheKey = new CacheKey();
 
//MappedStatement 的 id
// id就是Sql語句的所在位置包名+類名+ SQL名稱 cacheKey.update(ms.getId());
// offset 就是 0
cacheKey.update(rowBounds.getOffset());
// limit 就是 Integer.MAXVALUE cacheKey.update(rowBounds.getLimit());
//具體的SQL語句
cacheKey.update(boundSql.getSql());
//后面是update 了 sql中帶的參數
cacheKey.update(value);
...
if (configuration.getEnvironment() != null) {
// issue #176 cacheKey.update(configuration.getEnvironment().getId()); 
}

緩存到底用在

 
Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
//創建緩存
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key,  BoundSql boundSql)
throws SQLException {
...
  list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//這個主要是處理存儲過程用的。
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key,
boundSql); }
... }
// queryFromDatabase 方法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list; 
localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
    }
  return list;
}

二級緩存

image

二級緩存默認不開啟(需要手動開啟),如何開啟二級緩存

首先在全局配置文件sqlMapConfig.xml文件中加入如下代碼:

<!--開啟二級緩存--> 
<settings>
	<setting name="cacheEnabled" value="true"/>
</settings>

其次在UserMapper.xml文件中開啟緩存

<!--開啟二級緩存--> <cache></cache>

我們可以看到mapper.xml文件中就這么一個空標簽,其實這里可以配置,PerpetualCache這個類是 mybatis默認實現緩存功能的類。我們不寫type就使用mybatis默認的緩存,也可以去實現Cache接口 來自定義緩存

開啟了二級緩存后,還需要將要緩存的pojo實現Serializable接口,為了將緩存數據取出執行反序列化操 作,因為二級緩存數據存儲介質多種多樣,不一定只存在內存中,有可能存在硬盤中,如果我們要再取 這個緩存的話,就需要反序列化了。所以mybatis中的pojo都去實現Serializable接

useCacheflushCache

mybatis中還可以配置userCache和flushCache等配置項,userCache是用來設置是否禁用二級緩 存 的,在statement中設置useCache=false可以禁用當前select語句的二級緩存,即每次查詢都會發出 sql 去查詢,默認情況是true,即該sql使用二級緩存

<select id="selectUserByUserId" useCache="false"
resultType="com.lagou.pojo.User" parameterType="int">
  select * from user where id=#{id}
</select>

設置statement配置中的flushCache="true”屬性,默認情況下為true,即刷新緩存,如果改成false則 不 會刷新。使用緩存時如果手動修改數據庫表中的查詢數據會出現臟讀。

 <select id="selectUserByUserId" flushCache="true" useCache="false"
resultType="com.lagou.pojo.User" parameterType="int">
  select * from user where id=#{id}
</select>

一般下執行完commit操作都需要刷新緩存,flushCache=true表示刷新緩存,這樣可以避免數據庫臟 讀。所以我們不用設置,默認即可

二級緩存整合redis

pom依賴

<dependency>
  <groupId>org.mybatis.caches</groupId>
  <artifactId>mybatis-redis</artifactId>
  <version>1.0.0-beta2</version>
</dependency>

配置文件設置

 
<?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.lagou.mapper.IUserMapper">
<cache type="org.mybatis.caches.redis.RedisCache" />
<select id="findAll" resultType="com.lagou.pojo.User" useCache="true">
  select * from user
</select>

mapper接口設置

@CacheNamespace(implementation = RedisCache.class) // 開啟二級緩存
public interface IUserMapper {}

生成 redis.properties

redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

五、Mybatis插件

1.插件簡介

一般情況下,開源框架都會提供插件或其他形式的拓展點,供開發者自行拓展。這樣的好處是顯而易⻅ 的,一是增加了框架的靈活性。二是開發者可以結合實際需求,對框架進行拓展,使其能夠更好的工 作。以MyBatis為例,我們可基於MyBati s插件機制實現分⻚、分表,監控等功能。由於插件和業務 無 關,業務也無法感知插件的存在。因此可以無感植入插件,在無形中增強功能

2.Mybatis插件介紹

Mybati s作為一個應用廣泛的優秀的ORM開源框架,這個框架具有強大的靈活性,在四大組件

(Executor、StatementHandler、ParameterHandler、ResultSetHandler)處提供了簡單易用的插 件擴 展機制。Mybatis對持久層的操作就是借助於四大核心對象。MyBatis支持用插件對四大核心對象進 行 攔截,對mybatis來說插件就是攔截器,用來增強核心對象的功能,增強功能本質上是借助於底層的 動 態代理實現的,換句話說,MyBatis中的四大對象都是代理對象

image

MyBatis所允許攔截的方法如下:

  • 執行器Executor (update、query、commit、rollback等方法);
  • SQL語法構建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
  • 參數處理器ParameterHandler (getParameterObject、setParameters方法);
  • 結果集處理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);

Mybatis插件原理

在四大對象創建的時候

1、每個創建出來的對象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);

2、獲取到所有的Interceptor (攔截器)(插件需要實現的接口);調用 interceptor.plugin(target);返 回 target 包裝后的對象
3、插件機制,我們可以使用插件為目標對象創建一個代理對象;AOP (面向切面)我們的插件可 以 為四大對象創建出代理對象,代理對象就可以攔截到四大對象的每一個執行

插件具體是如何攔截並附加額外的功能的呢?以ParameterHandler來說

 
public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
Object object, BoundSql sql, InterceptorChain interceptorChain){
    ParameterHandler parameterHandler =
mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
  parameterHandler = (ParameterHandler)
  interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}
public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
}
  return target;
}

interceptorChain保存了所有的攔截器(interceptors),是mybatis初始化的時候創建的。調用攔截器鏈 中的攔截器依次的對目標進行攔截或增強。interceptor.plugin(target)中的target就可以理解為mybatis 中的四大對象。返回的target是被重重代理后的對象

如果我們想要攔截Executor的query方法,那么可以這樣定義插件:

 @Intercepts({
  @Signature(
    type = Executor.class,
    method = "query",
    args=
{MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
    )
})
public class ExeunplePlugin implements Interceptor {
//省略邏輯 }

除此之外,我們還需將插件配置到sqlMapConfig.xm l中。

 <plugins>
  <plugin interceptor="com.lagou.plugin.ExamplePlugin">
  </plugin>
</plugins>

自定義插件

  • Mybatis 插件接口-Interceptor
  • • Intercept方法,插件的核心方法
    • plugin方法,生成target的代理對象
    • setProperties方法,傳遞插件所需參數
package com.wuzx.plugin;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;


@Intercepts({
        @Signature(type = StatementHandler.class,//這是指攔截哪個接口
                   method = "prepare", //這個接口內的哪個方法名
                   args = {Connection.class, Integer.class}//// 這是攔截的方法的入參,按
順序寫到這,不要多也不要少,如果方法重載,可是要通過方法名和入參來確定唯一的
                  )

})
public class MyPlugin implements Interceptor {


    /**
     * 攔截方法,只要被攔截的目標對象的目標方法被執行時,每次都會執行intercept方法
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("對方法進行了增強,,,,");
        return invocation.proceed(); //讓原方法執行
    }

    /**
     * 主要為了把當前攔截器生成代理對象存到攔截器鏈里面
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        final Object wrap = Plugin.wrap(target, this);
        return wrap;
    }

    /**
     * 獲取配置文件的參數
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("獲取到的配置文件參數:" + properties);
    }
}

核心配置文件注入插件 sqlMapConfig.xml

 <plugins>
  <plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
<!--配置參數-->
    <property name="name" value="Bob"/>
  </plugin>
</plugins>

插件源碼分析

Plugin實現了 InvocationHandler接口,因此它的invoke方法會攔截所有的方法調用。invoke方法會 對 所攔截的方法進行檢測,以決定是否執行插件邏輯。該方法的邏輯如下:

 
// -Plugin
    public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
try { /*
commit]
*獲取被攔截方法列表,比如:
* signatureMap.get(Executor.class), 可能返回 [query, update,
*/
            Set<Method> methods =
signatureMap.get(method.getDeclaringClass());
args));
//檢測方法列表是否包含被攔截的方法
if (methods != null && methods.contains(method)) {
//執行插件邏輯
return interceptor.intercept(new Invocation(target, method,
//執行被攔截的方法
    return method.invoke(target, args);
} catch(Exception e){
} }

六、通用 mapper

什么是通用Mapper

通用Mapper就是為了解決單表增刪改查,基於Mybatis的插件機制。開發人員不需要編寫SQL,不需要 在DAO中增加方法,只要寫好實體類,就能支持相應的增刪改查方法

依賴

 <dependency>
  <groupId>tk.mybatis</groupId>
  <artifactId>mapper</artifactId>
  <version>3.1.2</version>
</dependency>

Mybatis配置文件中完成配置

 
<plugins>
<!--分⻚插件:如果有分⻚插件,要排在通用mapper之前-->
<plugin interceptor="com.github.pagehelper.PageHelper">
      <property name="dialect" value="mysql"/>
  </plugin>
<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor"> <!-- 通用Mapper接口,多個通用接口用逗號隔開 -->
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
  </plugin>
</plugins>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM