1 轉義字符
| 字符 | 轉義 | 描述 |
|---|---|---|
| < | < |
小於 |
| <= | <= |
小於等於 |
| > | > |
大於 |
| >= | >= |
大於等於 |
| <> | <> |
不等於 |
| & | & |
|
| ' | ' |
|
| " | " |
2 一級緩存以及二級緩存
學習Mybatis緩存的過程中,發現一篇美團的優秀文章: 聊聊MyBatis緩存機制.
此處對一級緩存以及二級緩存的使用進行總結.
2.1 一級緩存
2.1.1 小結
(1) MyBatis一級緩存的生命周期和SqlSession一致;
(2) MyBatis一級緩存內部設計簡單,只是一個沒有容量限定的HashMap,在緩存的功能性上有所欠缺;
(3) MyBatis的一級緩存最大范圍是SqlSession內部,有多個SqlSession或者分布式的環境下,數據庫寫操作會引起臟數據,建議設定緩存級別為Statement.
2.1.2 一級緩存臟讀現象
(1) 在使用MyBatis的項目中,被@Transactional注解的函數中,一個單獨的SqlSession對象將會被創建和使用,所有數據庫操作會共用這個sqlSession,當事務完成時,這個``sqlSession會以合適的方式提交或回滾; (2) SELECT語句默認開啟查詢緩存,並且不清除緩存,所以使用同一個 SqlSession 多次用相同的條件查詢數據庫時,只有第一次真實訪問數據庫,后面的查詢都直接讀取緩存返回; (3) 此時,如果其余SqlSession更新了帶待查詢數據,就會造成臟讀現象; (4) 或者同一次SqlSession中多次查詢數據`,例如多次查詢分表數據(查詢結果和分表查詢數據相關),就會造成查詢結果失效.
2.2 二級緩存
(1) MyBatis的二級緩存相對於一級緩存來說,實現了SqlSession之間緩存數據的共享,同時粒度更加的細,能夠到namespace級別,通過Cache接口實現類不同的組合,對Cache的可控性也更強。
(2) MyBatis在多表查詢時,極大可能會出現臟數據,有設計上的缺陷,安全使用二級緩存的條件比較苛刻。
(3) 在分布式環境下,由於默認的MyBatis Cache實現都是基於本地的,分布式環境下必然會出現讀取到臟數據,需要使用集中式緩存將MyBatis的Cache接口實現,有一定的開發成本,直接使用Redis、Memcached等分布式緩存可能成本更低,安全性也更高。
3 Myabtis枚舉值轉換與駝峰轉換配置
3.1 枚舉值轉換配置
mybatis.configuration.default-enum-type-handler = org.apache.ibatis.type.EnumOrdinalTypeHandler
定義如上配置,則Mybatis存儲枚舉值時,添加/更新枚舉值轉換為Integer,查詢時會自動將Integer轉換為相應的枚舉值.
3.2 駝峰轉換配置
mybatis.configuration.map-underscore-to-camel-case = true
4.動態注解SQL
4.1 查詢
4.1.1 單條查詢
@Select("SELECT * FROM `attachment` WHERE `id` = #{id}")
Attachment getAttachment(@Param("id") Integer id);
4.1.2 列表查詢
此處為了避免在注解的動態SQL中寫foreach,使用輔助類完成字符串替換的工作.
查詢出的結果Mybatis會自動將結果映射到返回值上,支持批量查詢結果.
@Lang(SimpleSelectInExtendedLanguageDriver.class)
@Select("SELECT id, created_time FROM `change` WHERE id in (#{ids})")
List<Change> getChangeTimeInfo(@Param("ids") List<Integer> ids);
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SimpleSelectInExtendedLanguageDriver extends XMLLanguageDriver implements LanguageDriver {
private final Pattern inPattern = Pattern.compile("\\(#\\{(\\w+)\\}\\)");
@Override
public SqlSource createSqlSource(Configuration configuration,
String script, Class<?> parameterType) {
Matcher matcher = inPattern.matcher(script);
if (matcher.find()) {
script = matcher.replaceAll(
"(<foreach collection=\"$1\" item=\"__item\" separator=\",\" >#{__item}</foreach>)");
}
script = "<script>" + script + "</script>";
return super.createSqlSource(configuration, script, parameterType);
}
}
4.2 插入數據
以下為示例使用的實體類:
import lombok.Data;
@Data
public class TaskTreeItem {
private Integer id;
/**
* 當前節點的祖先
*/
private Integer ancestor;
//
/**
* 當前節點
*/
private Integer descendant;
}
4.2.1 單條插入
單條插入數據,如果需要返回生成的主鍵值,可以設置useGeneratedKeys 為true,指定id為返回的主鍵值.
@Insert("INSERT INTO task_tree (ancestor,descendant,depth) VALUES (#{ancestor},#{descendant})")
@Options(useGeneratedKeys = true, keyColumn = "id")
int insert(TaskTreeItem taskTreeItem);
4.2.2 批量插入
批量插入需要寫動態SQL(此外需要確保數據庫支持),此處使用到<foreach>.
@Insert({"<script>",
"INSERT INTO `task_tree` (`ancestor`, `descendant`) VALUES ",
"<foreach item='item' index='index' collection='list' open='' separator=',' close=''>",
"(#{item.ancestor}, #{item.descendant})",
"</foreach>",
"</script>"})
Integer addTreeItems(List<TaskTreeItem> taskTreeItems);
4.3 更新數據
4.3.1 簡單更新
@Update("UPDATE `change` SET `status` = #{status}, `finish_time` = #{finishTime} WHERE `id` = #{id}")
int finishChange(@Param("id") int id, @Param("status") Change.Status status, Instant finishTime);
在不涉及變量判斷的情況下,通過@Param注解將參數映射到SQL語句中,即可實現數據的更新.
4.3.2 含邏輯判斷更新
業務的數據表更新中,經常含有復雜的判斷(諸如非空判斷,時間戳比較等判斷),因此在此處進行展示操作.
(業務場景中,在CRUD的各個環節均可能存在邏輯判斷,此處只是節選作為說明).
@Update("<script>" +
"UPDATE `change` " +
"<set>" +
"<if test=\"description != null\">" +
" description = #{description}," +
"</if> " +
"<if test=\"executePlan != null\">" +
" execute_plan = #{executePlan}," +
"</if> " +
"<if test=\"status != null\">" +
" status = #{status}," +
"</if> " +
" id = #{id}, " +
"</set>" +
" WHERE id = #{id}" +
"</script>")
int updateChange(Change change);
在注解中使用動態SQL要比在XML中使用困難,主要在於維護字符串拼接以及字符串格式化.
動態SQL需要在開頭以及結尾添加標簽,告知MyBatis當前語句為動態SQL.
此處,id = #{id},一行,這是為了避免所有屬性均為空時, SQL語句不規范導致業務執行異常.
4.4 刪除數據
@Delete("DELETE FROM milestone where id = #{milestoneId} AND task_id = #{taskId}")
int deleteTaskMilestone(@Param("taskId") int taskId, @Param("milestoneId") int milestoneId);
刪除數據較為簡單,此處不再贅述.
5 標簽
此處列舉經常使用的標簽作為記錄.
5.1
@Select("<script>" +
"<if test=\"incidentId != null\">" +
"(SELECT * FROM `change` WHERE `id` IN (SELECT `change_id` FROM `incident_change` WHERE `incident_id` = #{incidentId}) ORDER BY `id` DESC)" +
" UNION " +
"</if> " +
"(SELECT * FROM `change` " +
"<where>" +
"<if test=\"status != null\">" +
"AND `status` = #{status} " +
"</if>" +
"<if test=\"description != null and description != ''\">" +
"AND `description` LIKE CONCAT('%', #{description}, '%') " +
"</if>" +
"</where>" +
"ORDER BY `id` DESC)" +
"</script>")
List<Change> listChangeByCondition(ChangeQueryRequest request);
5.2 與
@Select("<script>" +
"SELECT task.*,content_text.content 'description' FROM `task` " +
" JOIN content_text ON task.description_id = content_text.`id` " +
" WHERE " +
"<choose>" +
"<when test=\"id != null\">" +
" task.id = #{id}" +
"</when>" +
"<otherwise>" +
" task.id IN (SELECT `task_id` FROM `key_object_task`)" +
"</otherwise>" +
"</choose>" +
"<if test=\"type != null\">" +
"AND task.type = #{type} " +
"</if>" +
"<if test=\"statuses != null and statuses.size()>0\">" +
"<foreach item='item' index='index' collection='statuses' open=' AND task.status IN (' separator=',' close=')' >" +
"#{item}" +
"</foreach>"+
"</if>" +
" ORDER BY task.id DESC" +
"</script>")
List<TaskResponse> listKeyObjectTask(KeyObjectTaskQueryRequest taskQueryRequest);
此處,可以注意:
(1) 查詢參數中包含statuses參數,其實質上是一個List<Enum>,此處使用foreach標簽就需要指明參數名稱,而不是使用list代替;
(2) TaskResponse類中包含一個description參數(參數對應content_text.content字段),因此使用content_text.content 'description'指明查詢結果的映射字段.
PS:
如果您覺得我的文章對您有幫助,可以掃碼領取下紅包或掃碼支持(隨意多少,一分錢都是愛),謝謝!
| 支付寶紅包 | 支付寶 | 微信 |
|---|---|---|
![]() |
![]() |
![]() |



