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 单条插入

单条插入数据,如果需要返回生成的主键值,可以设置useGeneratedKeystrue,指定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:
如果您觉得我的文章对您有帮助,可以扫码领取下红包或扫码支持(随意多少,一分钱都是爱),谢谢!

支付宝红包支付宝微信
764719-20190624102014468-193165117.png764719-20190624102025867-1690450943.png764719-20190624101626318-627523959.png

转载于:https://www.cnblogs.com/jason1990/p/10550744.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值