本篇文章算是對mybatis的查漏補缺,第一次過的時候沒有注意到的知識點,在此做一些整理。
一、mybatis處理參數源碼
-
單個參數:mybatis不會做特殊處理。
- #{參數名/任意名}:取出參數值。
-
多個參數:mybatis會做特殊處理。
- 多個參數會被封裝成 一個map,
- key:param1…paramN,或者參數的索引也可以
- value:傳入的參數值
- #{}就是從map中獲取指定的key的值
- 多個參數會被封裝成 一個map,
-
【命名參數】:明確指定封裝參數時map的key;@Param(“id”)
- 多個參數會被封裝成 一個map,
- key:使用@Param註解指定的值
- value:參數值
- #{指定的key}取出對應的參數值
- 多個參數會被封裝成 一個map,
(@Param("id")Integer id,@Param("lastName")String lastName);
解析參數封裝map的主要邏輯在ParamNameResolver中。
/*確定流程:
1.獲取每個標了param註解的參數的@Param的值:id,lastName; 賦值給name;
2.每次解析一個參數給map中保存信息:(key:參數索引,value:name的值)
name的值:
標註了param註解:註解的值
沒有標註:
1.全局配置:useActualParamName(jdk1.8):name=參數名
2.name=map.size();相當於當前元素的索引
{0=id, 1=lastName,2=2}
args【1,"Tom",'hello'】:*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
//1、參數爲null直接返回
if (args == null || paramCount == 0) {
return null;
//2、如果只有一個元素,並且沒有Param註解;args[0]:單個參數直接返回
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
//3、多個元素或者有Param標註
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
//4、遍歷names集合;{0=id, 1=lastName,2=2}
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//names集合的value作爲key; names集合的key又作爲取值的參考args[0]:args【1,"Tom"】:
//eg:{id=args[0]:1,lastName=args[1]:Tom,2=args[2]}
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)param
//額外的將每一個參數也保存到map中,使用新的key:param1...paramN
//效果:有Param註解可以#{指定的key},或者#{param1}
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
二、佔位符比較
大多情況下,我們去參數的值都應該去使用#{},其採用預編譯的方式,可以防止sql注入,少數情況下需要${},比如原生jdbc不支持佔位符的地方。
-- 比如分表、排序。。。;按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
#{}包含更豐富的用法,如規定參數的一些規則:
javaType、 jdbcType、 mode(存儲過程)、 numericScale、resultMap、 typeHandler、 jdbcTypeName、 expression(未來準備支持的功能);
以jdbcType爲例,它通常需要在某種特定的條件下被設置:
數據爲null的時候,有些數據庫可能不能識別mybatis對null的默認處理,比如Oracle,就會出現錯誤,因爲mybatis對所有的null都映射的是原生Jdbc的OTHER類型,oracle不能正確處理;
由於oracle不支持全局配置中:jdbcTypeForNull=OTHER;
就有以下兩種解決辦法:
- 使用佔位符的規則:
#{email,jdbcType=OTHER}
; - 設置jdbcTypeForNull=NULL,即全局配置中:
<setting name="jdbcTypeForNull" 。value="NULL"/>
三、多表查詢
<!--
場景一:
查詢Employee的同時查詢員工對應的部門
Employee===Department
一個員工有與之對應的部門信息;
id last_name gender d_id did dept_name (private Department dept;)
-->
<!--
聯合查詢:級聯屬性封裝結果集
-->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="did" property="dept.id"/>
<result column="dept_name" property="dept.departmentName"/>
</resultMap>
<!--
使用association定義關聯的單個對象的封裝規則;
-->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp2">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<!-- association可以指定聯合的javaBean對象
property="dept":指定哪個屬性是聯合的對象
javaType:指定這個屬性對象的類型[不能省略]
-->
<association property="dept" javaType="com.atguigu.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
<!-- public Employee getEmpAndDept(Integer id);-->
<select id="getEmpAndDept" resultMap="MyDifEmp">
SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id,
d.id did,d.dept_name dept_name FROM tbl_employee e,tbl_dept d
WHERE e.d_id=d.id AND e.id=#{id}
</select>
<!-- 使用association進行分步查詢:
1、先按照員工id查詢員工信息
2、根據查詢員工信息中的d_id值去部門表查出部門信息
3、部門設置到員工中;
-->
<!-- id last_name email gender d_id -->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpByStep">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!-- association定義關聯對象的封裝規則
select:表明當前屬性是調用select指定的方法查出的結果
column:指定將哪一列的值傳給這個方法
流程:使用select指定的方法(傳入column指定的這列參數的值)查出對象,並封裝給property指定的屬性
-->
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</resultMap>
<!-- public Employee getEmpByIdStep(Integer id);-->
<select id="getEmpByIdStep" resultMap="MyEmpByStep">
select * from tbl_employee where id=#{id}
<if test="_parameter!=null">
and 1=1
</if>
</select>
<!-- 可以使用延遲加載(懶加載);(按需加載)
Employee==>Dept:
我們每次查詢Employee對象的時候,都將一起查詢出來。
部門信息在我們使用的時候再去查詢;
分段查詢的基礎之上加上兩個配置:
-->
四、鑑別器
<!-- =======================鑑別器============================ -->
<!-- <discriminator javaType=""></discriminator>
鑑別器:mybatis可以使用discriminator判斷某列的值,然後根據某列的值改變封裝行爲
封裝Employee:
如果查出的是女生:就把部門信息查詢出來,否則不查詢;
如果是男生,把last_name這一列的值賦值給email;
-->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpDis">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!--
column:指定判定的列名
javaType:列值對應的java類型 -->
<discriminator javaType="string" column="gender">
<!--女生 resultType:指定封裝的結果類型;不能缺少。/resultMap-->
<case value="0" resultType="com.atguigu.mybatis.bean.Employee">
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</case>
<!--男生 ;如果是男生,把last_name這一列的值賦值給email; -->
<case value="1" resultType="com.atguigu.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="last_name" property="email"/>
<result column="gender" property="gender"/>
</case>
</discriminator>
</resultMap>
五、trim標籤
<!--public List<Employee> getEmpsByConditionTrim(Employee employee); -->
<select id="getEmpsByConditionTrim" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
<!-- 後面多出的and或者or where標籤不能解決
prefix="":前綴:trim標籤體中是整個字符串拼串 後的結果。
prefix給拼串後的整個字符串加一個前綴
prefixOverrides="":
前綴覆蓋: 去掉整個字符串前面多餘的字符
suffix="":後綴
suffix給拼串後的整個字符串加一個後綴
suffixOverrides=""
後綴覆蓋:去掉整個字符串後面多餘的字符
-->
<!-- 自定義字符串的截取規則 -->
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null && lastName!=""">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=""">
email=#{email} and
</if>
<!-- ognl會進行字符串與數字的轉換判斷 "0"==0 -->
<if test="gender==0 or gender==1">
gender=#{gender}
</if>
</trim>
</select>
六、動態sql 內置參數+bind
不只是方法傳遞過來的參數可以被用來判斷,取值,mybatis默認還有兩個內置參數:
- _parameter:代表整個參數
- 單個參數:_parameter就是這個參數
- 多個參數:參數會被封裝爲一個map;_parameter就是代表這個map
- _databaseId:數據庫id
- 如果配置了databaseIdProvider標籤,_databaseId就是代表當前數據庫的別名。
bind:可以將OGNL表達式的值綁定到一個變量中,方便後來引用這個變量的值。
<!--public List<Employee> getEmpsTestInnerParameter(Employee employee); -->
<select id="getEmpsTestInnerParameter" resultType="com.atguigu.mybatis.bean.Employee">
<!-- bind:可以將OGNL表達式的值綁定到一個變量中,方便後來引用這個變量的值 -->
<bind name="_lastName" value="'%'+lastName+'%'"/>
<if test="_databaseId=='mysql'">
select * from tbl_employee
<if test="_parameter!=null">
where last_name like #{lastName}
</if>
</if>
<if test="_databaseId=='oracle'">
select * from employees
<if test="_parameter!=null">
where last_name like #{_parameter.lastName}
</if>
</if>
</select>
七、緩存相關
一級緩存 sqlsession級別,一直開啓。 與數據庫同一次會話期間查詢到的數據會放到本地緩存中,獲取相同的數據直接從緩存中獲取,而沒必要查詢數據庫。
一級緩存失效的情況:
-
兩次查詢sqlsession不同。
-
sqlsession相同,查詢條件不同,當前一級緩存中沒由該數據。
-
sqlsession相同,兩次查詢之間執行了增刪改操作。
-
sqlsession相同,手動清除了一級緩存。
一級緩存範圍:
默認爲session,一個會話範圍內,可以指定statement,爲當前語句。
二級緩存,基於namespace,一個namespace對應一個二級緩存。
二級緩存工作機制
- 一個會話查詢一條數據,存放在當前會話的一級緩存中。
- 如果會話關閉,一級緩存中的數據會被保存到二級緩存中,新的會話查詢信息,就可以參照二級緩存。不同的namespace查詢出的數據會保存自己的緩存中。
- 查出的數據都會被默認先放在entriesToAddOnCommit中,只有會話提交或者關閉以後,一級緩存中的數據纔會轉移到二級緩存中。
二級緩存開啓:
-
主配置文件settings中enableCache設置爲true,默認爲true。
-
mapper映射配置文件設置cache。
<!--<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>-->
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"> </cache> -->
- pojo實現serializable接口
cache的標籤屬性:
- eviction:緩存的回收策略:
- LRU – 最近最少使用的:移除最長時間不被使用的對象。
- FIFO – 先進先出:按對象進入緩存的順序來移除它們。
- SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
- WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。
- 默認的是 LRU。
- flushInterval:緩存刷新間隔
- 緩存多長時間清空一次,默認不清空,設置一個毫秒值
- readOnly:是否只讀:
- true:
- 只讀,mybatis認爲所有從緩存中獲取數據的操作都是隻讀操作,不會修改數據。
- mybatis爲了加快獲取速度,直接就會將數據在緩存中的引用交給用戶。不安全,速度快
- false:
- 非只讀:mybatis覺得獲取的數據可能會被修改。
- mybatis會利用序列化&反序列的技術克隆一份新的數據給你(pojo實現serializable接口)。安全,速度慢
- true:
- size:緩存存放多少元素;
- type="":指定自定義緩存的全類名;
- 實現Cache接口即可;
和緩存相關的配置和屬性
- cacheEnabled=true:false:關閉緩存(二級緩存關閉)(一級緩存一直可用的)。
- 每個select標籤都有useCache=“true”:
- false:不使用緩存(一級緩存依然使用,二級緩存不使用)
- 【每個增刪改標籤的:flushCache=“true”:(一級二級都會清除)】查詢標籤:flushCache=“false”:
- 增刪改執行完成後就會清除緩存;flushCache=“true”:一級緩存就清空了;二級也會被清除;
- 如果flushCache=true;每次查詢之後都會清空緩存;緩存是沒有被使用的;
- sqlSession.clearCache();只是清楚當前session的一級緩存;
- localCacheScope:本地緩存作用域:(一級緩存SESSION);當前會話的所有數據保存在會話緩存中;STATEMENT:可以禁用一級緩存;