基础
(1)一个持久层框架
(2)核心是输入映射和输出映射
1.输入映射:向PreparedStatement中输入参数
2.输出映射:将查询结果集自动映射为java对象
(3)架构:
1.sqlMapConfig:mybatis全局配置文件,配置数据源、事物等mybatis运行环境,配置映射文件、mapper.xml........
2.sqlSessionFactory:创建sqlSession回话
3.sqlSession :操作数据库
4.Executor执行器:sqlSession通过Executor来操作数据库
5.mapped statement(底层封装对象):对数据库存储进行封装,包括sql语句,输入参数,输出结果类型
配置
springboot中的配置 其实如果基本使用甚至一跳都不用配置 又一次体现了springboot的强大
一般经常使用:
1.mybatis.configuration.mapUnderscoreToCamelCase=true 可以自动将数据库中的下划线转换成驼峰式命名
2.logging.level.com.spring.jiminshiro.mapper=debug 可以在控制台打印出生成的sql语句
configuration所有配置如下:
设置参数 | 描述 | 有效值 | 默认值 |
cacheEnabled | 这个配置使全局的映射器启用或禁用缓存。 | true | false | true |
lazyLoadingEnabled | 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 | true | false | true |
aggressiveLazyLoading | 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 | true | false | true |
multipleResultSetsEnabled | 允许或不允许多种结果集从一个单独的语句中返回(需要适合的驱动)。 | true | false | true |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方便表现不同。参考驱动文档或充分测试两种方法来决定所使用的驱动。 | true | false | true |
useGeneratedKeys | 允许JDBC支持生成的键。需要适合的驱动。如果设置为true则这个设置强制生成的键被使用,尽管一些驱动拒绝兼容但仍然有效(比如Derby)。 | true | false | false |
autoMappingBehavior | 指定MyBatis如何自动映射列到字段/属性。PARTIAL只会自动映射简单,没有嵌套的结果。FULL会自动映射任意复杂的结果(嵌套的或其他情况)。 | NONE, PARTIAL, FULL | PARTIAL |
动态查询
mybatis相较于现在流行的其他ORM框架最主要的一点就是完全自己编写sql语句 可以在某些方面可能会有些复杂 但也大大增加了其灵活性
mybatis支持一下几种动态判断
if
choose (when, otherwise)
trim (where, set)
foreach
实例:
@Select("<script>" +
"select"+TAB_PARMER+
"from"+TAB+
"where " +
"id != 'null'" +
"<if test='employee.staffId != null'>and staff_id=#{employee.staffId}</if>" +
"<if test='employee.name != null'>and name=#{employee.name} </if>" +
"<if test='employee.mobile != null'>and mobile=#{employee.mobile} </if>" +
"<if test='employee.area != null'>and area=#{employee.area} </if>" +
"<if test='employee.gender != null'>and gender=#{employee.gender} </if>" +
"<if test='employee.isValid != null'>and is_valid=#{employee.isValid}</if>" +
"</script>")
<WHERE> 与 WHERE 的区别 where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。若语句的开头为“AND”或“OR”,where 元素也会将它们去除。
<SET> 若语句的结尾为逗号,set 元素会将它们去除。
缓存
Mybatis中自带了缓存机制 将数据缓存设计成两级结构,分为一级缓存、二级缓存:
一级缓存
一级缓存是Session会话级别的缓存,位于表示一次数据库会话的SqlSession对象之中,又被称之为本地缓存 . 一个SqlSession对象会使用一个Executor对象来完成会话操作,Executor对象会维护一个Cache缓存,以提高查询性能。一级缓存是MyBatis内部实现的一个特性,用户不能配置,默认情况下自动支持的缓存,用户没有定制它的权利(不过这也不是绝对的,可以通过开发插件对它进行修改)
在查询后自动进行缓存 如果之后完全相同的查询会直接去缓存中查询 命中缓存会大大增加查询速度.但当执行SQL查询中间发生了增删改的操作,MyBatis会把SqlSession的缓存清空,防止脏数据.
一级缓存的范围有SESSION和STATEMENT两种,默认是SESSION,如果不想使用一级缓存,可以把一级缓存的范围指定为STATEMENT,这样每次执行完一个Mapper中的语句后都会将一级缓存清除。
如果需要更改一级缓存的范围,可以在Mybatis的配置文件中,通过localCacheScope指定。
<setting name="localCacheScope" value="STATEMENT"/>
一般建议初级玩家不要修改.
需要注意的是 当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中每个Mapper的每次查询操作都对应一个全新的SqlSession实例,这个时候就不会有一级缓存的命中,但是在同一个事务中时共用的是同一个SqlSession。
二级缓存
二级缓存是Application应用级别的缓存,它的是生命周期很长,跟Application的声明周期一样,也就是说它的作用范围是整个Application应用。
一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
二级缓存配置
要正确的使用二级缓存,需完成如下配置的。
- 在MyBatis的配置文件中开启二级缓存。
<setting name="cacheEnabled" value="true"/>
- 在MyBatis的映射XML中配置cache或者 cache-ref 。
cache标签用于声明这个namespace使用二级缓存,并且可以自定义配置。
<cache/>
- type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
- eviction: 定义回收的策略,常见的有FIFO,LRU。
- flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
- size: 最多缓存对象的个数。
- readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
- blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。
<cache-ref namespace="mapper.StudentMapper"/>
P.s. 对于缓存推荐阅读 美团技术团队的博客
Sql注入方式
注解方式
String TABLE_LEAVE = "id,staff_id,start_date,end_date,day_num,type,area";
String TABLE_LEAVE_PARAM = "#{id},#{staffId},#{startDate},#{endDate},#{dayNum},#{type},#{area}";
String TABLE_EMPLOYEE = "id,staff_id,name,mobile,area,gender,is_valid";
/**
* 通过员工号查询 Employee been
*
* @param staffId 员工号
* @return Employee been
*/
@Select({"SELECT ", TABLE_EMPLOYEE, " FROM employee WHERE staff_id = #{staff_id}"})
Employee findByEmployeeStaffId(@Param("staff_id") Long staffId);
/**
* 插入请假记录
* @param leaveHoliday leaveHoliday been
* @return 1:T 0:F
*/
@Insert({"INSERT INTO leave_holiday( ", TABLE_LEAVE, " ) VALUES( ", TABLE_LEAVE_PARAM, " )"})
Integer addLEAVE(LeaveHoliday leaveHoliday);
可以很方便的插入简单的sql语句, 加在方法上易于在代码编写过程中的使用. 可以用{ ... }的形式在其中使用 逗号 的形式拼接字符串 而不用使用加号的方式,并且也支持动态sql语句
XML方式
<mapper namespace="com.qunar.counter.dao.ContentDao">
<sql id="all_fields">
title AS title,
number AS number,
ch_number AS chNumber,
en_number AS enNumber,
punc_number AS puncNumber,
num_number AS numNumber,
link AS link
</sql>
<insert id="insert" parameterType="ContentModel">
INSERT
INTO
content
(
title,
number,
ch_number,
link
)
VALUES (
#{title},
#{number},
#{chNumber},
#{link}
)
</insert>
<select id="query" resultType="ContentModel">
SELECT
<include refid="all_fields"/>
FROM
content
order by id DESC
limit #{pageNum},#{listNum}
</select>
<select id="count" resultType="Integer">
SELECT
count(*)
FROM
content
</select>
</mapper>
在XML中进行配置sql 很方便的使用动态拼接语句 便于后期的维护
Provider方式
public class EmployeeProvider {
public String insertEmployee(final Employee employee) {
return new SQL() {
{
INSERT_INTO("employee");
if (employee.getStaffId() != null) {
VALUES("staff_id", "#{staffId}");
}
if (employee.getName() != null) {
VALUES("name", "#{name}");
}
if (employee.getMobile() != null) {
VALUES("mobile", "#{mobile}");
}
if (employee.getArea() != null) {
VALUES("area", "#{area}");
}
if (employee.getGender() != null) {
VALUES("gender", "#{gender}");
}
if (employee.getIsValid() != null) {
VALUES("is_valid", "#{isValid}");
}
}
}.toString();
}
}
构建Provider类及其方法 使用java拼接的方式来构建动态sql语句 支持断点Debug 个人认为在复杂sql的情况下适合使用
/**
* 插入员工信息
* @param employee 员工信息
* @return 1:T 0:F
*/
@InsertProvider(type = EmployeeProvider.class, method = "insertEmployee")
@Options(useGeneratedKeys = true)
Integer addEmployee(Employee employee);
调用方法:加@InsertProvider注解就可以调用 (@Options注解是为了让自增的id自动返回补全 方便单元测试断言)
比较
那这三种方法哪种最好呢? 答案是适合的就是最好的 简单的增删改查直接使用注解就可以 如果项目后期维护较多可以考虑使用XML的方式
而动态sql方面Provider非常好用
但哪种方式最快呢?
2018-08-08 20:46:26.823 INFO 4476 --- [main] c.q.mybatistest.mybatis.bean.BeanTest : Provider run 4120ms
2018-08-08 20:46:54.429 INFO 10732 --- [main] c.q.mybatistest.mybatis.bean.BeanTest : annotation run 6286ms
2018-08-08 20:47:39.526 INFO 10336 --- [main] c.q.mybatistest.mybatis.bean.BeanTest : XML run 7549ms
都查询100W条记录 对其进行测试 发现provider的方式最快比其他两种方式快三分之一左右,而注解比XML快一点. p.s.使用默认的一级缓存