【MyBatis】映射器&配置|注解完成CRUD(三)

举报
观止study 发表于 2023/05/31 17:00:53 2023/05/31
【摘要】 上一篇我们学习了如何使用Mapper代理开发,核心配置文件,但却始终没讲SQL映射文件,接下来便让我们一起来认识一下映射文件,再学习一下如何利用此完成常用CRUD操作,并介绍一下使用过程中涉及的一些知识点。 一.XML映射器 (1) 概述MyBatis官方文档中有提到MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它...

上一篇我们学习了如何使用Mapper代理开发,核心配置文件,但却始终没讲SQL映射文件,接下来便让我们一起来认识一下映射文件,再学习一下如何利用此完成常用CRUD操作,并介绍一下使用过程中涉及的一些知识点。

一.XML映射器

(1) 概述

MyBatis官方文档中有提到MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码
image.png

我们只需要在对应增删改查标签下编写SQL语句即可,对于参数设置等操作,它提供了丰富的属性选项来帮助我们配置。例如:
image.png

(2) resultMap结果映射

resultMap结果映射是 MyBatis 最强大的特性,如果对其理解透彻,许多复杂的映射问题都能迎刃而解。
image.png

接下来我们通过一个小案例学习它的用处:

当我们在进行查询操作时,如果实体类属性名 和 数据库表列名 不一致时,MyBatis不能自动封装数据。有些人可能会想,我将名称设置成一样不就行了,问题时,表不一定是你创建的,而且很多情况下,数据库中比较倾向下划线分割而Java中却是驼峰命名。

名称不一致:
image.png

名称一致:
image.png

  • 解决此类问题我们有两种方法:
  1. 起别名:在SQL语句中,对不一样的列名起别名,令别名和实体类属性名一样,即可
    image.png

这样可以很快解决问题,但是我们想,如果我们要写数十个SQL岂不是每个SQL语句都要重复的as as?这繁琐的工作量…还有一种利用<sql>片段的方法优化,比较鸡肋就不赘述。

  1. resultMap映射:定义<resultMap> 对不一致的属性名和列名进行映射
<!--id属性:映射唯一标识 ,type:映射的类型-->
<resultMap id="brandResultMap" type="brand">
<!--id 用于完成主键字段的映射,如果相同可以不用写-->
  <id property="id" column="brand_id" />
<!--    result 用于一般字段的映射,-->
<!--    property:实体类属性名称-->
<!--    column:表字段名称-->
  <result property="brandName" column="brand_name"/>
  <result property="companyName" column="company_name"/>
</resultMap>

然后需要在引用它的语句中将 resultType 属性改为 resultMap 属性就行了

<select id="selectAll" resultMap="brandResultMap">
  select * from tb_brand
</select>

至此就不用再理会 实体类属性名 和 数据库表列名 不一致的问题啦。是不是比之第一种简洁很多?

如上便是resultMap的一种简单用法,更多复杂使用可查阅官方文档,上面给出许多种用法~

二.CRUD引入

  • 使用MyBatis完成各项操作基本只需要如下三步即可:

    • 在Mapper接口中编写xxx方法
    • 在SQL映射文件(xml)中编写实现操作的SQL语句
    • 调用执行方法
  • 关键在于我们需要根据业务的不同分析:

    • SQL语句如何写
    • 是否需要传递参数
    • 返回值应该是什么类型

接下来,让我们一起基于上一节搭建的Mapper代理开发环境,先通过配置文件实现MyBatis的增删改查操作,再一起看看通过注解开发如何简化一些流程。
image.png

三.通过配置文件开发

(1) 查询

我们可以在<select>标签中完成查询语句的编写~

(1.1) 查询所有数据

  1. 根据业务分析,我们可以在Mapper接口中编写如下方法:
List<Brand> selectAll();

不需要传递参数,返回值应该为一个User集合。

  1. 在映射文件(xml)中编写实现业务SQL语句:
<select id="selectAll" resultType="User">
	select * from tb_user;
</select>

可以通过上一篇所讲MyBatisX插件快速生成外层标签,自己写业务SQL即可。

  1. 调用方法进行测试
public class MyBatisDemo {
    public static void main(String[] args) {
        try {
            // 1. 加载mybatis的核心配置文件,获取SqlSessionFactory
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 2. 获取SqlSession对象,用于执行sql
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 3.通过SqlSession的getMapper方法获取 Mapper接口的代理对象
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
			// 4.使用接口中编写的方法
			List<User> userList = userMapper.selectAll();
			// 5.打印查看结果
			System.out.println(userList);
            // 4.释放资源
            sqlSession.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

由于后续使用步骤基本一致,除了4,5步代码都将省略书写。

(1.2) 查询单个(参数占位符介绍)

  1. 根据业务分析,我们可以在Mapper接口中编写如下方法:
Brand selectById(int id);

我们可以通过id获取结果,因此返回值应该为单个User对象。

  1. 在映射文件(xml)中编写实现业务SQL语句:
<select id="selectById" parameterType="int" resultType="User">
	select * from tb_user where id = #{id};
</select>

parameterType:用于设置参数类型,该参数可以省略,MyBatis 可以自动推断出。

#{} 表示参数占位符,我们使用时传递过来的数值会将其替换掉,分为如下两种:

  • #{}:执行SQL时,会将#{}占位符替换为?,将来自动设置参数值,防止SQL注入问题, 底层使用的是 PreparedStatement,推荐使用√
    image.png

  • ${}:拼接SQL。会存在SQL注入问题,底层使用的是 Statement,不推荐使用
    image.png

  1. 调用方法进行测试
// ...省略

// 4.使用接口中编写的方法
User user = userMapper.selectById(2);
// 5.打印查看结果
System.out.println(user);

(1.3) 多条件查询(三种方式)

根据业务分析,我们可以知道查询条件往往会是多个参数,返回结果也应该为对应实体类集合。

例如,我们可以在映射文件中编写如下多条件查询代码:

    <select id="selectByCondition" resultType="User">
        select *
        from tb_user
        where username = #{username}
          and gender = #{gender}
          and addr = #{addr}
    </select>

接下来让我们一起来看看三种不同的接口使用形式~

(1.3.1) 散装参数

当我们直接写参数名称时,MyBatis无法得知我们的参数与查询条件对应情况,因此需要使用@Param("SQL中的参数名称")将参数与SQL条件字段#{xxx}进行映射。

List<User> selectByCondition(@Param("username") String username, @Param("gender") String gender, @Param("addr") String addr);

调用方法进行测试

// ...省略

// 4.直接使用接口中编写的方法即可
List<User> userList = userMapper.selectByCondition("zhangsan", "男", "北京");
// 5.打印查看结果
System.out.println(userList);

(1.3.2) 实体类封装参数

我们也可以将参数都设置在实体类中,并保证SQL中的参数名#{xxx}和 实体类属性名对应上,将实体类作为参数传递即可。

List<User> selectByCondition(User user);

调用方法进行测试

// ...省略

//4.直接使用接口中编写的方法即可
User user = new User();
user.setUsername("zhangsan");
user.setGender("男");
user.setAddr("北京");
List<User> userList = userMapper.selectByCondition(user);
// 5. 打印结果
System.out.println(userList);

(1.3.3) map集合

我们还可以通过map集合的方式对参数进行传递,必须确保key值与SQL中的参数名#{xxx}保持一致

List<User> selectByCondition(Map map);

调用方法测试

//4.直接使用接口中编写的方法即可
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("username", "zhangsan");
hashMap.put("gender", "男");
hashMap.put("addr", "北京");
List<User> userList = userMapper.selectByCondition(hashMap);
// 5. 打印结果
System.out.println(userList);

(1.4) 动态SQL查询优化

测试上述多条件查询可以发现,我们只有同时写上三个条件时才能查询到结果,少设置部分则无法查询到结果,这意味着我们需要重复写查询新的SQL,例如条件1,3,条件2,3,条件1,2。我的天,排列组合,当参数过多时…
image.png

我们可以使用动态SQL来优化这一过程,使得我们有更好的体验。

定义:SQL语句会随着用户的输入或外部条件的变化而变化

官网文档中这样说道:动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

详细使用可以查询官方文档~
image.png

我们可以对上述sql使用动态SQL语法进行如下改造,这样便可自由决定传递的参数个数:

    <select id="selectByCondition" resultType="User">
        select *
        from tb_user
        <where>
            <if test="username!=null">
                username = #{username}
            </if>
            and gender like #{gender}
            <if test="gender!=null">
                and gender like #{gender}
            </if>
            <if test="addr!=null">
                and addr like #{addr}
            </if>
        </where>
    </select>
  • <if>:用于判断参数是否有值,使用test属性进行条件判断

  • <where> 标签替换 where 关键字,解决第一个条件不需要逻辑运算符问题

动态SQL除了可以用于优化多条件查询,还可以简化单条件查询,例如碰到下述场景,我们可以写多个单条件查询SQL,但是使用动态SQL却可以简化这一过程。
image.png

编写接口

List<User> selectByConditionSingle(User user)

编写配置文件:

choose (when, otherwise):选择,类似于Java 中的 switch 语句,when->case,otherwise->default

    <select id="selectByCondition" resultType="User">
        select *
        from tb_user
        where
        <choose>
            <when test="username!=null">
                username like #{username}
            </when>
            <when test="gender!=null">
                gender like #{gender}
            </when>
            <when test="addr!=null">
                addr like #{addr}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </select>

如果不需要otherwise,为了防止报错,也可以写为如下形式:

    <select id="selectByCondition" resultType="User">
        select *
        from tb_user
        <where>
            <choose>
                <when test="username!=null">
                    username like #{username}
                </when>
                <when test="gender!=null">
                    gender like #{gender}
                </when>
                <when test="addr!=null">
                    addr like #{addr}
                </when>
            </choose>
        </where>
    </select>

测试:

// 省略...

//4.直接使用接口中编写的方法即可
User user = new User();
user.setUsername("zhangsan");
List<User> userList = userMapper.selectByConditionSingle(user);
// 5. 打印结果
System.out.println(userList);

至此我们已经简单了解了动态SQL的使用,更多用法可查看官方文档~

(2) 添加

我们可以在<insert>标签中完成插入语句的编写~

(2.1) 添加

  1. 根据业务分析,我们可以在Mapper接口中编写如下方法:
void add(User user);

参数应该为除了id之外的所有数据

  1. 在映射文件(xml)中编写实现业务SQL语句:
    <insert id="add">
        insert into tb_user (username, password, gender, addr)
        values (#{username}, #{password}, #{gender}, #{addr});
    </insert>
  1. 运行测试
//4.直接使用接口中编写的方法即可
User user = new User();
user.setPassword("666");
user.setGender("男");
user.setAddr("长安");
user.setUsername("李白");
userMapper.add(user);
  • 测试上述代码,我们打开数据库可以发现数据并没有插入成功,难道是我们哪里写错了?

  • 并没有,这是因为MyBatis默认开启事务:

    • 进行增删改操作后需要使用 sqlSession.commit(); 手动提交事务
    //4.直接使用接口中编写的方法即可
    User user = new User();
    user.setPassword("666");
    user.setGender("男");
    user.setAddr("长安");
    user.setUsername("李白");
    userMapper.add(user);
    // 提交事物
    sqlSession.commit();
    
    • 也可以设置为自动提交事务(关闭事务)openSession(true):
    // 2. 获取SqlSession对象,关闭事务
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //...
    // 后续操作不用再手动提交事物
    

(2.2) 添加(返回主键)

很多时候,在数据添加成功后,我们需要获取插入数据库数据的主键,比如添加用户后,我们可能需要获取其id信息.

  1. 根据业务分析,我们可以在Mapper接口中编写如下方法:
void add(User user);

参数应该为除了id之外的所有数据

  1. 在映射文件(xml)中编写实现业务SQL语句:
 <insert id="add" useGeneratedKeys="true" keyProperty="id">
        insert into tb_user (username, password, gender, addr)
        values (#{username}, #{password}, #{gender}, #{addr});
    </insert>

注意:keyProperty指定的是实体类的属性名,表示将获取到的主键值封装到哪儿个属性里

  1. 运行测试
v//4.直接使用接口中编写的方法即可
User user = new User();
user.setPassword("666");
user.setGender("男");
user.setAddr("长安");
user.setUsername("李白");
userMapper.add(user);
// 打印返回id值
System.out.println(user.getId());

需要注意的是,返回的主键重新封装在了传递的User实体类中。

(3) 修改

我们可以在<update>标签中完成修改语句的编写~

(3.1) 修改全部字段

  1. 根据业务分析,我们可以在Mapper接口中编写如下方法:
void update(User user);

参数应该为除了id之外的所有数据

  1. 在映射文件(xml)中编写实现业务SQL语句:
    <update id="update">
        update tb_user
        set username = #{username},
            password = #{password},
            ordered  = #{ordered},
            gender   = #{gender},
            addr     = #{addr}
        where id = #{id};
    </update>
  1. 运行测试
            //4.直接使用接口中编写的方法即可
            User user = new User();
            user.setId(10);
            user.setPassword("66");
            user.setGender("男");
            user.setAddr("安");
            user.setUsername("白");
            userMapper.update(user);

注意:如果没有关闭事务,此处仍需要手动提交事务且必须填充上每个字段,否则会将为设置的字段置为null!

(3.2) 修改部分字段

在上述修改全部代码中,与之前多条件查询类似,为了简化类似SQL的编写,我们可以使用动态SQL的语法来简化开发.

  1. 编写一个与上述一致的接口
void update(User user);
  1. 在映射文件(xml)中优化上述修改SQL语句:
    <update id="update">
        update tb_user
        <set>
            <if test="username != null">
                username = #{username},
            </if>
            <if test="password != null">
                password = #{password},
            </if>
            <if test="gender != null">
                gender = #{gender},
            </if>
            <if test="addr != null">
                addr = #{addr},
            </if>
        </set>
        where id = #{id};
    </update>

<set> 标签替换 set关键字,解决末尾,号多于引起的问题

  1. 运行测试
            //4.直接使用接口中编写的方法即可
            User user = new User();
            user.setId(10);
            user.setPassword("66");
            user.setGender("男");
            user.setUsername("白");
            userMapper.update(user);

注意:如果没有关闭事务,此处仍需要手动提交事务,可以只修改部分想修改的字段,不会影响其他字段!

(4) 删除

我们可以在<delete>标签中完成删除语句的编写~

(4.1) 删除单个

  1. 根据业务分析,我们可以在Mapper接口中编写如下方法:
    void deleteById(@Param("id") int id);

参数为待删除字段的标识

  1. 在映射文件(xml)中编写实现业务SQL语句:
    <delete id="deleteById">
        delete
        from tb_user
        where id = #{id}
    </delete>
  1. 测试
//省略...

//4.直接使用接口中编写的方法即可
 userMapper.deleteById(8);

注意:如果没有关闭事务,此处仍需要手动提交事务!

(4.2) 批量删除

  1. 根据业务分析,我们可以在Mapper接口中编写如下方法:
void deleteByIds(@Param("id") int id);

参数为多个需要被删除的数据标识

  1. 在映射文件(xml)中编写实现业务SQL语句:
    <delete id="deleteByIds">
        delete from tb_user
        where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">#{id}</foreach>
    </delete>

由于我们需要删除的个数是不确定的,因此我们仍然可以使用动态SQL的一些语句编写形成正确的SQL语句。

foreach 标签用来迭代任何可迭代的对象(如数组,集合)。

  • collection 属性:
    • mybatis会将数组参数,封装为一个Map集合。
      • 默认:array = 数组
      • 使用@Param注解改变map集合的默认key的名称
  • item 属性:本次迭代获取到的元素。
  • separator 属性:集合项迭代之间的分隔符。foreach 标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。
  • open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
  • close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
  1. 测试
//4.直接使用接口中编写的方法即可
int[] ids = {1, 3, 4};
 userMapper.deleteByIds(ids);

四.通过注解开发

通过上面的学习我们已经学会了如何在配置文件中编写代码,其实注解开发与配置文件开发的区别就是编写SQL的位置不同。
image.png

官方文档表明:使用注解开发简单的SQL语句可以使我们的代码更加简洁,但是对应复杂的SQL配置文件依旧是它的最好选择,我们可以在注解和配置文件中自由切换~

<Select><Insert><Update><Delete>所对应,MyBatis同样提供了我们几个让我们用于注解开发@Select@Insert@Update@Delete

(1) 查询

使用注解开发将无需在xml映射文件中重复编写代码,对于简单SQL还是一件比较舒服的事~

    @Select("select from tb_user")
    List<User> selectAll();

    @Select("select from tb_user where id = #{id}")
    User selectById(int id);

(2) 插入

    @Insert("insert into tb_user (username, password, gender, addr) values (#{username}, #{password}, #{gender}, #{addr})")
    int add(User user);

(3) 修改

    @Update("update tb_user  set username = #{username},password = #{password}, gender = #{gender},addr = #{addr} where id = #{id}")
    void update(User user);

(4) 删除

    @Delete("delete from tb_user  where id = #{id}")
    void deleteById(@Param("id") int id);

可以看到删除和查询这种短小的语句用注解写起来还是挺舒服的,但是换成插入和修改语句便不是特别友好了,因此我们可以按官方文档所述,不必拘泥于一种方式,而是根据自己的情况选择合适的方式~

五.参数传递流程(补充)

Mybatis 接口方法中可以接收各种各样的参数,如下:

  • 多个参数
  • 单个参数:单个参数又可以是如下类型
    • POJO 类型
    • Map 集合类型
    • Collection 集合类型
    • List 集合类型
    • Array 类型
    • 其他类型

(1) 多个参数

如下面的代码,就是接收两个参数,而接收多个参数需要使用 @Param 注解,那么为什么要加该注解呢?这个问题要弄明白就必须来研究Mybatis 底层对于这些参数是如何处理的。

User select(@Param("username") String username,@Param("password") String password);
<select id="select" resultType="user">
	select *
    from tb_user
    where 
    	username=#{username}
    	and password=#{password}
</select>

我们在接口方法中定义多个参数,Mybatis 会将这些参数封装成 Map 集合对象,值就是参数值,而键在没有使用 @Param 注解时有以下命名规则:

  • 以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。如:

    map.put(“arg0”,参数值1);

    map.put(“arg1”,参数值2);

  • 以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。如:

    map.put(“param1”,参数值1);

    map.put(“param2”,参数值2);

代码验证:

  • UserMapper 接口中定义如下方法

    User select(String username,String password);
    
  • UserMapper.xml 映射配置文件中定义SQL

    <select id="select" resultType="user">
    	select *
        from tb_user
        where 
        	username=#{arg0}
        	and password=#{arg1}
    </select>
    

    或者

    <select id="select" resultType="user">
    	select *
        from tb_user
        where 
        	username=#{param1}
        	and password=#{param2}
    </select>
    
  • 运行代码结果如下
    image.png

    在映射配合文件的SQL语句中使用用 arg 开头的和 param 书写,代码的可读性会变的特别差,此时可以使用 @Param 注解。

在接口方法参数上使用 @Param 注解,Mybatis 会将 arg 开头的键名替换为对应注解的属性值。

代码验证:

  • UserMapper 接口中定义如下方法,在 username 参数前加上 @Param 注解

    User select(@Param("username") String username, String password);
    

    Mybatis 在封装 Map 集合时,键名就会变成如下:

    map.put(“username”,参数值1);

    map.put(“arg1”,参数值2);

    map.put(“param1”,参数值1);

    map.put(“param2”,参数值2);

  • UserMapper.xml 映射配置文件中定义SQL

    <select id="select" resultType="user">
    	select *
        from tb_user
        where 
        	username=#{username}
        	and password=#{param2}
    </select>
    
  • 运行程序结果没有报错。而如果将 #{} 中的 username 还是写成 arg0

    <select id="select" resultType="user">
    	select *
        from tb_user
        where 
        	username=#{arg0}
        	and password=#{param2}
    </select>
    
  • 运行程序则可以看到错误
    image.png

结论:以后接口参数是多个时,在每个参数上都使用 @Param 注解。这样代码的可读性更高。

(2) 单个参数

  • POJO 类型

    可以直接使用。要求 属性名参数占位符名称 一致

  • Map 集合类型

    可以直接使用。要求 map集合的键名参数占位符名称 一致

  • Collection 集合类型

    Mybatis 会将集合封装到 map 集合中,如下:

    map.put(“arg0”,collection集合);

    map.put(“collection”,collection集合;

    可以使用 @Param 注解替换map集合中默认的 arg 键名。

  • List 集合类型

    Mybatis 会将集合封装到 map 集合中,如下:

    map.put(“arg0”,list集合);

    map.put(“collection”,list集合);

    map.put(“list”,list集合);

    可以使用 @Param 注解替换map集合中默认的 arg 键名。

  • Array 类型

    Mybatis 会将集合封装到 map 集合中,如下:

    map.put(“arg0”,数组);

    map.put(“array”,数组);

    可以使用 @Param 注解替换map集合中默认的 arg 键名。

  • 其他类型

    比如int类型,参数占位符名称 叫什么都可以。尽量做到见名知意

六.特殊字符处理(补充)

我们肯定会在SQL语句中写特殊字符,比如某一个字段大于某个值,如下图
image.png

可以看出报错了,因为映射配置文件是xml类型的问题,而 > < 等这些字符在xml中有特殊含义,不能直接使用。所以此时我们需要将这些符号进行转义,可以使用以下两种方式进行转义:

  • 转义字符

    下图的 &lt; 就是 < 的转义字符。
    image.png

  • <![CDATA[内容]]>可将内容直接写在这其中

image.png

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。