理解MyBatis及其使用

  作为一款优秀那的持久层框架,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 的底层和实际应用的联系.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值