Mybatis:緩存,動態SQL,注解SQL以及動態標簽使用


1 轉義字符

字符 轉義 描述
< &lt; 小於
<= &lt;= 小於等於
> &gt; 大於
>= &gt;= 大於等於
<> &lt;&gt; 不等於
& &amp;
' &apos;
" &quot;

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接口實現,有一定的開發成本,直接使用RedisMemcached等分布式緩存可能成本更低安全性也更高。

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需要在開頭以及結尾添加


免責聲明!

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



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