作为一款优秀那的持久层框架,Mybatis在实际开发中拥有着广泛的应用。 因为之前负责的项目起源比较早(08年开始做,现在还在不断更新维护),是使用JDBCtemplate的形式来实现持久层,项目自己完成了 字段与类中成员变量的映射关系(贼复杂,可读性也特别差)。所以在新开发的sms(短信)系统中,果断应用了springMVC+myBatis 的技术选型。因为之前对其就有过一些的应用经验,现在用起来也还算得上是得心应手,不过其中一些机制还不太清楚,正好趁这个周末来进行一下理解和梳理,以便更好的应用在工作中。
之前接手这个项目的时候,大概就只有一个架子和不几个功能。开发到今天已经算是基本完成了业务需求。记得同事离职的时候把项目扔给我,当时一下接手了七八个项目的开发与维护工作(光解决业务问题就缠了我一段时间),很久都没来得及看测试环境。导致在接手的时候 测试环境都跑不起来。每次访问的时候都会报500,日志提示是spring 的Enviroment 错误。 找这个问题找了很久,包括联系运维大哥帮忙,后来找到了不知道哪个大哥写mapper文件的时候写错了一条sql,而且把mapper文件的schemal给改了,导致整个环境跑不起来,然后他写完还没上线(线上环境是好的,测试没办法用,给我气的)。
先说先mapper文件吧,这可能是mybatis比较精华的地方了。如果可以看懂mapper文件 并找到dao层相绑定的接口,那么就可以很快的复现一条sql的具体内容和由来。我们做java的,大部分时候都要与CRUD打交道,所以了解sql的执行是至关重要的。比如一个mapper文件中 sql的id 是updateStatus 那么 他的内容大致就是 update from xxxtable set id = x where id =x; 那么你只需找到相应的dao层, 并找到其应用的地方(这里推以下idea的快捷键 ctrl+g find usage 找到用处) 就可以复现一条sql的使用场景了。 在对别人代码修改的时候非常有用。 那么mapper文件是如何与DAO进行绑定的? mapper文件又是怎么通过接口名来找到 与之对应的sql呢?
简单点来说,在每个mapper文件的顶部,都会有命名空间(namespace)与之对应的包的全限定名,用于将mapper与dao一一对应起来,在spring中会配置一个扫描,将扫描其路径下的所有mapper . 而这个id 就会与dao层方法的方法名一一对应. spring会为其生成代理,最终在调用dao层方法的时候使用mapper文件中的sql语句. 在mapper文件的开头也会将 列名与对应成员变量对应起来, 例如:
<mapper namespace="com.xxx.xx.xxx.DepartmentDao">
<resultMap id="BaseResultMap" type="com.dajie.sms.bean.Department">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="dept_name" property="deptName" jdbcType="VARCHAR"/>
<result column="dept_leader" property="deptLeader" jdbcType="VARCHAR"/>
<result column="parent_id" property="parentId" jdbcType="INTEGER"/>
<result column="status" property="status" jdbcType="INTEGER"/>
<result column="operator_id" property="operatorId" jdbcType="VARCHAR"/>
<result column="operator_name" property="operatorName" jdbcType="VARCHAR"/>
<result column="distribute_sums" property="distributeSums" jdbcType="INTEGER"/>
<result column="is_defined" property="isDefined" jdbcType="INTEGER"/>
<result column="tpl_id" property="tplId" jdbcType="INTEGER"/>
<result column="remark" property="remark" jdbcType="VARCHAR"/>
<result column="last_operator_id" property="lastOperatorId" jdbcType="VARCHAR"/>
<result column="last_operator_name" property="lastOperatorName" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="template_name" property="templateName" jdbcType="VARCHAR"/>
</resultMap>
之前看到别人遇到过column 与 成员变量名对应不上的问题. 解决这种问题的方式就是 通常可以在sql中为列增加别名,来对应映射关系.(一般来说,开发的时候操点心就把列和属性都对上是不会出现这种事情的), MyBatis 会通过反射机制来进行赋值.
而MyBatis的另一个好处就是可以有开发人员来自定义动态SQL. 动态sql的实现主要是通过trim .if ,foreach ,choose ,when 来实现的. 与开发发中的语义相近, if 通常用来对成员变量进行值的判断, 如果存在则会将标签的内容拼接到SQL中 . foreach 则会对其循环取值, choose 会根据条件来选取标签 和when 组合使用 下面是几个实际应用
<select id="getDepts" parameterType="list" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from tb_sms_department
where id in
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item}
</foreach>
<choose>
<when test="createRange!=null and createRange==0">
order by create_time desc
</when>
<when test="createRange!=null and createRange==1">
order by create_time
</when>
<when test="updateRange!=null and updateRange==0">
order by update_time desc
</when>
<when test="updateRange!=null and updateRange==1">
order by update_time
</when>
</choose>
另外还想再说一下防止SQL注入的一些注意事项.
1.Mybatis采用了 预编译的形式,在拼接sql前 使用? 占位即可. 而底层则是使用了我们非常熟悉的prepareStatement, 我们都知道这是一个Statment 的子类,也是JDBC连接数据库的七大步骤之一. 使用预编译SQL会在一定程度上减小SQL注入攻击的概率
2.在取值的时候 使用#{} 代替 ${} . ${} 这种形式会直接参与编译,
3. 进行模糊查询的时候 利用concat 对模糊条件进行拼接 而不是直接 '%name%' 这种形式
暂时知道这么多
MyBatis缓存
MyBatis 缓存分为两个级别, 第一级别存放在sqlSession中, 默认是开启的.
二级缓存默认使用内存对象【PerpetualCache】内部维护一个HashMap对象来存储,如果需要开启 可以在配置文件中添加一行<cache> 的配置, 是mapper级别的缓存,可以接入memecache 或者redis之类的第三方缓存.(一般来说都不开二级缓存吧)
mybatis的二级缓存主要是在Executor对象上来做文章,当mybatis发现你在mybatis.xml配置文件中设置了cacheEnabled=true时,mybatis在创建sqlsession时创建Executor对象,同时会对Executor加上装饰者【CacheExecutor】。CacheExecutor对于查询请求,会判断application级别的二级缓存是否有缓存结果,如果有查询结果则直接返回,如果没有再交给查询器Executor实现类,也就是【SimpleExecutor】来执行查询。再就是缓存结果,返回给用户 . 开启二级缓存的话 很容易使得查询到的值不是最新的(是从缓存中取的,而不是从库中),这点在日常开发中需要注意一下.
<configuration>
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="User" type="com.autohome.model.User" />
</typeAliases>
<mappers>
<mapper resource="mapper/UserMapper.xml" />
</mappers>
</configuration>
MyBatis 一对一 一对多.
在很多业务场景中会存在这样的对应关系,例如 , 一个班只能有一个数学老师,一个数学老师也教一个班(假设) 老师可以有N的学生, 学生也 可以有N个老师
在MyBatis中处理 这种关系 有两种方案.
一种是:使用嵌套结果映射来处理重复的联合结果的子集
另一种是:通过执行另外一个SQL映射语句来返回预期的复杂类型
<mapper namespace="com.yc.mybatis.test.classMapper">
<!--
方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
封装联表查询的数据(去除重复的数据)
select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=1
-->
<select id="getClass" parameterType="int" resultMap="getClassMap">
select * from class c, teacher t where c.teacher_id = t.t_id and c.teacher_id=#{id}
</select>
<!-- resultMap:映射实体类和字段之间的一一对应的关系 -->
<resultMap type="Classes" id="getClassMap">
<id property="id" column="c_id"/>
<result property="name" column="c_name"/>
<association property="teacher" javaType="Teacher">
<id property="id" column="t_id"/>
<result property="name" column="t_name"/>
</association>
</resultMap>
太忙就先写到这里,周末的时候写一下有关MyBatis 的底层和实际应用的联系.