Mybatis常見問題
1,大于號、小于號在sql陳述句中的轉換
使用 mybatis 時 sql 陳述句是寫在 xml 檔案中,如果 sql 中有一些特殊的字符的話,比如< ,<=,>,>=等符號,會引起 xml 格式的錯誤,需要替換掉,或者不被轉義, 有兩種方法可以解決:轉義字符和標記 CDATA 塊,
方式一:轉義字符
<select id="searchByPrice" parameterType="Map" resultType="Product">
<!-- 方式1、轉義字符 -->
select * from Product where price >= #{minPrice} and price <= #{maxPrice}
</select>
方式二:標記CDATA
<select id="searchByPrice" parameterType="Map" resultType="Product">
<!-- 方式2、CDATA -->
<![CDATA[select * from Product where price >= #{minPrice} and price <= #{maxPrice} ]]> </select>
轉義字符表

2.傳入引數時引數為0查詢條件失效
場景案例
場景是這樣的,需要做一個對賬單查詢,可以按金額范圍進行查詢,頁面引數寫完之后進行條件,輸入0測驗了無數次均失效,
原因決議
當頁面引數為0,傳入到mybatis的xml中后,如果不是字串,需指定資料型別,否則會被誤認為null
<if test="data.tatalAmount != null and data.totalAmount !='' ">
and total_Amount=#{data.totalAmount}
</if>
這種情況如果totalAmount為0時將被誤認為是null,里面的條件不會被執行,
解決方案
1,添加0判斷
<if test="data.tatalAmount != null and data.totalAmount !='' or tatalAmount==0 ">
and total_Amount=#{data.totalAmount}
</if>
2,規定傳入引數的型別
<if test="data.tatalAmount != null and data.totalAmount !='' ">
and total_Amount=#{data.totalAmount,jdbc.Type=DECIMAL}
</if>
3,Mybatis中#{}和${}區別
#{}是預編譯處理,像傳進來的資料會加個" "(#將傳入的資料都當成一個字串,會對自動傳入的資料加一個雙引號)
${}就是字串拼接,直接替換掉占位符,$方式一般用于傳入資料庫物件,例如傳入表名,
使用${}的話會導致sql注入,什么是sql注入呢?比如select * from user where id = #{value}
value本應該是一個數值,然后如果對方傳過來的是 001 and name = tom.這樣不就相當于多加了一條sql陳述句進去,
把SQL陳述句直接寫進來了,如果是攻擊性的陳述句呢?001;drop table user,直接把表給刪了
所以為了防止 SQL 注入,能用 #{} 的不要去用 ${}
如果非要用 ${} 的話,那要注意防止 SQL 注入問題,可以手動判定傳入的變數,進行過濾,一般 SQL 注入會輸入很長的一條 SQL 陳述句
4,Mybatis動態sql陳述句(OGNL語法)
1、if
解決當要查詢的多個條件有一個為空而導致的查詢結果為空的情況
<select id="select" resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="name!= null">
AND name like #{title}
</if>
</select>
2、where
像上面的那種情況,如果where后面沒有條件,然后需要直接寫if判斷(開頭如果是 and / or 的話,會去除掉)
<select id="select" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="title != null">
AND title like #{title}
</if>
<if test="name!= null">
AND name like #{title}
</if>
<where>
</select>
3、choose(when、otherwise)
choose 相當于 java 里面的 switch 陳述句,otherwise(其他情況)
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
4、trim
prefix:前綴prefixoverride:去掉第一個and或者是or
select * from test
<trim prefix="WHERE" prefixoverride="AND丨OR">
<if test="a!=null and a!=' '">AND a=#{a}<if>
<if test="b!=null and b!=' '">AND a=#{a}<if>
</trim>
5、set
set 元素主要是用在更新操作的時候,如果包含的陳述句是以逗號結束的話將會把該逗號忽略,如果set包含的內容為空的話則會出錯,
<update id="dynamicSetTest" parameterType="Blog">
update t_blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="content != null">
content = #{content},
</if>
<if test="owner != null">
owner = #{owner}
</if>
</set>
where id = #{id}
</update>
6、foreach
foreach主要用在構建in條件中
<select id="dynamicForeachTest" resultType="Blog">
select * from t_blog where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
open separator close
相當于是in (?,?,?)
如果是個map怎么辦
<select id="dynamicForeach3Test" resultType="Blog">
select * from t_blog where title like "%"#{title}"%" and id in
<foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
collection對應map的鍵,像這樣
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
ids.add(6);
ids.add(7);
ids.add(9);
Map<String, Object> params = new HashMap<String, Object>();
params.put("ids", ids);
5,Like模糊查詢
方式一:
$ 這種方式,簡單,但是無法防止SQL注入,所以不推薦使用
LIKE '%${name}%'
方式二:
LIKE "%"#{name}"%"
方式三:字串拼接
AND name LIKE CONCAT(CONCAT('%',#{name},'%'))
方式四:bind標簽
<select id="searchStudents" resultType="com.example.entity.StudentEntity"
parameterType="com.example.entity.StudentEntity">
<bind name="pattern1" value="'%' + _parameter.name + '%'" />
<bind name="pattern2" value="'%' + _parameter.address + '%'" />
SELECT * FROM test_student
<where>
<if test="age != null and age != '' and compare != null and compare != ''">
age
${compare}
#{age}
</if>
<if test="name != null and name != ''">
AND name LIKE #{pattern1}
</if>
<if test="address != null and address != ''">
AND address LIKE #{pattern2}
</if>
</where>
ORDER BY id
</select>
6,傳遞多個引數
方法一:使用map介面傳遞引數
嚴格來說,map適用幾乎所有場景,但是我們用得不多,原因有兩個:首先,map是一個鍵值對應的集合,使用者要通過閱讀它的鍵,才能明了其作用;其次,使用map不能限定其傳遞的資料型別,因此業務性質不強,可讀性差,使用者要讀懂代碼才能知道需要傳遞什么引數給它,所以不推薦用這種方式傳遞多個引數,
public List<Role> findRolesByMap(Map<String, Object> parameterMap);
<select id="findRolesByMap" parameterType="map" resultType="role">
select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>
方法二:使用注解傳遞多個引數
MyBatis為開發者提供了一個注解@Param(org.apache.ibatis.annotations.Param),可以通過它去定義映射器的引數名稱,使用它可以得到更好的可讀性 這個時候需要修改映射檔案的代碼,此時并不需要給出parameterType屬性,讓MyBatis自動探索便可以了 使可讀性大大提高,使用者也方便了,但是這會帶來一個麻煩,如果SQL很復雜,擁有大于10個引數,那么介面方法的引數個數就多了,使用起來就很不容易,不過不必擔心,MyBatis還提供傳遞Java Bean的形式,
public List<Role> findRolesByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
<select id="findRolesByAnnotation" resultType="role">
select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>
方法三:通過Java Bean傳遞多個引數
public List<Role> findRolesByBean(RoleParams roleParam);
<select id="findRolesByBean" parameterType="com.xc.pojo.RoleParams" resultType="role">
select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>
方法四:混合使用
在某些情況下可能需要混合使用幾種方法來傳遞引數,舉個例子,查詢一個角色,可以通過角色名稱和備注進行查詢,與此同時還需要支持分頁
public List<Role> findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParam PageParam);
<select id="findByMix" resultType="role">
select id, role_name as roleName, note from t_role
where role_name like concat('%', #{params.roleName}, '%') and note like concat('%', #{params.note}, '%')
limit #{page.start}, #{page.limit}
</select>
總結:
描述了4種傳遞多個引數的方法,對各種方法加以點評和總結,以利于我們在實際操作中的應用,
?使用 map 傳遞引數導致了業務可讀性的喪失,導致后續擴展和維護的困難,在實際的應用中要果斷廢棄這種方式,
?使用 @Param 注解傳遞多個引數,受到引數個數(n)的影響,當 n≤5 時,這是最佳的傳參方式,它比用 Java Bean 更好,因為它更加直觀;當 n>5 時,多個引數將給呼叫帶來困難,此時不推薦使用它,
?當引數個數多于5個時,建議使用 Java Bean 方式,
?對于使用混合引數的,要明確引數的合理性,
7,MyBatis快取機制
快取機制減輕資料庫壓力,提高資料庫性能
mybatis的快取分為兩級:一級快取、二級快取
一級快取:
一級快取為 SqlSession 快取,快取的資料只在 SqlSession 內有效,在操作資料庫的時候需要先創建 SqlSession 會話物件,在物件中有一個 HashMap 用于存盤快取資料,此 HashMap 是當前會話物件私有的,別的 SqlSession 會話物件無法訪問,
具體流程:
第一次執行 select 完畢會將查到的資料寫入 SqlSession 內的 HashMap 中快取起來
第二次執行 select 會從快取中查資料,如果 select 同傳引數一樣,那么就能從快取中回傳資料,不用去資料庫了,從而提高了效率
注意:
1、如果 SqlSession 執行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就會清空當前 SqlSession 快取中的所有快取資料,這樣可以保證快取中的存的資料永遠和資料庫中一致,避免出現差異
2、當一個 SqlSession 結束后那么他里面的一級快取也就不存在了, mybatis 默認是開啟一級快取,不需要配置
3、 mybatis 的快取是基于 [namespace:sql陳述句:引數] 來進行快取的,意思就是, SqlSession 的 HashMap 存盤快取資料時,是使用 [namespace:sql:引數] 作為 key ,查詢回傳的陳述句作為 value 保存的
二級快取:
二級快取是mapper 級別的快取,也就是同一個 namespace 的 mapper.xml ,當多個 SqlSession 使用同一個 Mapper 操作資料庫的時候,得到的資料會快取在同一個二級快取區域
二級快取默認是沒有開啟的,需要在 setting 全域引數中配置開啟二級快取
開啟二級快取步驟:
1、conf.xml 配置全域變數開啟二級快取
<settings>
<setting name="cacheEnabled" value="https://www.cnblogs.com/yaomagician/archive/2023/04/07/true"/>默認是false:關閉二級快取
<settings>
2、在userMapper.xml中配置
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>當前mapper下所有陳述句開啟二級快取
這里配置了一個 FIFO快取,并每隔60秒重繪,最大存盤512個物件,而回傳的物件是只讀的,因此在不同執行緒中的呼叫者之間修改它們會導致沖突,可用的識訓策略有,默認的是LRU:
-
LRU- 最近最少使用的;移除最長時間不被使用的物件, -
FIFO- 先進先出;按物件進入快取的順序來移除它們, -
SOFT- 軟參考;移除基于垃圾回收器狀態和軟參考規則的物件 -
WEAK- 弱參考;更積極地移除基干垃圾收集器狀態和弱參考規則的物件
若想禁用當前select陳述句的二級快取,添加 useCache="false"修改如下:
<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">
具體流程:
1.當一個sqlseesion執行了一次select 后,在關閉此session 的時候,會將查詢結果快取到二級快取
2.當另一個sqlsession執行select 時,首先會在他自己的一級快取中找,如果沒找到,就回去二級快取中找,找到了就回傳,就不用去資料庫了,從而減少了資料庫壓力提高了性能
注意:
1、如果 SqlSession 執行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就會清空當前mapper 快取中的所有快取資料,這樣可以保證快取中的存的資料永遠和資料庫中一致,避免出現差異
2、mybatis 的一級快取是基于[namespace:sql陳述句:引數]來進行快取的,意思就是,SqlSession 的 HashMap 存盤快取資料時,是使用 [namespace:sql:引數]作為 key ,查詢回傳的陳述句作為 value 保存的,
是否應該使用二級快取?
那么究竟應該不應該使用二級快取呢?先來看一下二級快取的注意事項:
-
快取是以
namespace為單位的,不同namespace下的操作互不影響, -
insert,update,delete操作會清空所在
namespace下的全部快取, -
通常使用MyBatis Generator生成的代碼中,都是各個表獨立的,每個表都有自己的
namespace, -
多表操作一定不要使用二級快取,因為多表操作進行更新操作,一定會產生臟資料,
如果你遵守二級快取的注意事項,那么你就可以使用二級快取,
但是,如果不能使用多表操作,二級快取不就可以用一級快取來替換掉嗎?而且二級快取是表級快取,開銷大,沒有一級快取直接使用 HashMap 來存盤的效率更高,所以二級快取并不推薦使用,
8,MyBatis時間timestamp做條件進行查詢
首先要將條件 轉換為 時間戳
long startTime = TimeUtil.parseTimestamp(start);
long endTime = TimeUtil.parseTimestamp(end);
/*對應工具類*/
public static long parseTimestamp(String datetime){
try{
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = dateformat.parse(datetime);
return date.getTime()/1000;
}catch(Exception e){
e.printStackTrace();
}
return 0;
}
然后Mapper.xml中 使用BETWEEN and 和 to_timestamp
<if test="startDate !=null and startDate !='' and endDate !=null and endDate !=''">
AND tdnm.create_time BETWEEN to_timestamp(#{startDate}) AND to_timestamp(#{endDate})
</if>
9,mybatis 是否支持延遲加載?延遲加載的原理是什么?
1.MyBatis 支持延遲加載,
2.什么是延遲加載:延遲加載,也稱為懶加載,是指在進行關聯查詢時,按照設定延遲規則推遲對關聯物件的select查詢,延遲加載可以有效的減少資料庫壓力,MyBatis的延遲加載只是對關聯物件的查詢有延遲設定,對于主加載物件都是直接執行查詢陳述句的,
3.MyBatis 對關聯物件的加載型別
(1)直接加載:執行完對主加載物件的select陳述句,馬上執行對關聯物件的select查詢,
(2)侵入式延遲:執行對主加載物件的查詢時,不會執行對關聯物件的查詢,但當要訪問主加載物件的詳情時,就會馬上執行關聯物件的select查詢,(將關聯物件的詳情作為主加載物件的詳情的一部分出現)
(3)深度延遲:執行對主加載物件的查詢時,不會執行對關聯物件的查詢,訪問主加載物件的詳情時也不會執行關聯物件的select查詢,只有當真正訪問關聯物件的詳情時,才會執行對關聯物件的select查詢,
4.延遲加載的原理:呼叫的時候觸發加載,而不是在初始化的時候就加載資訊
例如:呼叫 a. getB(). getName(),這個時候發現 a. getB() 的值為 null,此時會單獨觸發事先保存好的關聯 B 物件的 SQL,先查詢出來 B,然后再呼叫 a. setB(b),而這時候再呼叫 a. getB(). getName() 就有值了
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549399.html
標籤:其他
