mybatis是什么
ORM-对象关系数据库映射
Mybatis是⼀个半⾃动化的持久层框架,sql映射框架。
使用
配置文件
SqlMapConfig
最重要是事务管理器和数据源的配置
mapper.xml
简单示例:
<mapper namespace="com.lagou.mapper.OrderMapper">
<resultMap id="orderMap" type="com.lagou.domain.Order">
<result column="uid" property="user.id"></result>
<result column="username" property="user.username"></result>
<result column="password" property="user.password"></result>
<result column="birthday" property="user.birthday"></result>
</resultMap>
<select id="findAll" resultMap="orderMap">
select * from orders o,user u where o.uid=u.id
</select>
</mapper>
详细示例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
//namespace:命名空间 与mapper.java文件名对应
<mapper namespace="com.sinosoft.mapper.AccOpenBankMapper">
//resultMap 封装结果集用于将查询的结果映射到实体类,Mybatis最强大的功能。
//可以省略配置的情况:实体类中属性没有其他实体类的引用,全是Java自带的类,并且属性名与数据库字段名一样
//适用情况: 1.实体类的属性名与数据库字段名不同
2.实体类中属性有其他实体类的引用,即关联关系
<resultMap id="BaseResultMap" type="com.sinosoft.entity.AccOpenBank">
<id column="BANKCODE" jdbcType="VARCHAR" property="BANKCODE" />
<result column="BANKNAME" jdbcType="VARCHAR" property="bankName" />
<result column="CREATEDATE" jdbcType="DATE" property="createDate" />
</resultMap>
//select 这类标签对应数据库的四种基本语句:select,delete,update,insert
//id:与Mapper.java文件中的抽象方法名相同
//parameterType:可以省略(经测试,1.java基本类型的封装类,2.Date,String这样的Java自带类,3.只包含1和2的属性的实体类 是没有问题的)
//resultType:返回的结果集
<select id="qryAccOpenBankByMybatis" parameterType="com.sinosoft.dto.AccOpenBankDTO" resultType="com.sinosoft.dto.AccOpenBankDTO">
select
bankCode||bankName bankCode,
//include:引用其他SQL语句块
<include refid="queryCondition"/>
<include refid="orderStatement"/>
</select>
//SQL:提取出公共的SQL语句块并定义ID,用include标签通过ID引用
<sql id="queryCondition">
//where:相当于SQL语句中的where
<where>
//相当于SQL语句中的if,可以去重(针对and和or)
//test:这个属性比较坑
//bankCode!=null 不为null
//bankCode!='' 不等于空字符串
//and
//or
//集合.size 集合的长度
//bankCode=='yyy'.toString() 不支持bankCode=='yyy'这种写法 但是 bankCode==12是可以的,虽然bankCode是个字符创 于字符串比较
<if test="bankCode!=null and bankCode!=''">
//一些超长的SQL语句可能报错,<![CDATA[ ]]>可以表示转化成文本,可以处理这个错误
// ||用于连接表达式和字符串成一个整体
<![CDATA[ and bankCode like '%'||#{bankCode}||'%' ]]>
</if>
<if test="bankName!=null and bankName!=''">
<![CDATA[ and bankName like '%'||#{bankName}||'%' ]]>
</if>
</where>
</sql>
<sql> //foreach:便利集合用的标签
//item:遍历集合时,每次集合中的元素的变量名
//index:循环的下标
//collection:最奇葩的是这个
//方法中只有一个集合参数,无论它的变量名是什么,都用list。但当它是作为一个对象的属性传入时就可以用属性名调用。真无语...
//open="(" :以"("开始
//separator="," : 分隔符是","
//close=")" : 以")"结束
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</sql>
<sql id="orderStatement">
<if test="sort!= null and sort!=''">
order by ${sort} ${order}
</if>
</sql>
<select id="findById" parameterType="String" resultType="com.sinosoft.dto.AccOpenBankDTO">
//* 最好不要使用:1.sql执行效率问题 2.返回值可能绑定的有问题
select * from accOpenBank where bankCode = #{bankCode}
</select>
</mapper>
resultMap
关联关系
一对一
<resultMap id="orderMap" type="com.lagou.domain.Order">
<result property="id" column="id"></result>
<result property="ordertime" column="ordertime"></result>
<result property="total" column="total"></result>
<association property="user" javaType="com.lagou.domain.User">
<result column="uid" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
一对多
<resultMap id="userMap" type="com.lagou.domain.User">
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<collection property="orderList" ofType="com.lagou.domain.Order">
<result column="oid" property="id"></result>
<result column="ordertime" property="ordertime"></result>
<result column="total" property="total"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select *,o.id oid from user u left join orders o on u.id=o.uid
</select>
多对多
<resultMap id="userRoleMap" type="com.lagou.domain.User">
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<collection property="roleList" ofType="com.lagou.domain.Role">
<result column="rid" property="id"></result>
<result column="rolename" property="rolename"></result>
</collection>
</resultMap>
<select id="findAllUserAndRole" resultMap="userRoleMap">
select u.*,r.*,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id
</select>
sql
占位符
- #{} 预编译,防止sql注入
- ${} 字符串连接,不能防sql注入
动态sql
- if
- choose , when, otherwise
- trim, where, set
- foreach
原理
执行
- SqlResource
该接⼝含义是作为sql对象的来源,通过该接⼝可以获取sql对象。其唯⼀的实现类是XmlSqlResource,表示通过xml⽂件⽣成sql对象。 - Sql
该接⼝可以⽣成sql语句和获取sql相关的上下⽂环境(如ParameterMap、 ResultMap等),有三个实现类: RawSql表示为原⽣的sql语句,在初始化即可确定sql语句; SimpleDynamicSql表示简单的动态sql,即sql语句中参数通过 p r o p e r t y property property⽅式指定,参数在sql⽣成过程中会被替换,不作为sql执⾏参数; DynamicSql表示动态sql,即sql描述⽂件中包含isNotNull、 isGreaterThan等条件标签。 - SqlChild
该接⼝表示sql抽象语法树的⼀个节点,包含sql语句的⽚段信息。该接⼝有两个实现类: SqlTag表示动态sql⽚段,即配置⽂件中的⼀个动态标签,内含动态sql属性值(如prepend、 property值等); SqlText表示静态sql⽚段,即为原⽣的sql语句。每条动态sql通过SqlTag和SqlText构成相应的抽象语法树。 - SqlTagHandler
该接⼝表示SqlTag(即不同的动态标签)对应的处理⽅式。⽐如实现类IsEmptyTagHandler⽤于处理标签, IsEqualTagHandler⽤于处理标签等。 - SqlTagContext
⽤于解释sql抽象语法树时使⽤的上下⽂环境。通过解释语法树每个节点,将⽣成的sql存⼊SqlTagContext。最终通过SqlTagContext获取完整的sql语句。
设计模式
解释器模式: 初始化过程中构建出抽象语法树,请求处理时根据参数对象解释语法树,⽣成sql语句。
⼯⼚模式: 为动态标签的处理⽅式创建⼯⼚类(SqlTagHandlerFactory),根据标签名称获取对应的处理⽅式。
策略模式: 将动态标签处理⽅式抽象为接⼝,针对不同标签有相应的实现类。解释抽象语法树时,定义统⼀的解释流程,再调⽤标签对应的处理⽅式完成解释中的各个⼦环节。
mapper方法不能重载
原因:mapper接口的方法 -> xml方法 接⼝全限名+⽅法名 定位到 MappedStatement
缓存
一级缓存
- 默认开启
- 存储结构:hashMap
- 范围:SqlSession
- 判断相同:
- statementId
- sql(boundSql.getSql())
- 参数
- 结果集中的结果范围
- 失效场景:
- SqlSession.close()
- SqlSession.clearCache()
- SqlSession中执行了更新操作(insert,delete,update)
问题
- 多个mapper是否有问题,需确认
- 查出后修改值,再查是否是修改后的值,id:1->2,需确认
- 注意事务隔离级别,分布式环境下,读完有缓存,另一个session更新了数据库,再读是取得缓存,内容是更新前的。如果是读已提交,则完全没有问题。如果是可重复读,则需要看业务场景。
二级缓存
默认不开启
- 存储结构:hashMap
- 范围:mapper.namespace,是跨sqlSession的
- 失效场景:
- 被缓存淘汰策略淘汰
二级缓存的缺点
namespace的缓存范围,如果一个表有多个mapper.xml文件,就会导致数据不一致的问题
一般我们在需要跨sqlsession范围使用缓存,就考虑使用缓存中间件了,比如redis
二级缓存整合redis
延迟加载
延迟加载是什么
一对一,一对多,多对多的关联关系中,对主表的查询时不一起查询关联表,等待需要的时候再查询。
延迟加载使用
<settings>
<setting name="lazyLoadingEnabled" value="true"/><!-- 开启延迟加载,默认为false:不开启 -->
<setting name="aggressiveLazyLoading" value="false"></setting><!-- 侵略式延迟加载,默认为true:开启 -->
</settings>
<mapper namespace="cn.xh.dao.UserDao">
<resultMap id="categoryMap" type="cn.xh.pojo.Category">
<id column="cid" property="cid"></id>
<result column="cname" property="cname"></result>
<collection property="books" column="cid" select="findBookWithLazy"></collection>
</resultMap>
<select id="findCategoryWithLazingload" resultMap="categoryMap">
select * from category
</select>
<select id="findBookWithLazy" parameterType="int" resultType="cn.xh.pojo.Book">
select * from book where cid = #{cid}
</select>
</mapper>
延迟加载的方式
- 直接加载
- 侵入式延迟加载:执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情属性时,就会马上执行关联对象的select查询。
- 执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的 select 查询。
Mybatis与Hibernate的对比
综述
- Hibernate 是全自动完全的ORM框架,对数据库结构提供了较为完整的封装
- mybatis 是半自动轻量级ORM框架,主要是sql和pojo的映射
开发难度和学习成本
Hibernate 单表操作简单,复杂操作(对象的状态管理,关联关系映射,hql,动态查询,sql优化)难,整体学习成本高
mybatis 有插件或者mybatis plus支持下,单表操作简单,复杂操作也不难,整体学习成本低
缓存
Hibernate 缓存比较完善
mybatis 不太行
插件
- 开源框架都会提供插件或其他形式的拓展点,供开发者⾃⾏拓展。
- MyBatis提供的是插件机制,即4大组件的拦截器
- 可以实现分⻚、分表,监控等功能
- 常用插件:分页插件,通用Mapper插件
可以拦截的方法
- 执⾏器Executor (update、 query、 commit、 rollback等⽅法);
- SQL语法构建器StatementHandler (prepare、 parameterize、 batch、 updates query等⽅ 法);
- 参数处理器ParameterHandler (getParameterObject、 setParameters⽅法);
- 结果集处理器ResultSetHandler (handleResultSets、 handleOutputParameters等⽅法);
使用
- 继承Interceptor接口,并注解声明器连接的类,方法等
- 配置到sqlMapConfig.xml中
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
)
})
public class ExeunplePlugin implements Interceptor {
//省略逻辑
}
<plugins>
<plugin interceptor="com.lagou.plugin.ExamplePlugin">
</plugin>
</plugins>