Java EE开发平台随手记5——Mybatis动态代理接口方式的原生用法

  为了说明后续的Mybatis扩展,插播一篇广告,先来简要说明一下Mybatis的一种原生用法,不过先声明:下面说的只是Mybatis的其中一种用法,如需要更深入了解Mybatis,请参考官方文档,或者研读源码。  

  我们知道,使用Mybatis的方式有很多种,从是否集成上分,可以单独使用,也可以和Spring集成使用;从使用方式上分,可以编写静态工具类,在静态工具中调用SqlSession,也可以直接注入SqlSession/ SqlSessionTemplate,还可以编写Dao接口,让mybatis自动生成代理子类(对于只编写接口,不写实现类就可以运行有疑问的朋友,可以先了解一下JDK中的动态代理技术,这里就不展开了);在sql脚本的编写上,可以使用xml——也就是通常所说的SqlMapper,可以使用注解,还可以使用Mybatis提供的API生成等等。

  根据新平台运行环境和之前同事的开发习惯,我们的选择组合是(姑且称之为动态代理接口方式或代理接口):集成Spring + Dao接口/自动动态代理子类 + SqlMapper配置

  代理接口方式,我们姑且不管是怎么样代理的,但能够运行起来,肯定是有一段真正的可以运行的代码——实际上就是调用SqlSession接口中的方法。因此首先需要解决的就是下面三个基本问题:

问题1:确定需要执行的sqlId

  从SqlSession接口的方法签名可以知道,所有的数据访问方法,都必须有一个参数“statement”,也就是我们通常所说的sqlId。那么,sqlId是怎么确定的?SqlMapper中那么多配置,Mybatis怎么知道调用哪一个?

问题2:确定需要执行的方法

  不管是用哪种方式,归根结底还是调用SqlSession接口中的方法,那么问题来了,SqlSession接口中那么多不同方法,怎么知道调用哪一个呢?比如Dao接口中的一个查询方法,是调用SqlSession的selectOne?还是selectList?还是有回调的select?还是分页查询呢?

问题3:确定执行SQL时的参数

  除了sqlId,还有执行SQL时的参数,也是需要解决的一个基本问题,如果Dao接口中有多个参数,传入了多个参数,又怎么组装成一个统一的参数呢?SQL运行时的参数也会影响到执行的方法,因为很多重载的方法就只能通过运行时参数来确定。

  下面,我们带着这三个问题,看一个简单的用户维护的例子,分析一下代理接口方式原生用法是怎么处理这三个问题的,然后,也思考一下Mybatis的处理有没有什么不足和缺陷,我们又怎么处理这些不足和缺陷?

  业务场景做了简化,用户模型只包括用户ID、用户名称和用户所属的机构,需要完成的操作有:查询用户列表、查询用户列表(分页查询)、查找用户对象、新增用户、修改用户以及删除用户。

(1)Dao接口

 1 @Repository("userDao")
 2 public interface IUserDao {
 3     
 4     /**
 5      * 查询用户列表
 6      * @param user    用户查询参数
 7      * @return
 8      */
 9     public List dList(UserForm user);
10     
11     /**
12      * 查询用户列表(分页查询)
13      * @param user    用户查询参数
14      * @param page  分页对象
15      * @return
16      */
17     public List dPageList(UserForm user, IPage page);
18     
19     /**
20      * 查找单个用户
21      * @param userId 用户ID
22      * @return
23      */
24     public UserBean dFind(@Param("userId")String userId);
25     
26     /**
27      * 新增单个用户
28      * @param user
29      * @return
30      */
31     public int dInsert(UserForm user);
32     
33     /**
34      * 更新单个用户
35      * @param user
36      * @return
37      */
38     public int dUpdate(UserForm user);
39     
40     /**
41      * 删除单个用户
42      * @param userId
43      * @return
44      */
45     public int dDelete(@Param("userId")String userId);
46 }

(2)SqlMapper配置

 1 xml version="1.0" encoding="UTF-8" ?>
 2 DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 3 <mapper namespace="com.forms.beneform4j.webapp.systemmanage.user.dao.IUserDao">
 4     
 5     <select id="dList" resultType="com.forms.beneform4j.webapp.systemmanage.user.bean.UserBean">
 6         SELECT U.USER_ID, U.USER_NAME, U.ORG_ID
 7           FROM BF_USER U
 8          <where>
 9              <if test="null != userId and '' != userId">
10                  AND U.USER_ID = #{userId, jdbcType=VARCHAR}
11              if>
12              <if test="null != orgId and '' != orgId">
13                  AND U.ORG_ID = #{orgId, jdbcType=VARCHAR}
14              if>
15              <if test="null != userName and '' != userName">
16                  AND U.USER_NAME LIKE '%'||#{userName, jdbcType=VARCHAR}||'%'
17              if>
18          where>
19          ORDER BY U.ORG_ID, U.USER_ID
20     select>
21     
22     <select id="dPageList" resultType="com.forms.beneform4j.webapp.systemmanage.user.bean.UserBean">
23         SELECT U.USER_ID, U.USER_NAME, U.ORG_ID
24           FROM BF_USER U
25          <where>
26              <if test="null != userId and '' != userId">
27                  AND U.USER_ID = #{userId, jdbcType=VARCHAR}
28              if>
29              <if test="null != orgId and '' != orgId">
30                  AND U.ORG_ID = #{orgId, jdbcType=VARCHAR}
31              if>
32              <if test="null != userName and '' != userName">
33                  AND U.USER_NAME LIKE '%'||#{userName, jdbcType=VARCHAR}||'%'
34              if>
35          where>
36          ORDER BY U.ORG_ID, U.USER_ID
37     select>
38     
39     <select id="dFind" resultType="com.forms.beneform4j.webapp.systemmanage.user.bean.UserBean">
40         SELECT U.USER_ID, U.USER_NAME, U.ORG_ID
41           FROM BF_USER U
42          WHERE U.USER_ID = #{userId, jdbcType=VARCHAR}
43     select>
44     
45     <insert id="dInsert" >
46         INSERT INTO BF_USER (USER_ID, USER_NAME, ORG_ID)
47         VALUES (#{userId,jdbcType=VARCHAR}, #{userName,jdbcType=VARCHAR}, #{orgId,jdbcType=VARCHAR})
48     insert>
49     <update id="dUpdate" >
50         UPDATE BF_USER
51            SET USER_NAME = #{userName,jdbcType=VARCHAR},
52                ORG_ID = #{orgId,jdbcType=VARCHAR}
53          WHERE USER_ID = #{userId,jdbcType=VARCHAR}
54     update>
55     <delete id="dDelete">
56         DELETE FROM BF_USER 
57          WHERE USER_ID = #{userId, jdbcType=VARCHAR}
58     delete>
59 mapper>

  现在来看Mybatis是怎么解决上面的问题的。

  第一个问题:确定需要执行的sqlId

  Mybatis的解决方法很简单,就是将接口类加上方法作为sqlId,可以用下面一个公式来描述:

需要执行的SqlId
 = Dao接口所在的类名(全限定名)+ 点号(.)+ 方法名
 = SqlMapper配置文件中的命名空间 + 点号(.) + [select|insert|update|delete]元素的id属性

  有什么不足呢?很明显,以下几种情形都无法处理:

  1. 两个重载的方法(方法名相同,参数不同),需要执行不同的sqlId
  2. 两个不同名的方法,需要引用相同的sqlId配置(参数不同,比如查询列表和查找元素,就常常可以共用一个配置,各个业务模块都有可能引用这段配置,当然,这种情况下并不建议dao直接引用