引入 Mapper
前面我们所写的增删改查是存在问题的。主要问题就是冗余代码过多,模板化代码过多。发现它有很多可以优化的地方。每个方法中都要获取 SqlSession,涉及到增删改的方法,还需要 commit,SqlSession 用完之后,还需要关闭,sqlSession 执行时需要的参数就是方法的参数,sqlSession 要执行的 SQL ,和 XML 中的定义是一一对应的。这是一个模板化程度很高的代码。
既然模板化程度很高,我们就要去解决它,原理很简单,就是前面 Spring 中所说的动态代理。我们可以将当前方法简化成 一个接口:
public interface UserMapper {
List<User> getAllUser();
User findUserById(Long id);
Integer deleteUserById(Long id);
Integer addUser(User user);
Integer updateUserById(User user);
}
这个接口对应的 Mapper 文件如下(注意,UserMapper.xml 和 UserMapper 需要放在同一个包下面)
<?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.java.lang.mapper.UserMapper">
<select id="getAllUser" resultType="com.java.lang.pojo.User"> /*resultType 为返回值类型*/
select *
from user;
</select>
<insert id="addUser" parameterType="com.java.lang.pojo.User">
insert into user (username, address)
values (#{userName}, #{address});
</insert>
<delete id="deleteUserById" parameterType="Long">
delete
from user
where id = #{id};
</delete>
<update id="updateUserById" parameterType="com.java.lang.pojo.User">
update user
set username = #{userName}
where id = #{id};
</update>
<select id="findUserById" parameterType="Long" resultType="com.java.lang.pojo.User">
select *
from user
where id = #{id};
</select>
<!--当主键为UUID 时-->
<!-- <insert id="addUser" parameterType="com.java.lang.pojo.User">
<selectKey resultType="String" keyProperty="id" order="BEFORE">
select uuid();
</selectKey>
insert into user
(id,username,address)
values (#{id},#{username},#{address})
</insert>-->
</mapper>
@Test
public void selectUserById2() {
UserMapper userMapper = session.getMapper(UserMapper.class);
// System.out.println(userMapper); 这个userMapper为Mybatis提供的代理对象
User user = userMapper.findUserById(3L);
System.out.println("user = " + user);
}
Mapper 映射文件
1.parameterType
这个表示传入的参数类型。
$
和#
这是一个非常非常高频的面试题,虽然很简单。在面试中,如果涉及到 MyBatis,一般情况下,都是这个问题。
在 MyBatis 中,我们在 mapper 引用变量时,默认使用的是 #,像下面这样:
<select id="getUserById" resultType="org.javaboy.mybatis.model.User">
select * from user where id=#{id};
</select>
除了使用 # 之外,我们也可以使用 $ 来引用一个变量:
<select id="getUserById" resultType="org.javaboy.mybatis.model.User">
select * from user where id=${id};
</select>
在旧的 MyBatis 版本中,如果使用 KaTeX parse error: Expected 'EOF', got '#' at position 40: …MyBatis 中,无论是 `#̲` 还是 ``,如果只有一个参数,可以不用取别名,如下:
public interface UserMapper {
User getUserById(Integer id);
}
既然 #
和 $
符号都可以使用,那么他们有什么区别呢?
上面这个日志,是 $ 符号执行的日志,可以看到,SQL 直接就拼接好了,没有参数。
下面这个,是 # 执行的日志,可以看到,这个日志中,使用了预编译的方式:
在 JDBC 调用中,SQL 的执行,我们可以通过字符串拼接的方式来解决参数的传递问题,也可以通过占位符的方式来解决参数的传递问题。当然,这种方式也传递到 MyBatis 中,在 MyBatis 中,$ 相当于是参数拼接的方式,而 # 则相当于是占位符的方式。
一般来说,由于参数拼接的方式存在 SQL 注入的风险,因此我们使用较少,但是在一些特殊的场景下,又不得不使用这种方式。其实就是jdbc中Statement和PreparedStatement 的区别。
Mapp 多个参数
当我们传入多个参数时,mapper里面该如何接收?
// 根据ID 修改 名字
int updateUserNameById(String userName, Long id);
<update id="updateUserNameById">
update user
set username = #{username}
where id = #{id};
</update>
然后直接报错:
这里是说,找不到我们定义的 username 和 id 这两个参数。同时,这个错误提示中指明,可用的参数名是 [arg1, arg0, param1, param2],相当于我们自己给变量取的名字失效了,要使用系统提供的默认名字,默认名字实际上是两套体系:
第一套就是 arg0、arg1、、、、
第二套就是 param1、param2、、、
注意,这两个的下标是不一样的。
因此,按照错误提示,我们将参数改为下面这样:
<update id="updateUserNameById">
update user
set username = #{arg0}
where id = #{arg1};
</update>
或者这样:
<update id="updateUserNameById">
update user
set username = #{param1}
where id = #{param2};
</update>
这两种方式,都可以使该方法顺利执行。
但是,默认的名字不好记,容易出错,我们如果想要使用自己写的变量的名字,可以通过给参数添加 @Param 来指定参数名(一般在又多个参数的时候,需要加),一旦用 @Param 指定了参数类型之后,可以省略掉参数类型,就是在 xml 文件中,不用定义 parameterType 了:
// 根据ID 修改 名字
int updateUserNameById(@Param("username") String userName, @Param("id") Long id);
<update id="updateUserNameById">
update user
set username = #{username}
where id = #{id};
</update>
这样定义之后,我们在 mapper.xml 文件中,就可以直接使用 username 和 id 来引用变量了。
3 对象参数
例如添加一个用户:
int addUser(User user);
<insert id="addUser" parameterType="com.java.lang.pojo.User">
insert into user(username, address)
values (#{username},#{address});
</insert>
也可以加@Param :
int addUser(@Param("user") User user);
<insert id="addUser" parameterType="com.java.lang.pojo.User">
insert into user(username, address)
values (#{user.username}, #{user.address});
</insert>
map参数
一般不推荐在项目中使用 Map 参数。如果想要使用 Map 传递参数,技术上来说,肯定是没有问题的。
Integer updateUsernameById(HashMap<String,Object> map);
XML 文件写法如下:
<update id="updateUsernameById">
update user set username = #{username} where id=#{id};
</update>
引用的变量名,就是 map 中的 key。基本上和实体类是一样的,如果给 map 取了别名,那么在引用的时候,也要将别名作为前缀加上,这一点和实体类也是一样的。
两种主键回填的方式
1.(常用的)
<insert id="addUser" parameterType="com.java.lang.pojo.User" useGeneratedKeys="true" keyProperty="id">
/* useGeneratedKeys参数只针对 insert 语句生效,默认为 false;
useGeneratedKeys : 表示如果插入的表id以自增列为主键,则允许 JDBC 支持自动生成主键,并可将自动生成的主键id返回。
keyProperty : 映射到哪里
*/
insert into user(username, address)
values (#{user.username}, #{user.address});
</insert>
2.(不常用的)
<insert id="addUser" parameterType="com.java.lang.pojo.User">
<selectKey keyProperty="id" resultType="Long" order="AFTER">/*after 先执行插入语句,然后再执行这个自带函数*/
select last_insert_id(); /*mysql 自带函数,作用是可以查询到刚刚插入的id*/
</selectKey>
insert into user(username, address)
values (#{username}, #{address});
</insert>
resultType 是返回类型,在实际开发中,如果返回的数据类型比较复杂,一般我们使用 resultMap,但是,对于一些简单的返回,使用 resultType 就够用了。
resultMap
在实际开发中,resultMap 是使用较多的返回数据类型配置。因为实际项目中,一般的返回数据类型比较丰富,要么字段和属性对不上,要么是一对一、一对多的查询,等等,这些需求,单纯的使用 resultType 是无法满足的,因此我们还需要使用 resultMap,也就是自己定义映射的结果集。
<resultMap id="bookMap" type="com.java.lang.pojo.Book"> <!--type 映射到哪一个对象-->
<id property="id" column="id"/> <!--id 标签只有一个 ,property 对象里的字段, column 数据库里的字段,这里关联映射-->
<result property="name" column="b_name"/> <!--其他的字段映射-->
<result property="author" column="author"/>
</resultMap>
<select id="listBook" resultMap="bookMap">
select *
from t_book;
</select>
动态sql
if
是一个判断节点,如果满足某个条件,节点中的 SQL 就会生效。例如分页查询,要传递两个参数,页码和查询的记录数,如果这两个参数都为 null,那我就查询所有。
List<Book> listBookByPage(@Param("start") int start , @Param("size") int size);
<select id="listBookByPage" resultMap="bookMap">
select *
from t_book
<if test="start !=null and size != null">
limit #{start}, #{size};
</if>;
</select>
<choose>
// 根据书名或者作者查询 同时存在时,以书名为准
List<Book> listBookByNameOrAuthor(Book book);
<select id="listBookByNameOrAuthor" resultMap="bookMap" parameterType="com.java.lang.pojo.Book">
select *
from t_book
where 1 = 1
<choose>
<when test="name != null and name != ''">/*如果有多个when 只会满足其中一个,比如第一个满足了,下面的就不走了,类似于switch*/
and b_name = #{name}
</when>
<when test="author != null and author != ''">
and author = #{author}
</when>
<otherwise>and 2 = 2</otherwise><!--<otherwise>如果上述条件都不满足</otherwise> -->
</choose>
</select>
<where>
用 where 节点将所有的查询条件包起来,如果有满足的条件,where 节点会自动加上,如果没有,where 节点也将不存在,在有满足条件的情况下,where 还会自动处理 and 关键字。
// 根据id/name 查询Book 如果都为null ,则返回所有
List<Book> listBookByIdOrName(Book book);
<select id="listBookByIdOrName" resultMap="bookMap" parameterType="com.java.lang.pojo.Book">
select *
from t_book
<where>
<if test="name != null">
and b_name = #{name}
</if>
<if test="id != null">
and id = #{id}
</if>
</where>
</select>
<Set>
/*根据传入的属性更新Book */
int updateBook(Book book);
<update id="updateBook" parameterType="com.java.lang.pojo.Book">
update t_book
<set>
<if test="name != null ">
b_name = #{name},
</if>
<if test="author != null">/* 包含在set种 如果此条件也满足,后面的',' 会被自动去掉*/
author = #{author},
</if>
</set>
where id = #{id}
</update>
<trim>
可以代替where / set
// 根据id/name 查询Book 如果都为null ,则返回所有
List<Book> listBookByIdOrName2(Book book);
代替where 实例
<select id="listBookByIdOrName2" resultMap="bookMap" parameterType="com.java.lang.pojo.Book">
<!-- /*<trim>会自动判断是否需要where以及 是否prefixOverrides前缀需要and*/-->
select *
from t_book
<trim prefix="where" prefixOverrides="and ">
<if test="name != null">
and b_name = #{name}
</if>
<if test="id != null">
and id = #{id}
</if>
</trim>
</select>
代替set 实例:
<update id="updateBook2" parameterType="com.java.lang.pojo.Book">
update t_book
<trim prefix="set" suffixOverrides=", ">
<if test="name != null ">
b_name = #{name},
</if>
<if test="author != null">/* 包含在set种 如果此条件也满足,后面的',' 会被自动去掉*/
author = #{author},
</if>
</trim>
where id = #{id}
</update>
<foreach>
遍历 数组/list
/*根据id集合查询*/
List<Book> listUserByIds(@Param("ids")Long ...ids);
<select id="listUserByIds" resultMap="bookMap">
select *
from t_book where id in
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</select>
遍历对象
/*批量插入*/
int bathAddBooks(@Param("books") List<Book> list);
<insert id="bathAddBooks">
insert into t_book (b_name,author) values /*(xxx,xxx),(xxx,xxx)*/
<foreach collection="books" separator="," item="book">
(#{book.name},#{book.author})
</foreach>
</insert>
遍历map
/*批量更新*/
int bathAddBooks2(@Param("map")Map<String,Object> map,@Param("id") Long id);
<update id="bathAddBooks2">
update t_book
<set>
<foreach collection="map" index="key" item="val" separator=",">
${key} = #{val}
</foreach>
</set>
where id = #{id}
</update>
<bind>
/*根据作者名字查询*/
List<Book> getBooksByAuthorFirstName(@Param("author") String author);
<select id="getBooksByAuthorFirstName" resultMap="bookMap">
<bind name="authorLike" value="author + '%'"/>
/*相当于定义变量*/
select * from t_book
where author like #{authorLike};
</select>
sql 片段
大家知道,在 SQL 查询中,一般不建议写 *,因为 select * 会降低查询效率。但是,每次查询都要把字段名列出来,太麻烦。这种使用,我们可以利用 SQL 片段来解决这个问题。
例如,我们先在 mapper 中定义一个 SQL 片段:
<sql id="Base_Column">
id,usename,address
</sql>
然后,在其他 SQL 中,就可以引用这个变量:
<select id="getUserByIds" resultType="org.javaboy.mybatis.model.User">
select <include refid="Base_Column" /> from user where id in
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</select>