一、MyBatis的映射文件的概述
二、MyBatis**常用配置解析
1.environments標簽
- 事務管理器(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文件
4.typeAliases標簽
類型別名是為Java 類型設置一個短的名字。原來的類型名稱配置如下
配置typeAliases,為com.lagou.domain.User定義別名為user
批量取別名
<!--給實體類的全限定名給別名-->
<typeAliases>
<!-- 給單個實體類取別名 -->
<!-- <typeAlias type="com.wuzx.pojo.User" alias="xxuser"></typeAlias>-->
<!--批量取別名 ,別名就是類型,別名不區分大小寫-->
<package name="com.wuzx.pojo"/>
</typeAliases>
上面我們是自定義的別名,mybatis框架已經為我們設置好的一些常用的類型的別名
三、MyBatis復雜映射
實現復雜關系映射之前我們可以在映射文件中通過配置來實現,使用注解開發后,我們可以使用 @Results注解,@Result注解,@One注解,@Many注解組合完成復雜關系的配置
一對一查詢
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也提供了對緩存的支持,分為一級緩存
和二級緩存
- 一級緩存是SqlSession緩存。在操作數據庫時需要構造sqlSession對象。在對象中有一個數據結構
(HashMap)
用於存儲緩存數據。不同的sqlSession之間的緩存區域(HashMap)是互不影響的 - 二級緩存mapper級別的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個sqlsession可以公用二級緩存,二級緩存是跨SqlSession的。
一級緩存
1、第一次發起查詢用戶id為1的用戶信息,先去找緩存中是否有id為1的用戶信息,如果沒有,從 數據
庫查詢用戶信息。得到用戶信息,將用戶信息存儲到一級緩存中。
2、 如果中間sqlSession去執行commit操作(執行插入、更新、刪除),則會清空SqlSession中的 一 級緩存,這樣做的目的為了讓緩存中存儲的是最新的信息,避免臟讀。
3、 第二次發起查詢用戶id為1的用戶信息,先去找緩存中是否有id為1的用戶信息,緩存中有,直 接從 緩存中獲取用戶信息
一級緩存原理探究與源碼分析
調研了一圈,發現上述所有方法中,好像只有clearCache()和緩存沾點關系,那么就直接從這個方 法入 手吧,分析源碼時,我們要看它(此類)是誰,它的父類和子類分別又是誰,對如上關系了解了,你才 會 對這個類有更深的認識,分析了一圈,你可能會得到如下這個流程圖
再深入分析,流程走到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;
}
二級緩存
二級緩存默認不開啟(需要手動開啟),如何開啟二級緩存
首先在全局配置文件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接
useCache和flushCache
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中的四大對象都是代理對象
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>