赞
踩
映射器是 MyBatis 最强大的工具,也是我们使用 MyBatis 时用得最多的工具,因此熟练掌握它十分重要,MyBatis 是针对映射器构造的 SQL 构建轻量级构架,并且通过配置生成对应的 JavaBean 返回给调用者,而这些配置主要便是映射器,在 MyBatis 中你可以根据情况定义动态 SQL 来满足不同场景 的需要,它比其他的框架灵活得多,MyBatis 还支持自动绑定 JavaBean,我们只要让 SQL返回的字段名和 javaBean属性名保持一致,或者采用驼峰命名,便可以省掉这些元繁琐的映射配置。
首先让我们明确在映射器中我们可以定义哪些元素,它们的作用是什么?
元素名称 | 描述 | 备注 |
---|---|---|
select | 查询语句 ,最常用,最复杂的元素之一 | 可以自定义参数,返回结果集等 |
insert | 插入语句 | 执行后返回一个整数,代表插入的条数 |
update | 更新语句 | 执行后返回一个整数,代表更新的条数 |
delete | 删除语句 | 执行后返回一个整数,代表删除条数 |
parameterMap | 定义参数映射关系 | 即将被删除的元素,不建义大家使用 |
sql | 允许定义一部分 SQL,然后在各个地方引用它 | 例如,一张表列名,我们可以一次定义,在多个 SQL 语句中使用 |
resultMap | 用来描述从数据库结果集中来加载对象,它是最复杂,最强大的元素 | 它将提供映射规则 |
cache | 给定命名空间的缓存配置 | ---- |
cache-ref | 其他命名空间缓存配置的引用 | – |
毫无疑问,select 元素是我们最常用也是功能最强大的 SQL 语言,select元素帮助我们从数据库中读取数据,组装数据给业务人员,执行 select 语句前,我们需要定义参数,它可以是一个简单的参数类型,例如 int,float,String,也可以是一个复杂的参数类型,例如 javaBean,Map 等,这些都是 MyBatis 接受的参数类型,执行 SQL后,MyBatis 也提供了强大的映射规则,甚至是自动映射来帮助我们把返回结果集绑定到 JavaBean 中,select 元素配置如下表所示
元素 | 说明 | 备注 |
---|---|---|
id | 它和 Mapper 的命名空间组合起来是唯一的,提供给 MyBatis 调用 | 命名空间和 id 组合起来不唯一,MyBatis 将抛出异常 |
parameterType | 你可以给出类的全命名,也可以给出类的别名,但是使用别名必需 MyBatis 内部定义或者自定义 | 我们可以选择 JavaBean,Map 等复杂的参数类型传递给 SQL |
parameterMap | 即将废弃的元素,我们不再讨论它 | – |
resultType | 定义类的全路径,在自动匹配的情况下,结果集将通过 javaBean 的规范映射或定义为 int,double,float等参数,也可以使用别名,但是要符合别名的规范,不能和 resultMap 同时使用 | 它是我们常用的参数之一,比如我们统计总条数就可以把它的值设置为 int |
resultMap | 它是映射集的引用,将执行强大的映射功能,我也可以以使用 resultType 或者 resultMap 其中的一个,resultMap 可以给予我们自定义的映射规则的机会 | 它是 MyBatis 最复杂的元素之一,可以配置映射规则,级联,TypeHandler |
flushCache | 它的作用是调用 SQL 后,是否要求 MyBatis 清空之前的查询本地缓存和二级缓存 | 取值为 boolean,true/false,默认为 false |
useCache | 启动二级缓存的开关,是否要求 MyBatis 将此次结果缓存 | 取值为 boolean,true/false,默认为 true |
timeout | 设置超时参数,等超时的时候抛出异常,单位为秒 | 默认值是数据库厂商提供的 JDBC 驱动所设置的秒数 |
fetchSize | 获取记录的总条数设定 | 默认值是数据库厂商提供的 JDBC 驱动所设置的条数 |
statementType | 告诉 MyBatis 使用哪个 JDBC 的 Statement 工作,取值为 STATEMENT(Statement),PREPARED(PreparedStatement) ,CallableStatement | 默认值为 PREPARED |
resultSetType | 这是对 JDBC的 resultSet 接口而言,它的值包括 FORWARD_ONLY(游标允许向前访问),SCROLL_SENSITIVE(双向滚动,但不及时更新,就是如果数据库里的数据修改过,并不在 resultSet 中反应出来),SCROLL_INSTNSITIVE(双向滚动,并及时跟踪数据库更新,以便更改 resultSet 中的数据) | 默认值是数据库厂商提供的 JDBC驱动所设置的 |
databaseId | 它的使用请参考第3章databaseIdProvider 数据库厂商标识这部分内容 | 提供多种数据库的支持 |
resultOrdered | 这个设置仅适用于嵌套结果集 select 语句,如果为 true,就是假设包含了嵌套结果集或者是分组了,当返回一个主结果行的时候,就不能对前面的结果结构的引用,这就确保了在获取嵌套结果集的时候不至于导致内存不够用 | 取值为布尔值,true/false,默认值为 false |
resultSet | 适合于多个结果集的情况,它将列出执行的 sql 后每个结果集的名称,每个名称间用逗号分隔 | 很少使用 |
如下:
<select id="getUser" resultType="User" parameterType="java.lang.Long"> select * from lz_user where id=#{id} </select>
这样就可以使用 MyBatis 调用 SQL 了,十分简单,下面对操作步骤进行归纳概括。
传递多个参数
更多的时候,我们需要传递多个参数给映射器,如果我们需要多个参数来查询 。
<select id="getUser" resultType="User" parameterType="map"> select * from lz_user where id=#{id} and username="#{username}" </select>
这个方法虽然简单易用,但是有一个弊端:这样设置的参数使用了 Map,而 Map 需要键值对应,由于业务关联不强,你需要深入程序看代码,造成了可读性下降,MyBatis 为我们提供了更好的实现方式,它就是注解参数的形式,让我们来看看如何实现。
public User getUser(@Param("id")Long id ,@Param("username") String username); //把映射的 XML 修改为无需定义参数类型 <select id="getUser" resultType="User" > select * from lz_user where id=#{id} and username="#{username}" </select>
当我们把参数传递给后台的时候,通过@Param 提供的名称 MyBatis 就会知道#{id}代表着 id,#{username} 代表着 username,参数的可读性大大提高,但是这引发了另一个麻烦,一个 SQL如果要有10个参数需要查询 ,如果我们用@Param方式,那么参数将十分得要,可读性依旧不高,不过 MyBatis 为我们提供了 JavaBean 的方式来解析这个问题 。
在参数过多的情况下,MyBatis 允许组织一个 JavaBean,通过简单的 setter 和 getter方法设置参数,这样就可以使得可读性变高了。
@Data public Class UserParam{ private Long id ; private String username; }
public User getUser(UserParam user); //把映射的 XML 修改为无需定义参数类型 <select id="getUser" resultType="User" > select * from lz_user where id=#{id} and username="#{username}" </select>
总结:我们描述了3种传递参数的方式,下面对各种方式加以点评和总结,以利于我们在实际操作中应用。
使用 resultMap 映射结果集
在某个时候,我们需要处理更加复杂的结果集,resultMap 为我们提供了这样的模式,我们需要映射器定义 resultMap,这也是我们常见的场景。
<resultMap id="BaseResultMap" type="com.spring_101_200.test_131_140.test_132_mybatis_typehandlers.User"> <id column="id" property="id"/> <result column="is_delete" property="isDelete"/> <result column="gmt_create" property="gmtCreate"/> </resultMap> <select id="getUserByMap" resultMap="BaseResultMap"> select * from lz_user where id=#{id} </select>
解释一下 resultMap 的配置。
resultMap 可没有你看到的这么简单,它是映射器中最为复杂的元素,它一般用于得复杂,级联这些关联的配置,在简单的情况下,我们可以使用 resultType 通过自动映射来完成,这样配置的工作量会大大减少,未来随着进一步的学习深入,我们还会讨论 resultMap 的高级应用。
insert元素,相对于 select 元素而言要简单得多,MyBatis 会在执行插入之后返回一个整数,以表示你进行操作后插入的记录数。insert 元素配置详解 。
属性名称 | 描述 | 备注 |
---|---|---|
id | 它和 Mapper 的命名空间组合起来唯一的,作为唯一的标识提供给 MyBatis 调用 | 如不唯一,MyBatis 将抛出异常 |
parameterType | 你可以给出类的全命名,也是一个别名,但是使用别名必需是 MyBatis 内部定义或者自定义别名,定义方法可以参考typeAlias 元素讲解 | 我们可以选择 JavaBean,Map 等参数类型传递给 SQL |
parameterMap | 即将废弃元素 | – |
flushCache | 它的作用是在调用 SQL 后,是否要求 MyBatis 清空之前的查询的本地缓存和二级缓存 | 取 boolean 值,true/false,默认为 false |
timeout | 设置超时参数,等超时的时候将抛出异常,单位为秒 | 默认值是数据加厂商提供的 JDBC 驱动设置的秒数 |
statementType | 告诉 MyBatis 使用哪个 JDBC 的 Statement 工作,取值为 STATEMENT(statement),PREPARED(PreparedStatement) 和 CallableStatement | 默认为 PREPARED |
keyProperty | 表示以哪个列作为属性的主键,不能和 keyProperty 同时使用 | 设置哪个列为主键,如果是联合主键可以用逗号将其隔开 |
useGeneratedKeys | 这会命令 MyBatis 使用 JDBC的 getGeneratedKeys 方法来取出数据库内部生成的主键,例如,MySQL和 SQL Server 自动递增字段,Oracle 的序列等,但是使用它就必需要给 keyProperty 或 KeyColumn 赋值 | 取值为 boolean,默认为 false |
keyColumn | 指明第几列是主键,不能和 keyProperty 同时使用,只接受整形参数 | 和 keyProperty 一样联合主键可以逗号隔开 |
databaseId | 请参考上一篇博客databaseIdProvider 数据库厂商标识这部分内容 | 提供了多种数据库支持 |
lang | 自定义语言,可使用第三方语言,使用得较少 | – |
虽然元素也不少,但是我们实际操作中常用的元素只有几个,并不是很难撑握,下面来看看:
<insert id="insertUser" parameterType="User"> INSERT INTO lz_user (username, password, real_name, manager_id) VALUES (#{username},#{password},#{realName},#{managerId}) </insert>
现实中还有许多我们需要处理的问题,主键自增字段,MySQL 里面主要需要根据一些特殊的规则去生成,在插入后,我们往往需要获取这个主键,以便于未来操作,而 MyBatis 提供了实现的方法。
首先我们可以使用 keyProperty 属性指定哪个是主键字段,同时使用 useGeneratedKeys告诉 MyBatis这个主键是否使用数据库内置策略生成。
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id" > insert into lz_user( <if test="username != null">username, </if> <if test="password != null">password, </if> <if test="realName != null">real_name, </if> <if test="managerId != null">manager_id, </if> <if test="sex != null">sex, </if> <if test="sexStr != null">sex_str, </if> is_delete, gmt_create, gmt_modified )values( <if test="username != null">#{ username}, </if> <if test="password != null">#{ password}, </if> <if test="realName != null">#{ realName}, </if> <if test="managerId != null">#{ managerId}, </if> <if test="sex != null">#{ sex}, </if> <if test="sexStr != null">#{ sexStr}, </if> 0, now(), now() ) </insert>
这样我们传入的 user 对象无需设置 id的值,MyBatis 会用数据库设置进行处理,这样好处就是 MyBatis 插入的时候,它会回填 JavaBean 的 id,我们进行调试,在插入之后,它会自动填充主键,方便以后使用。
在实际工作中,往往不是我们想像的那样简单,需要根据一些特殊的关系设置主键 id 的值,假设我们取消 id 的自增规则,我们要求,如果lz_user 表中没有记录,则我们需要将 id=1 ,否则,我们就取最大的 id加2,来设置新的主键,对于一些特殊的要求,MyBatis 也给我们提供了对应办法。
<insert id="insertUserIdAddDouble" parameterType="User" useGeneratedKeys="true" keyProperty="id" > <selectKey keyProperty="id" resultType="long" order="BEFORE"> select if(max(id) is null ,1 ,max(id) + 2 ) as newId from lz_user </selectKey> insert into lz_user( <if test="id != null">id, </if> <if test="username != null">username, </if> <if test="password != null">password, </if> <if test="realName != null">real_name, </if> <if test="managerId != null">manager_id, </if> is_delete, gmt_create, gmt_modified )values( <if test="id != null">#{ id}, </if> <if test="username != null">#{ username}, </if> <if test="password != null">#{ password}, </if> <if test="realName != null">#{ realName}, </if> <if test="managerId != null">#{ managerId}, </if> 0, now(), now() ) </insert>
需要注意的是,上面id 列必需写。这样,我们就能定义自己的规则来生成主键了,MyBatis 的灵活性也得到体现。
这两个元素比较简单,所以我们讨论,和 insert 元素一样,MyBatis 执行完 update 元素和 delete 元素后返回一个整数,标出执行后影响的记录条数。
<update id="updateUserById" parameterType="User" > update lz_user <trim prefix="set" suffixOverrides=","> <if test="isDelete != null">is_delete = #{isDelete},</if> <if test="gmtCreate != null">gmt_create = #{gmtCreate},</if> <if test="username != null">username = #{username},</if> <if test="password != null">password = #{password},</if> <if test="realName != null">real_name = #{realName},</if> <if test="managerId != null">manager_id = #{managerId},</if> <if test="sex != null">sex = #{sex},</if> <if test="sexStr != null">sex_str = #{sexStr}</if> </trim> ,gmt_modified = now() where id = #{id} </update> <update id="deleteUserById" parameterType="java.lang.Long"> update lz_user set is_delete = 1 where id=#{id} limit 1 </update>
我们所面对的大部分场景都和这个相似,需要通过一个 JavaBean 插入一张表的记录或者根据主键删除记录,对于参数传递可参数 select 元素传递参数的例子,插入和删除执行完成 MyBatis 会返回一个整数显示更新或者删除几条记录。
正如你所看到的,我们可以传入一个简单的参数,比如 int,double等,也可以传入 JavaBean,这些我们都讨论,有时候我们需要处理一些特殊的情况,我们可以指定特定的类型,以确定使用哪个 typeHandler 处理它们,以便我们进行特殊的处理。
#{age,javaType=int,javaType=NUMBERIC}
当我们还可以指定用哪个 typeHandler 去处理参数
#{age,javaType=int,javaType=NUMBERIC,TypeHandler=MyTypeHandler}
此外,我们还可以对一些数值的参数设置其保存的精度 。
#{price,javaType=double,jdbcType=NUMBERIC,numbericScale=2}
可见,MyBatis 映射器可以通过 EL的功能帮助我们所需要的多种功能,使用还是很方便的。
对于存储过程而言,存在3种参数,输入参数(IN),输出参数(OUT),输入输出参数(INOUT),MyBatis 的参数规则为其提供了良好的支持,我们通过制定 mode 属性来确定是哪一种参数,它的选项有3种:IN,OUT,INOUT,当参数设置的 OUT 或者 INOUT 的时候,正如你所希望的一样,MyBatis 会将存储过程返回的结果设置到你参数中,当你返回一个游标(也就是我们制定的 JDBCTYPE=CURSOR) 的时候,你还需要去设置 resultMap 以便 MyBatis 将存储过程的参数映射到对应的类型,这时,MyBatis 就会通过你所设置的 resultMap 自动为你设置映射结果
#{user,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=userResultMap}
这里的 javaType 是可选的,即使你不指定它,MyBatis 也会自动检测它。
MyBatis 还支持一些高级特性,比如我们说结构体,但是当你注册参数的时候,你就会需要去指定语句的名称(JavaTypeName),比方说下面的用法。
#{user,mode=OUT,jdbcType=STRUCT,javaTypeName=MY_TYPE,resultMap=duserResultMap}
在大部分情况下MyBatis 都会去推断你返回的数据类型,所以大部分的情况下,你都无法去配置参数类型和结果类型,只是可能返回为空的字段类型而已,因为 null 值,MyBatis 无法判断其类型。
#{gmtCreate},#{id},#{realName},#{username},#{password}
对于备注而言,可能返回值是空的,用 javaType=VARCHAR 明确告知 MyBatis,让他被 StringTypeHandler 处理即可。
这里我们暂时不给出调试过程和方法,我们会在后面来进行使用。
在 MyBatis 中,我们常常传递字符串,我们设置的参数#{name}在大部分的情况下MyBatis 会用创建预编译的语句,然后 MyBatis 为它设置值,而我们有个时候我们需要的是传递 Sql 语句的本身,而不是 SQL 所需要的参数,例如,在一些动态表格(有时候经常遇到根据不同的条件产生不同的动态列)中,我们要传递 SQL的列名,根据某些列进行排序 ,或者传递给列名 SQL 都是比较常见的场景,当然 MyBatis 也对这样的场景进行支持,这些都是 Hibernate难以做到的。
例如:在程序中传递变量 columns="col1,col2,col3…"给 sql,让其组装成 SQL 语句,我们当然不想被 MyBatis 像处理普通参数一样把它设置成"col1,col2,col3…"那么 们就可以写成下面这样
select ${columns} from lz_user
这样 MyBatis 就不会帮我们转译 columns,而变直出,而不是作为SQL 的参数进行设置了,只有这样对 SQL 直言是不安全的,MyBatis 给你灵活性的同时也需要你自己去控制参数以保证 SQL 运转的正确性和安全性。
SQL 元素的意义,在于我们可以定义一串 SQL 语句的组成部分,其他的语句可以通过引用来使用它,例如,你有一个 SQL 需要 select 几十个字段映射到 JavaBean 中去,我的第二条 SQL 也是这几十个字段映射到 JavaBean 中去,显然这些字段写两遍不太合适,那么就用 SQL 元素来完成,例如插入角色,查询角色列表就可以这样定义
<sql id="role_columns" > id,role_name,note </sql> <select parameterType="long" id="getUser" resultMap="userMap"> select <include refid="role_columns"/> from t_user where id = #{id} </select>
这里我们用 sql 元素定义了 role_columns,它可以很方便的使用 include 元素的 refid属性进行引用,从而达到重用的功能。
< id="role_columns" > #{prefix}.id,#{prefix}.role_name,#{prefix}.note </sql> <select parameterType="long" id="getUser" resultMap="userMap"> select <include refid="role_columns"> <property name="prefix" value="r"/> </include> from t_user where id = #{id} </select>
这样就可以给 MyBatis 入参,我们还可以这样给 refid 一个参数值,由程序制定的引入 SQL
<sql id=“someinclude”>
select * from <indeclude refid="${tableName}"/>
</sql>
这样就可以实现一处定义多处引用,大大减少了工作量。
元素里还以以下的元素。
<resultMap> <constructor> <idArg/> <arg/> </constructor> <id /> <result /> <association /> <collection /> <discriminator > <case /> </discriminator> </resultMap>
其中 constructor 元素用于配置构造方法,一个 POJO 可能不存在没有参数的构造方法。
这个时候,我们就可以使用 constructor 进行配置,假设角色 UserBean 不存在没有参数的构造方法,它的构造方法声明为 public User(int id ,String username),那么我们需要配置这个结果集
<resultMap ...> <constructor> <idArg column="id" javaType="int"/> <arg column="username" javaType="String"/> </constructor> .... </resultMap>
这样,MyBatis 就知道需要用这个构造方法来构造 POJO 了
id 元素表示哪个列是主键,允许多个主键,多个主键则称为联合主键,result 是配置 POJO到 SQL列名的映射关系,这里的 result 和 id 两个元素都有如下表属性。
元素名称 | 说明 | 备注 |
---|---|---|
property | 映射到列结果字段或属性,如果 POJO的属性匹配是存在的,和给定 SQL列名(column 元素)相同的,那么 MyBatis 就会映射到 POJO 上 | 可以使用导航式的字段,比如访问一个学生对象(Student) 需要访问学生证(selfcard) 的发生日期(issueDate),那么我们可以写成 selfcard.issueDate |
column | 这里对应的是 SQL的列 | – |
javaType | 配置 java 的类型 | 可以特定的类完全限定名或者 MyBatis 上下文的别名 |
jdbcType | 配置数据库类型 | 这是一个 jdbc 的类型,MyBatis 己经为我们做了限定,基本支持所有常用的数据库 |
typeHandler | 配置数据库类型 | 这是一个 jdbc 的类型,myBatis 己经为我们做了限定,基本支持所有常用的数据库类型 |
typeHandler | 类型处理器 | 允许你用特定的处理器来覆盖 MyBatis 默认的处理器,这就是制定 jdbcType 和 javaType 相系转化的规则 |
此外有 association,collection和 discriminator 这些元素,我们将在级联那里详细讨论它们的用法。
一般而言,任何的 select 语句都使用 map 存储。
<select id=“findColorByNote” parameterType=“string” resultType=“map”>
select * from lz_user where id = #{username}
</select>
使用 map原则上是可以匹配所有的结果集的,但是使用 map 接口就意味着可读性下降,所以这不是一种推荐方式,更多的时候,我们使用 PO的方式。
使用 map 方式就意味着可读性的丢失,POJO 是我们最常用的方式,也是我们推荐的方式,一方面我们可以使用自动映射,正如select 语句里讨论的一样,我们还可以使用 select 语句的属性 resultMap 配置映射集合,只是使用前需要配置类似的 resultMap。
<resultMap id="BaseResultMap" type="com.sina.model.entity.user.User"> <id column="id" property="id"/> <result column="is_delete" property="isDelete"/> <result column="gmt_create" property="gmtCreate"/> <result column="gmt_modified" property="gmtModified"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="real_name" property="realName"/> <result column="manager_id" property="managerId"/> <result column="sex" property="sex"/> <result column="sex_str" property="sexStr"/> </resultMap>
resultMap 元素的属性 id 代表着这个 resultMap 的标识,type 代表着你需要映射的 POJO,我们可以使用 MyBatis 定义好的类的别名,也可以使用自定义类的全限定名。
映射关系中,id 元素表示这个对象的主键,property 代表着 POJO 的属性名称,column 表示数据库中列名,于是 POJO就和数据库中 SQL的结果一一对应起来,接着我们的映射文件中的 select 元素里这样写,就可以使用了。
<select id="selectUserById" resultMap="resultMap" > select * from lz_user where id=#{id} and is_delete = 0 limit 1 </select>
我们可以发现 SQL 语句的列名resultMap 是 column 是一一对应的,使用 XML配置结果集,我们可以配置 typeHandler,javaType,jdbcType,但是这条语句中配置了 resultMap 就不需要配置 resultType了。
在数据库中包含着一对多,一对一的关系,通过用户信息查找账单信息。
select * from lz_user lu left join lz_user_bill lub on lu.id =lub.user_id where lu.id =456
这里是将用户信息和账单信息都查询出来,我们也希望用户信息中多一个属性,List<UserBill> billList 这样就可以取出用户的时候,同时将账单信息取出 。我们将这种关系叫做级联。
在级联中存在3种对应关系,其一,一对多的关系,如用户也账单的关系,举个例子,一家软件公司存在着许多软件工程师,公司和软件工程师就是一对一的关系,其二,一对一的关系,例如每个软件工程师都有一个编号(ID),这是它是软件保护公司的标识,它与工程师是一对一的关系,其三,多对多的关系,例如,有些公司一个角色可以对应多个用户,但是一个用户也可以兼任多个角色,通俗而言,一个人可以既是总经理,同是也是技术总监,而技术总监这个职位可以对应很多人,这就是多对多的关系。
在实际中,多对多的关系应用不多,因为它复杂,会增加理解和关联的复杂度,推荐的方法是,用一对一关系,把它分解为双向关系,以降低关系的复杂度,简化程序,有时候,我们也需要鉴别关系,比如我们去体检,男女有别,男性和女性的体检项目并不完全一样,如果让男性去体检妇科项目,就会闹出笑话。
所在在MyBatis 中级分为3种,association,collection和 discriminator,下面分别介绍 。
association一对一级联
在实际操作中,我需要从账单中查找到用户信息。
1.准备数据库表
CREATE TABLE lz_user
(
id
bigint(20) unsigned NOT NULL AUTO_INCREMENT,
is_delete
tinyint(2) DEFAULT ‘0’,
gmt_create
datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP,
username
varchar(32) DEFAULT NULL COMMENT ‘用户名’,
password
varchar(64) DEFAULT NULL COMMENT ‘密码’,
real_name
varchar(64) DEFAULT NULL,
manager_id
int(11) DEFAULT NULL COMMENT ‘管理员id’,
sex
int(11) DEFAULT ‘1’ COMMENT ‘性别’,
sex_str
varchar(32) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=473 DEFAULT CHARSET=utf8mb4 COMMENT=‘公益口罩’;
准备账单表
CREATE TABLE lz_user_bill
(
id
bigint(20) unsigned NOT NULL AUTO_INCREMENT,
is_delete
tinyint(2) DEFAULT ‘0’,
gmt_create
datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
gmt_modified
datetime DEFAULT CURRENT_TIMESTAMP,
type
varchar(32) DEFAULT ‘-’ COMMENT ‘收支类型’,
user_id
int(11) DEFAULT NULL COMMENT ‘用户id’,
manager_id
int(11) DEFAULT NULL COMMENT ‘管理员id’,
amount
decimal(12,2) DEFAULT NULL,
remark
text COMMENT ‘备注’,
bill_type
varchar(256) DEFAULT NULL COMMENT ‘账单类型’,
pay_type
varchar(255) DEFAULT NULL COMMENT ‘支付方式’,
status
int(11) DEFAULT ‘0’ COMMENT ‘-1表示作费,0表示提交,1表示已经报销’,
self_look
int(11) DEFAULT ‘0’ COMMENT ‘0表示公开,1表示仅仅自己可见’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=62 DEFAULT CHARSET=utf8mb4 COMMENT=‘公益口罩’;
<resultMap type="com.spring_101_200.test_131_140.test_133_mybatis_lazyloadingenabled_aggressivelazyloading.UserBill" id="ordersUserLazyLoading"> <!---对订单信息进行映射配置-> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="amount" property="amount"/> <result column="type" property="type"/> <association property="user" javaType="com.spring_101_200.test_131_140.test_133_mybatis_lazyloadingenabled_aggressivelazyloading.User" select="findUserById" column="user_id"/> </resultMap> <!--查询订单关联用户--> <select id="findUserBillLazyLoading" parameterType="java.lang.Long" resultMap="ordersUserLazyLoading"> select * from lz_user_bill where id = #{id} </select> <!--根据Id查询用户--> <select id="findUserById" parameterType="java.lang.Long" resultMap="userBillResult"> select * from lz_user lu where lu.id =#{id} </select>
请看上面加粗的代码,这是通过一次关联来处理问题,其中 select 元素指定 SQL 去查询,而 column 则是指定传递给 select 语句的参数,这里就是Bill 里的 user_id,当取出 UserBill 的时候,MyBatis 就会知道用下面的 SQL 取出我们需要的级联信息。
@Data public class UserBill { private Long id; private String type; private Long userId; private BigDecimal amount; private User user; }
一个用户有多条账单,一条账单只对应一个用户,这就是一对多关系,在 MyBatis 中,是如何来取出一对多关联数据的呢?因为数据库表结果上面己经提到过了,这里就不再重复。
<resultMap id="userBillResult" type="UserBillInfo"> <id property="id" column="id" /> <collection property="billList" ofType="Bill" > <id property="id" column="id" /> <result property="type" column="type"></result> <result property="userId" column="user_id"></result> <result property="amount" column="amount"></result> </collection> </resultMap> <select id="selectUserBill" parameterType="java.lang.Long" resultMap="userBillResult"> select * from lz_user lu left outer join lz_user_bill lub on lu.id =lub.user_id where lu.id =#{id} </select>
@Data public class UserBillInfo { private Long id; private List billList; }
public void testGetUser() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserBillInfo userBillInfo = userMapper.selectUserBill(456l); System.out.println(JSON.toJSONString(userBillInfo)); }
4.结果
我们打印出了用户 id,同时根据用户 id 获取账单信息。
鉴别器级联是在特定的条件下去使用不同的 POJO,本例中主要是为了测试,如果账单信息的 is_delete等于1,取出用户信息,看是谁的账单信息被删除了。还是数据库表不做重复说明。
<?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"> <mapper namespace="com.spring_101_200.test_141_150.test_146_mybatis_discriminator.UserMapper"> <resultMap type="com.spring_101_200.test_141_150.test_146_mybatis_discriminator.UserBill" id="ordersUserLazyLoading"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="amount" property="amount"/> <result column="type" property="type"/> <discriminator javaType="int" column="is_delete"> <case value="1" resultType="com.spring_101_200.test_141_150.test_146_mybatis_discriminator.UserBill"> <association property="user" javaType="User" select="findUserById" column="user_id"/> </case> </discriminator> </resultMap> <select id="findUserBillLazyLoading" parameterType="java.lang.Long" resultMap="ordersUserLazyLoading"> select * from lz_user_bill where id = #{id} </select> <select id="findUserById" parameterType="java.lang.Long" resultType="User"> select * from lz_user lu where lu.id =#{id} </select> </mapper>
@Data public class UserBill { private Long id; private Integer isDelete; private String type; private Long userId; private BigDecimal amount; private User user; }
public void testGetUser() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserBill user = userMapper.findUserBillLazyLoading(60l); System.out.println(JSON.toJSONString(user)); }
测试结果,当账单信息被删除时,打印出用户信息。
级联优势是能够方便快捷的获取数据,比如学生和学生成绩信息往往是最常用的关联信息,这个时候级联是完全有必要的,多层关联时,建义不超过三层关联时尽量少用级联,因为不仅用处不大,而且会造成复杂度增加,不利于他从的理解和维护,同时级联时也存在一些劣势,有时候我们并不需要获取所有的数据,例如,我们对学生课程和成绩感兴趣,我就不用取出学生证和健康情况了,因为取出学生证和健康情况表不但没有意义,而且会多执行几条 SQL ,导致性能下降,我们可以使用代码去取他们,而不需要每次都加载。
级联还有更加严重的问题,假设有表关联到 User 表,那么可以想象,我们还要增加级联关系到结果集里,那么级联关系就会变得更加复杂,如果我们采用类似默认的场景,那么一个关联我们就要多执行一次 SQL,正如我们上面的例子一样,每次取一个 User对象,那么它所有的信息将被取出来,这样会造成 SQL 执行过多而导致性能下降,这就是 N+1 问题,为了解决这个问题,我们应该考虑使用延迟加载的功能。在上一篇博客中,我己经对延迟加载做了详细的分析了,这里就不再做过多的赘述。
之前讲的是全局设置,但是还是不太灵活的,为什么呢?因为我们不能指定哪些属性可以立即加载,哪些属性可以延迟加载,当一个功能的两个对象经常要一起使用时,我们采用即时加载更好,因为即时加载可以多条 SQL一次性发送,性能高,例如,学生和学生课程成绩,当遇到类似于健康和学生证的情况时,则用延迟加载好些,因为健康和学生请表可能不需要经常访问,这样,我们就要修改 MyBatis 的全局默认的延迟加载功能,不过不必担心,MyBatis 可以很容易的解决这些问题,因为它也有局部延迟加载的功能,我们可以在 association 和 collection元素上加入属性值 fetchType 就可了,它有两个取值范围,即 eager 和 lazy,它的默认值取决于你在配置文件中 setting 的配置,假如我们没有配置它,那么它们就是 eager,一旦你配置了它们,那么全局的变量就会被它们所覆盖,这样我们就可以灵活的指定哪些东西可以延迟加载,哪些东西不需要延迟加载,很灵活。
也许有人对延迟加载感兴趣,延迟加载的实现原理是通过动态代理来实现的,在默认情况下,MyBatis在3.3或者以上的版本时,采用的是 JAVASSIST 的动态代理,低版本用的是 CGLIB,当然,你可以使用配置修改,有兴趣的同学可以去研究一下动态代理的相关内容,它会生成一个动态代理对象,里面保存着相关的 SQL 和参数,一旦我们使用这个代理对象的方法时,它会进入动态代理对象的代理方法里,方法里面会通过发送 SQL 和参数,就可以把对应的结果从数据库中查找出来。这便是其实现原理。
缓存是互联网系统常常用到的,其特点就是将数据保存到内存中,目前流行的缓存服务器有 MongoDB ,Redis ,EHcache 等,缓存是计算机内存中保存数据 ,在读取的时候无需从磁盘中读入,因此具备快速读取和使用的特点,如果缓存例中率高,那么就可以极大的提高系统的性能,如果缓存命中率低,那么缓存就没有意义了,所以使用缓存的关键在于存储内容访问的命中率。
MyBatis 对缓存的提供支持,但是没有配置默认的情况下,它只开启一级缓存(一级缓存只是相对于同一个 SqlSession 而言)
所以在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用同一个 Mapper 的方法,往往只执行一次 SQL,因为使用 SqlSession 第一次查询后,MyBatis 会将其放入缓存中,以后再查询的时候,如果没有声明需要刷新缓存,并且缓存没有超时的情况下,SqlSession 都会取出当前缓存的数据,而不会再次发送 SQL 到数据库中。
但是如果你使用的不同的 SqlSession 对象,因为不同的 SqlSession 都是相互隔离的,所以用相同的 Mapper,参数和方法,它还是会再次发送SQL 到数据库中去执行,返回结果。
测试:
public void testGetUser() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.getUser(456l); System.out.println("再次查询用户"); user = userMapper.getUser(456l); System.out.println(JSON.toJSONString(user)); }
我们发现第一个 SqlSession 实际只发生过一次查询,而第二次查询就直接从缓存中取出了,也就是 SqlSession 层面的一级缓存,它在各个 SqlSession 是相互隔离的,为了能克服这个问题,我们往往需要配置二级缓存,使得缓存是 SqlSessionFactory 层面上是能够提供给各个 SqlSession 对象共享的。
而 SqlSessionFactory 层面上的二级缓存是不开启的,二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis 要求返回的 POJO 必需是可序列化的,也就是要求实现 Serializable接口,配置方法很简单,只需要在映射XML 文件配置的时候就开启缓存就可以了。
<cache/>
这样一个语句里面,很多的设置是默认的,如果我们想只是这样的配置,那就意味着。
添加了这个配置后,我们还必需做一件重要的事情,否则就会出现异常,这就是 MyBatis 要返回的 POJO对象要实现 Serializable接口,否则它就会抛出异常。
<cache > <!--eviction(收回策略) LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。 FIFO(先进先出):按对象进入缓存的顺序来移除它们。 SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。 WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。 --> <property name="eviction" value="LRU" /> <!--flushinterval(刷新间隔) 可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。--> <property name="flushInterval" value="6000000" /> <!--size(引用数目) 可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024 。--> <property name="size" value="1024" /> <!--readOnly(只读) 属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。 可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是 false。--> <property name="readOnly" value="false" /> </cache>
系统缓存是 MyBatis机器上的本地缓存,但是大型服务器上,会使用各类不同的缓存服务器,这个时候我们可以定制缓存,比如现在十分流行的 Redis 缓存,我们需要实现 MyBatis 为我们提供的 org.apache.ibatis.cache.Cache,缓存接口简介如下:
//获取缓存编号
String getid();
//保存 key 值的缓存
void putObject(Object key,Object value);
//通过 Key 获取缓存对象
Object getObject(Object key);
//通过 key 删除缓存
Object removeObject(Object key);
//清空缓存
void clear();
//获取缓存对象大小
int getSize();
//获取缓存的读写锁
ReadWriteLock getReadWriteLock();
因为每种缓存都有其特点,上面的接口需要自己去实现。
public class MybatisPlusCache implements Cache { // 读写锁 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); //这里使用了redis缓存,使用springboot自动注入 private HashMap<String, Object> cacheMap = new HashMap<>(); private String id; //是mybatis必须要求的,必写。此id是xml中的namespace的值 public MybatisPlusCache(final String id) { if (id == null) { throw new IllegalArgumentException("未获取到缓存实例id"); } this.id = id; } //返回cache的唯一名称 @Override public String getId() { return this.id; } //缓存存值 @Override public void putObject(Object key, Object value) { String k = id + "_" + key.toString(); //id是namespace的值,key是方法名,value是查询的结果 System.out.println("putObject :" + k); cacheMap.put(k, value); } //缓存取值 @Override public Object getObject(Object key) { String k = id + "_" + key.toString(); System.out.println("getObject : " + k); return cacheMap.get(k); } //mybatis保留方法 @Override public Object removeObject(Object key) { return null; } //清空缓存,在增删改时会自动调用 @Override public void clear() { cacheMap.clear(); } @Override public int getSize() { int i = 0; for (String key : cacheMap.keySet()) { i++; } return i; } @Override public ReadWriteLock getReadWriteLock() { return this.readWriteLock; } public boolean equals(Object o) { if (getId() == null) throw new CacheException("Cache instances require an ID."); if (this == o) return true; if (!(o instanceof Cache)) return false; Cache otherCache = (Cache) o; return getId().equals(otherCache.getId()); } public int hashCode() { if (getId() == null) throw new CacheException("Cache instances require an ID."); return getId().hashCode(); } }
上述插件,我只是举个例子,如果想用 Redis,MongoDB,自己去相应的方法中实现即可。
<?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"> <mapper namespace="com.spring_101_200.test_141_150.test_143_mybatis_cachenamespace_xml.UserMapper" > <cache type="com.spring_101_200.test_141_150.test_143_mybatis_cachenamespace_xml.MybatisPlusCache"> <!--eviction(收回策略) LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。 FIFO(先进先出):按对象进入缓存的顺序来移除它们。 SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。 WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。 --> <property name="eviction" value="LRU" /> <!--flushinterval(刷新间隔) 可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。--> <property name="flushInterval" value="6000000" /> <!--size(引用数目) 可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024 。--> <property name="size" value="1024" /> <!--readOnly(只读) 属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。 可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是 false。--> <property name="readOnly" value="false" /> </cache> <select id="getUser" parameterType="java.lang.Long" resultType="com.spring_101_200.test_141_150.test_143_mybatis_cachenamespace_xml.User"> select * from lz_user where id=#{id} </select> </mapper>
上面加粗字体,就是引入自定义缓存插件。
public void test() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.getUser(456l); //一定要commit,不然查询出来结果不会被 put sqlSession.commit(); System.out.println("======================"); user = userMapper.getUser(456l); System.out.println(JSON.toJSONString(user)); }
4.结果如下:
想在不同的 Mapper 中共用一个二级缓存。
public class MybatisPlusCache implements Cache { // 读写锁 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); //这里使用了redis缓存,使用springboot自动注入 private HashMap<String, Object> cacheMap = new HashMap<>(); private String id; //是mybatis必须要求的,必写。此id是xml中的namespace的值 public MybatisPlusCache(final String id) { if (id == null) { throw new IllegalArgumentException("未获取到缓存实例id"); } this.id = id; } //返回cache的唯一名称 @Override public String getId() { return this.id; } //缓存存值 @Override public void putObject(Object key, Object value) { String k = id + "_" + key.toString(); //id是namespace的值,key是方法名,value是查询的结果 System.out.println("putObject id: " + id); System.out.println("putObject key: " + key); String myK = getKey(key); if(StringUtils.isNotBlank(myK)){ System.out.println("putObject myK: " + myK ); k = myK; } cacheMap.put(k, value); } //缓存取值 @Override public Object getObject(Object key) { String k = id + "_" + key.toString(); System.out.println("getObject id: " + id); System.out.println("getObject key: " + key); String myK = getKey(key); if(StringUtils.isNotBlank(myK)){ System.out.println("getObject myK: " + myK ); k = myK; } Object obj = cacheMap.get(k); if(obj !=null){ System.out.println(JSON.toJSONString(obj)); } return obj; } public String getKey(Object key){ if(key instanceof CacheKey){ String keys [] = key.toString().split(":"); return keys[keys.length-2] + keys[keys.length-1]; } return null; } //mybatis保留方法 @Override public Object removeObject(Object key) { return null; } //清空缓存,在增删改时会自动调用 @Override public void clear() { cacheMap.clear(); } @Override public int getSize() { int i = 0; for (String key : cacheMap.keySet()) { i++; } return i; } @Override public ReadWriteLock getReadWriteLock() { return this.readWriteLock; } public boolean equals(Object o) { if (getId() == null) throw new CacheException("Cache instances require an ID."); if (this == o) return true; if (!(o instanceof Cache)) return false; Cache otherCache = (Cache) o; return getId().equals(otherCache.getId()); } public int hashCode() { if (getId() == null) throw new CacheException("Cache instances require an ID."); return getId().hashCode(); } }
这个缓存类和上一个不一样的地方,就是getObject 和 putObject 存储的 key 不一样,这里的 key 是 sql + 参数的形式,也就是如果两个 XML 文件中,只要查询的 SQL 一样,同时查询的条件一样,就使用缓存中数据返回。
<?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"> <mapper namespace="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserMapper" > <cache type="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.MybatisPlusCache"> <!--eviction(收回策略) LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。 FIFO(先进先出):按对象进入缓存的顺序来移除它们。 SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。 WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。 --> <property name="eviction" value="LRU" /> <!--flushinterval(刷新间隔) 可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。--> <property name="flushInterval" value="6000000" /> <!--size(引用数目) 可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024 。--> <property name="size" value="1024" /> <!--readOnly(只读) 属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。 可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是 false。--> <property name="readOnly" value="false" /> </cache> <select id="getUser" parameterType="java.lang.Long" resultType="User"> select * from lz_user where id=#{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"> <mapper namespace="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserBillMapper" > <cache-ref namespace="com.spring_101_200.test_141_150.test_145_mybatis_cachenamespaceref_xml.UserMapper"/> <select id="getUser" parameterType="java.lang.Long" resultType="User"> select * from lz_user where id=#{id} </select> </mapper>
public void test1() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserBillMapper userBillMapper = sqlSession.getMapper(UserBillMapper.class); User user = userMapper.getUser(456l); System.out.println("user:" + JSON.toJSONString(user)); sqlSession.commit(); System.out.println("======================"); user.setRealName("bbbbbbbb"); User user2 = userBillMapper.getUser(456l); System.out.println("user2:"+JSON.toJSONString(user2)); }
如果使用 JDBC或者其他框架,很多时候你得根据需要去拼接 SQL,这是一个麻烦的事情,而 MyBatis 提供对 SQL 语句动态组装能力,而且它只有几个基本的元素,十分简单,大量的判断都可以在 MyBatis 的映射 XML 文件里配置,以达到许多我们需要大量代码才能实现功能,大大减少了编写代码的工作量,这体现了 MyBatis 的灵活性,调试可配置和可维护性,MyBatis 也可以注解中配置 SQL,但是由于注解中配置功能受限,对于复杂的 SQL而言可读性很差,所以使用得较少。
MyBatis 的动态 SQL包括以下几种元素。
元素 | 作用 | 备注 |
---|---|---|
if | 判断语句 | 单条件分支判断 |
choose(when,otherwise) | 相当于 java中的 case when 语句 | 多条件分支判断 |
trim(where,set) | 辅助元素 | 用于处理一些 SQL拼装问题 |
foreach | 循环语句 | 在 in 语句等列举条件中常用 |
下面我们来讨论这些元素的用法。
if元素是我们最常用的判断语句,相当于 Java 中的 if 语句,它常常与 test 属性联合使用。
在大部分情况下,if 元素使用法简单,让我们先了解它的基本用法,现在我们要根据角色名称(username)去查找用户,但是用户名是一个可填可不填的条件,不填写时候就不需要它的查询条件,这是查询常见的场景之一,if 元素提供了简易的实现方式,如下所示
<select id="getUser" resultType="User" parameterType="java.lang.Long" > select * from lz_user where 1 = 1 <if test="username !=null "> and username=#{username} </if> </select>
这句话的含义就是当我们将参数 username 传递给映射器中,采取构造对 username 查询,如果这个参数为空,则不要这个条件去查询,显然这样的场景在我们的实际工作中十分常见,通过 MyBatis 的条件语句我们可以节省许多的拼接 SQL 的工作,把精力集中在 XML 维护上;
java 语言中 if 语句就是一种非此即彼的关系,但是很多的时候,我们所面对的不是一种非此即彼的选择,在有些时候,我们还需要第三种选择甚至是更多的选择,也就是说我们也需要 switch…case…default 语句,而映射器的动态语句中 choose,when,otherwise元素承担了这个功能,让我们看看下面的场景。
让我们看看如何使用 choose,when,otherwise 元素去实现
<select id="getUser" resultType="User" parameterType="java.lang.Long" > select * from lz_user where 1 = 1 <choose> <when test="username != null and username != ''"> and username= #{username} </when> <when test="realName !=null and realName!=''"> and real_name = #{realName} </when> <otherwise> and username is not null </otherwise> </choose> </select>
这样 MyBatis 就会根据参数的设置进行判断来动态组装SQL,以满足不同的业务要求。
细心的读者会发现SQL 语句中加入一个条件"1=1",如果没有加入这个条件,可能就会报错。
select * from lz_user where and username=#{username}
显然会报错关于 SQL 的语法异常,而加入了1=1 这样条件又显得相当的奇怪,不过不必担心,我们可以用 where 元素去处理 SQL 以达到预期的效果,例如我们去掉条件1 = 1 只要有 where 元素就可以了。
<select id="getUser" resultType="User" parameterType="java.lang.Long" > select * from lz_user <where> <if test="username !=null "> and username=#{username} </if> </where> </select>
这里当 where 元素里面条件成立,才加入 where 这个 SQL 关键字组装的 SQL 里,否则就不加入。
有时候我们需要去掉一些特殊的 SQL 语法,比如常见的 and ,or 而使用 trim元素就可以达到我们预想的效果。
<select id="getUser" resultType="User" parameterType="java.lang.Long" > select * from lz_user <trim prefix="where" prefixOverrides="and"> <if test="username !=null "> and username=#{username} </if> </trim> </select>
稍微解释一下,trim 元素就意味着我们需要去掉一些特殊字符串,prefix 代表着语句的前缀,而 prefixOverrides 代表着你需要去掉的那种字符,上面的写法基本与 where 是等效的。
在 Hibernate 中我们常常需要更新某一对象,发送所有的字段给持久对象,现实中的场景常常是,我想更新一个字段,如果发送所有的属性去更新一遍,对网络带宽消耗较大,性能最佳的办法是把主键和更新字段的值传递给 SQL 更新即可,例如,角色表有一个主键和两个字段,如果一个个的去更新,需要写两条 SQL,如果有1000个字段呢?显然这种做法是不方便的,而在 Hibernate 中,我们做更新都是会用全部字段发送给 SQL的方法来避免这一情况发生。
在 MyBatis 中,我们常常可以使用 set 元素来完成这些功能。如
<update id = "updateUser" parameterType="user"> udpate lz_user <set> <if test="username != null and username!=''"> username = #{username} </if> <if test="realName != null and realName !=''"> real_name = #{realName} </if> </set> where id = #{id} </update>
当然也可以这样写
<update id="updateUserById" parameterType="User" > update lz_user <trim prefix="set" suffixOverrides=","> <if test="isDelete != null">is_delete = #{isDelete},</if> <if test="gmtCreate != null">gmt_create = #{gmtCreate},</if> <if test="username != null">username = #{username},</if> <if test="sex != null">sex = #{sex},</if> <if test="sexStr != null">sex_str = #{sexStr}</if> </trim> ,gmt_modified = now() where id = #{id} </update>
set 元素遇到逗号,它会把对应的逗号去掉,如果我们自己编写那将是多少次的判断呢?当我们只想更新备注,我们只需要传递备注信息和角色编号即可,而不需要再传递角色名称,MyBatis 就会根据参数的规则进行动态 SQL组装,这样便能满足要求,同时避免全部字段更新麻烦。
显然 foreach元素是一个循环语句,它的作用是遍历集合,它能够很好的支持数组和 List,Set 接口集合,对此提供遍历。
在数据库中,数据字典经常使用的内容,比如在此用户表中,性别可以是男或女或未知。
1-男,2-女,0-未知。
实际工作中,可能是需要查找男性和女性用户
<select id="getUser" resultType="User" parameterType="java.lang.Long" > select * from lz_user <where> <foreach collection="sexList" index="index" item="sex" open="(" separator="," close=","> #{sex} </foreach> </where> </select>
这里需要稍微解析一下。
在 SQL 中对于in 语句我们常常使用,对于大量的数据的 in 语句需要我们特别注意,因为它会消耗大量的性能,还有一些数据库的 SQL 执行的 SQL 长度也是有限制的,所以我们有的时候还是要计算一下 collectio的长度。
test 属性用于条件判断语句,它在 MyBatis 中广泛的使用,它的作用相当于判断真假,在大部分的场景中我们都用它来判断空或非空,有时候我们需要判断字符串,数字和枚举等,所以我们十分有必要讨论一下它的用法。
<select id="getUser" resultType="User" parameterType="java.lang.Long" > select * from lz_user where 1 = 1 <if test="username !=null "> and username=#{username} </if> </select>
如果用户名不为空,则自动在 where 1 = 1 后面加上 and username=#{username}
换句话说,这条语句判定成功了,在旧版本里面我们往往需要加入 toString(),新的版本己经解决了这个问题,同样它们可以给我们判断数值型参数,对于枚举而言,取决于你使用体积 typeHandler,这些需要3章枚举 typeHandler的介绍。
bind 元素的作用是 OGNL表达式自定义一个上下文变量,这样更加方便我们使用,在我们进行模糊查询的时候,如果 MyBatis 数据库,我们常常用到的是一个 concat 用%和参数相连接,然而在 Oracle 数据库则是使用"||",这样 SQL 需要提供两种形式去实现,但是有了 bind元素,我们就完全不必使用数据库语言的,只需要使用 MyBatis 语言就可以实现所需参数相连。
比如我们要按用户名模糊查询,我们就可以将映射文件写成这样。
<select id="getUser" resultType="User" parameterType="java.lang.String" > <bind name="pattern" value="'%' +_parameter + '%'"/> select * from lz_user where username like #{pattern} </select>
这里的"_parameter" 代表着就是传递进来的参数,它和通配符连接后,赋给了 pattern,我们就可以在 select 语句中使用这个变量进行模糊查询了,不管是 MySQL 数据库还是 Oracle 数据库都可以使用这样的语句来,提高了其可移植性。
我们传递参数往往还不止一个,如果我们传递了多个参数,那是怎样使用呢?
<select id="getUser" resultType="User" parameterType="java.lang.String" > <bind name="pattern_username" value="'%' +username + '%'"/> <bind name="pattern_realName" value="'%'+realName+'%'"></bind> select * from lz_user where username like #{pattern_username} or realName like #{pattern_realName} </select>
目前我们只是对 MyBatis 的使用做讲解,关于原理和源码的解析,我们放到后面的博客中去讲解。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。