Mybatis:缓存,动态SQL,注解SQL以及动态标签使用
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
需要在开头以及结尾添加