Mybatis作为日常开发中最常见的持久层框架,相信大家已经很熟悉了。这里只列出我自己用到的、认为重要的知识点(不定期新增)。欢迎大家指正和补充。
1.Java数据类型和MySQL数据库数据类型的对应关系
常用的几个如下(左为Java数据类型,右为MySQL数据类型):
-
java.lang.Integer — INGEGER
-
java.lang.Long — BIGINT
-
java.lang.BigDecimal — DECIMAL
-
java.lang.String — VARCHAR
-
java.uitl.Date — DATE
说明几点,对于java.lang.Boolean,在MySQL中可用CHAR类型的'Y(或y)'和'N(或n)'来表示,'1''0'也是可以的;如果存储的的是日期+时间,MySQL的DATETIME类型也能支持;超长文本和有预定格式的文本(比如博客的正文)建议使用TEXT型的数据类型来存储。
2.MyBatis之resultType返回值类型
2.1返回基本类型
这种情况里,常见的是返回字符串,举个例子
// mapper文件
String getSaltByAdminName(String adminName);
对应的xml文件
<!--xml文件-->
<select id="getSaltByAdminName" resultType="string">
select salt from t_admin where userName=#{userName} and type=1
</select>
xml文件中的string是java.lang.String的别名。
2.2返回实体对象
// mapper文件
CommonUser getAuthorByBlogId(Long id);
<!--xml文件-->
<select id="getAuthorByBlogId" resultType="com.holic.blog.entity.CommonUser">
SELECT a.id,a.nickName,a.avatar FROM t_admin a,t_blog b WHERE a.id=b.blogAdminId and b.id=#{id}
</select>
在resultType属性中指定实体对象的全限定路径。
2.3返回List
List<Long> getTagIdByBlogId(Long id);
<select id="getTagIdByBlogId" resultType="java.lang.Long">
select tagId from t_link where blogId=#{id}
</select>
注意,resultType属性的值指向的是List集合内存储的数据的类型,而不是list。 以上示例都是查询,其实增删改也能返回值,只是不太常用。这里推荐一款IDEA插件--free-idea-mybatis,挺好用的,会在你的mapper文件和xml文件之间建立起关联,有问题容易排查,同时能帮你根据mapper文件的接口在xml中生成SQL。
3.MyBatis之动态标签
动态标签常用于有条件的CRUD中,来动态的组成SQL语句。
3.1Insert
先上栗子
<insert id="saveBlog" parameterType="com.holic.blog.entity.Blog" useGeneratedKeys="true" keyProperty="id">
insert into t_blog
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="title != null and title !=''">
title,
</if>
<if test="description != null and description != ''">
description,
</if>
</trim>
<trim prefix=" values (" suffix=")" suffixOverrides=",">
<if test="title != null and title !=''">
#{title},
</if>
<if test="description != null and description != ''">
#{description},
</if>
</trim>
</insert>
insert的基本语法是insert into table_name(field1,field2,...) values(value1,value2,...),现在再看示例,是不是很明白了: MyBatis的trim标签在此用于去除sql语句中多余逗号,同时给sql语句拼接"("、")"以及"values(" 等;if标签则是用来过滤的,如果入参不满足条件则不拼接SQL。还有个题外话,useGeneratedKeys属性为true表明插入时主键自增,keyProperty表示将生成的主键值保存到入参的哪个字段里,这里还是保存到主键字段id。
3.2update
第二个栗子
<update id="updateBlogById" parameterType="com.holic.blog.entity.Blog">
update t_blog a
<set>
<if test="title != null and title != ''">
a.title=#{title},
</if>
<if test="content != null and content != ''">
a.content=#{content},
</if>
<if test="description != null and description != ''">
a.description=#{description},
</if>
</set>
where a.id=#{id}
</update>
update的语法有多种,这里使用的是update table_name as a set a.field1=value1 where a.field2=value2。set标签和trim标签的作用类似,去掉多余的逗号。if标签的作用则完全相同。
3.3select
还是个栗子
<select id="findAllBlogBySearch" resultType="com.holic.blog.entity.example.ShowBlogForAdmin" parameterType="com.holic.blog.entity.example.SearchBlogForAdmin">
select a.title,a.recommend,a.published,a.updateDate,b.name typeName,a.id from t_blog a,t_type b
<where>
a.blogTypeId=b.id
<if test="title != null and title != ''">
and title = #{title}
</if>
<if test="typeId != null and typeId != ''">
and blogTypeId = #{typeId}
</if>
<if test="recommend != null and recommend !=''">
and recommend=#{recommend}
</if>
</where>
</select>
查询应该是最常见的了,这里where标签来动态的拼接查询条件,if标签作为判断。
MybatisGenerator可以根据数据库表结构生成mapper文件和xml文件,有兴趣的小伙伴可以试试。
4.当mapper接口的入参多于1时的处理办法
下面的查询语句会报错
CommonUser checkAdminByUserNameAndPassWord(String userName, String passWord);
<select id="checkAdminByUserNameAndPassWord" resultMap="admin">
select * from t_admin where userName=#{userName} and passWord=#{passWord}
</select>
原因就在于Mybatis不知道接口方法的两个参数和SQL中占位符的对应关系。处理方法有两种: 一是修改mapper中的接口,添加@Param(注意包名,org.apache.ibatis.annotations.Param)注解。
CommonUser checkAdminByUserNameAndPassWord(@Param("userName") String userName, @Param("passWord") String passWord);
xml文件不变
<select id="checkAdminByUserNameAndPassWord" resultMap="admin">
select * from t_admin where userName=#{userName} and passWord=#{passWord}
</select>
注解中指定的名字即为xml文件中的占位符名。
第二种办法是修改xml文件,指定占位符名在mapper接口参数中的序号(从0开始)。
<select id="checkAdminByUserNameAndPassWord" resultMap="admin">
select * from t_admin where userName=#{0} and passWord=#{1}
</select>
mapper接口不变
CommonUser checkAdminByUserNameAndPassWord(String userName, String passWord);
接口中参数userName在第一位,所以xml中userName对应的是0,以此类推。 我使用了第一种方法,这样SQL的可读性高一点。
5.模糊查询
模糊查询时会涉及到老生常谈的SQL注入的问题。
like '%${param}%'
这样的写法能实现模糊查询,但有SQL注入的风险,所以使用下面的写法
like concat('%',#{param},'%')
使用MySQL的函数来拼接'%',#的使用避免了SQL注入,完美。
6.批量插入数据
如果同时插入的数据比较多,在java代码中循坏调用单条插入的语句效率较低,考虑使用Mybatis的批量插入来实现。 mapper接口
int saveLink(List<Link> linkList);
注意入参,是个List,集合存储的是一个对象。 SQL语句
<insert id="saveLink" parameterType="java.util.List" useGeneratedKeys="true">
insert into t_link (blogId, tagId) values
<foreach collection="list" separator="," item="item">
(#{item.blogId}, #{item.tagId})
</foreach>
</insert>
insert语句中parameterType属性也是List。批量插入的语法就如示例中那样。现在解释一下foreach标签中的各个属性:
- collection属性,该属性是必须指定的,但是在不同情况 下,该属性的值是不一样的:(1)如果传入的是单参数且参数类型是一个List的时候,collection属性值为list;(2)如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array;(3)如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map
- separator表示在每次进行迭代之间以什么符号作为分隔符,一般是逗号
- item表示集合中每一个元素进行迭代时的别名,相当于入参List里的某个元素,item.blogId就可以拿到Link对象中的blogId属性的值。
7.Mybatis分页查询
这里推荐一个分页插件:pagehelper,简单易用,上手快。 全部用法可以去项目的git主页查看,这里只介绍我自己的使用经验。PageHelper类有一个静态方法startPage(),入参是页码和每页大小
/**
* 开始分页
*
* @param pageNum 页码
* @param pageSize 每页显示数量
*/
public static <E> Page<E> startPage(int pageNum, int pageSize) {
return startPage(pageNum, pageSize, DEFAULT_COUNT);
}
把这个方法在想要进行分页查询的方法上紧挨着调用就行,就像这样
@Override
public PageInfo<Type> listType(Integer pageNum, Integer pageSize) {
// 获取第pageNum页,每一页pageSize条内容,默认查询总数count
// 紧跟在startPage方法后的第一个MyBatis 查询方法会被进行分页
PageHelper.startPage(pageNum, pageSize);
List<Type> list = typeMapper.findAllType();
// 用PageInfo对结果进行包装
PageInfo page = new PageInfo(list);
return page;
}
这是个基本的用法,不过也够用了。使用这个插件需要点配置,配置文件(命名为mybatis-config.xml,不唯一):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 设置为true时,offset会当成 pageNum 使用,limit 和 pageSize 含义相同 -->
<!-- 和startPage中的pageNum效果一样-->
<property name="offsetAsPageNum" value="true"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询;开启PageInfo类的使用 -->
<property name="rowBoundsWithCount" value="true"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
<property name="pageSizeZero" value="false"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
</configuration>
在yml配置文件中引用它:
mybatis:
config-location: classpath:mybatis-config.xml
这里稍微注意一下配置文件的位置,根目录下是classpath后直接跟文件名,否则要带上路径。
8.关于Order By
OrderBy默认是升序。
SELECT a.validatecode FROM t_code a WHERE a.codeId = '18345970836' ORDER BY a.makedate,a.maketime;
上面的SQL和下面这个结果一样
SELECT a.validatecode FROM t_code a WHERE a.codeId = '18345970836' ORDER BY a.makedate ASC,a.maketime ASC;
可以看出,如果对多个字段都有排序的需要,那么每个字段后都要有排序标识(ASC或者DESC),不写则按升序处理。开发的时候要求查询到最新的验证码,一开始的SQL是这样的
SELECT a.validatecode FROM t_code a WHERE a.codeId = '18345970836' ORDER BY a.makedate,a.maketime DESC LIMIT 1;
很明显是错误的,因为日期是升序,所以测试的时候一直报错,后来才意识到必须对每个要排序的字段添加排序标识,改正后如下
SELECT a.validatecode FROM t_code a WHERE a.codeId = '18345970836' ORDER BY a.makedate DESC,a.maketime DESC LIMIT 1;
这样日期和时间都是降序,那么查询到的才是最新的验证码。
9.Left(Right) Join
下面这张图讲的挺清楚的
简单的描述一下,left join将关键字左侧表的数据根据where条件过滤后列出;再根据on条件匹配关键字右侧表的数据,有符合条件的列出,没有则为null。right join与left join相反。 需要注意table_a a left join table_b b on a.id=b.id and ...类似这样的SQL,是不会过滤记录的,只会决定显示关键字右侧表记录的条数,左侧表的数据一定会全部展示。想要过滤记录还是要靠where子句来完成。就像下边这样的
SELECT a.item,b.score FROM t_item a LEFT JOIN t_score b on a.id=b.id WHERE a.id=4;
where跟在on的后边。
10.批量更新
10.1多行记录更新为同一个值
<update id = "updatePushStatus" parameterType = "java.util.List">
update
SYS_TEXT_PUSH
SET
PUSH_STATUS = 1,
LAST_UPDATE_DATE = NOW()
WHERE
PUSH_ID IN
<foreach collection='list' item = 'item' open= '(' separator=',' close =')'>
#{item}
</foreach>
</update>
10.2每行更新的值不同
<update id="updateActWeight" parameterType="java.util.List">
<foreach collection='list' item = 'item' index = 'index' separator=";">
UPDATE t_wl_bill_detail SET ACT_WEIGHT = #{item.actWeight}
WHERE MATERIAL_ID = #{item.materialId} and BILL_ID = #{item.billId}
</foreach>
</update>
如果报错,需要在连接mysql的url上加 &allowMultiQueries=true;
11.动态SQL对于数值的判断
当planSendGoodsAmount 的值为0时,下边标签的判断结果是false
<if test="planSendGoodsAmount != null and planSendGoodsAmount != ''"> PLAN_SEND_GOODS_AMOUNT = (IFNULL(PLAN_SEND_GOODS_AMOUNT, 0) + #{planSendGoodsAmount}), </if>
其中 planSendGoodsAmount 字段是BigDecimal类型的。原因在于planSendGoodsAmount !=''这个条件,值为0时会返回false。 因为Mybatis解析表达式使用的是OGNL,对于数值的判断如下:
If the object is a Number, its double-precision floating-point value is compared with zero; non-zero is treated as true, zero as false;
可见,对象是一个Number类型,值为0时将被解析为false。解决办法就是去掉第二个判断条件,因为数值类型的值没必要和''做比较。
未完待续。。。