MyBatis的XML实现方法

在使用mybatis框架进行开发时,有两种开发方式,一种是使用注解进行开发,另外一种就是使用xml进行开发.这篇文章主要介绍怎么通过xml的方式进行开发.

1. XML介绍

XML(Extensible Markup Language)是一种类似于 HTML,但是没有使用预定义标记的语言.因此,可以根据自己的设计需求定义专属的标记.这是一种强大将数据存储在一个可以存储,搜索和共享的格式中的方法.最重要的是,因为 XML 的基本格式是标准化的,如果你在本地或互联网上跨系统或平台共享或传输 XML,由于标准化的 XML 语法,接收者仍然可以解析数据.我们可以简单将其理解为一种由标签构成的语言,我们在使用xml进行开发时,需要使用一些特定的标签.

2. XML文件使用前的配置

我们需要在application.properties或者application.yml文件中(配置格式不同)配置xml文件的地址,下面我在application.yml文件中配置改信息.我们需要在这个路径下,创建一个xml进行使用:

mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

而在xml文件中,我们需要写入一些信息,如下图所示:

<?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.lzq.mybatis.demo.mapper.UserInfoXMLMapper">
    
</mapper>

其中比较重要的信息是mapper标签中的namespace属性,它的值是到这个xml绑定的mapper的路径.

其他的信息我们直接赋值粘贴即可.

3. XML开发的具体方法

使用xml开发仍然需要一个mapper接口,需要给这个接口加上@Mapper注解,就交给了Spring进行动态管理.我们就先在这个mapper接口中把方法定义出来,具体的实现在xml中.

@Mapper
public interface UserInfoXMLMapper {

}

3.1 增删查改(CRUD)

我们这里先以插入作为示例.

先在这个mapper接口中写出insert方法,因为是插入一个对象,所以我们就需要传入一个对象userInfo作为参数.

@Mapper
public interface UserInfoXMLMapper {
    public void insertUser(UserInfo userInfo);

}

然后,在xml文件中使用<insert>,这对标签把sql语句包括住,我们仍然使用#{ }的方式获取到参数,注意我们需要在id中指定出这个sql语句和哪一个方法是对应的,id要与方法名一致,如下图所示:

 <insert id="insertUser">
        insert into userinfo(username,password,age,gender,phone)
          values(#{username},#{age},#{gender},#{phone})
 </insert>

这就是最基本的xml开发方式,如果是删查改,只需要把最外层的标签对应改成<delete>,<select>,<update>,然后其中对应的sql语句也改一下即可.

但是自己去写稍显复杂,我们可以先在mapper中声明方法,此时方法会报错:

告诉你这个方法没有在xml中定义/实现,这时我们可以直接Alt+Shift+Enter快捷键让IDEA快速帮我们在xml中生成一个对应的语句.如果命名比较好,像这个insertUser方法,它会自动帮你生成insert标签.使用query/select,则会自动生成select标签.否则,它会让你选择需要生成哪一个标签.

3.2 结果映射

比如在表中有一个delete_flag字段,表示逻辑删除与否.但是我在实体类中设置的属性是deleteFlag,它们的名称不是完全相同的.如果我们进行一个查询所有信息的语句,得到的结果会发现deleteFlag这个属性出现的值都是0,这其实就是没有正确地把查询到的数据转换成对象.进行使用注解的方式我们可以通过起别名的方法解决,也可以通过配置驼峰自动转换的方式处理.而在xml中,我们就可以使用结果映射的方式进行实现.

<mapper namespace="com.lzq.mybatis.demo.mapper.UserInfoXMLMapper">

    <resultMap id="BaseMap" type="com.lzq.mybatis.demo.model.UserInfo">
        <id column="id" property="id"></id>
        <result column="delete_flag" property="deleteFlag"></result>
        <result column="create_time" property="createTime"></result>
        <result column="update_time" property="updateTime"></result>
    </resultMap>

</mapper>

resultMap中需要对这个结果映射集命名,并指定它对应的实体类的路径.

column中的值是表的字段名,property的值是实体类中的属性名.

3.3 参数绑定问题

 <select id="queryAllUser" resultType="com.lzq.mybatis.demo.model.UserInfo">
        select username,password, age, gender, phone from userinfo 
          where id = #{value}
 </select>

这里我们想要根据id来进行查询操作,但是获取的是一个value值,这个时候只要我们给它传递一个整型参数,这个value就能接收到进行查询:

public List<UserInfo> queryAllUser(Integer id);

但是如果我们有两个参数,情况就会截然不同:

public List<UserInfo> queryAllUser(Integer id1,Integer id2);

这种情况下,由于没有参数名称叫做value,它就不知道到底要接收哪一个参数,所以会报错.同时IDEA会告诉我们,可以把value改成四个有效的名称:id1,id2,param1,param2.可见IDEA会根据我们参数的名称自动生成能够在sql语句中对应的参数的名称.

解决方法有三个:

1. 把id1,id2中其中一个参数名称改为value.

2. 把value的名称改为上述四个有效的名称之一.

3. 在id1或者id2前加上@Param("value")这个注解进行参数绑定.

为了避免这种情况的发生,我们推荐sql语句中的参数名称和方法中参数的名称保持一致.

如果我们绑定了一个对象时,我们的取值方式就需要变成:#{绑定的对象名.属性名},十分地复杂,应该确保表中字段名和实体类属性名尽可能相同.示例如下:

<update id="update">
        update userinfo set username = #{userInfo.username} where username = 'admin'
</update>

3.4 #{}和${}在获取值时的区别

这部分知识并不仅仅属于xml开发,而是属于mybatis框架.我们这里使用xml开发的形式进行演示.

比如,我在mapper接口中声明了两个方法如下:

public List<UserInfo> queryByName1(String username);
public List<UserInfo> queryByName2(String username);

在xml中写出对应的sql语句:

<select id="queryByName1" resultType="com.lzq.mybatis.demo.model.UserInfo">
    select * from userinfo where username = #{username}
 </select>
 <select id="queryByName2" resultType="com.lzq.mybatis.demo.model.UserInfo">
    select * from userinfo where username = ${username}
 </select>

我们可以测试一下这两个方法的区别,可以在mapper接口中右键,点击generate-test,直接就会生成对应的测试类,在测试类中需要加上@SpringBootTest这个注解,同时需要注入对应的mapper接口:

package com.lzq.mybatis.demo.mapper;

import com.lzq.mybatis.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserInfoXMLMapperTest {
    @Autowired
    private UserInfoXMLMapper userInfoXMLMapper;
    @Test
    void queryByName1() {
        userInfoXMLMapper.queryByName1("admin");
    }

    @Test
    void queryByName2() {
        userInfoXMLMapper.queryByName2("admin");
    }
}

接下来,我们来测试一下两个方法到底有什么区别,为了能够直观地看到查询的过程,在application.yml文件中加上下面的配置信息,可以打印出日志:

mybatis:
  configuration: # 配置打印 MyBatis⽇志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

queryByName1:

queryByName2:

会发现第一个方法成功执行了,第二个方法报错了(BadSQLGrammarException)表示sql语句的语法错误.

我们可以看到两个方法运行日志中,第一个方法Preparing中参数的位置是?,表示一个占位符,这其实就是一个预编译的sql语句;但是第二个Preparing中参数的位置直接填上了参数admin,这说明它是一个即时的sql语句.第一个方法把参数带入时,自动地给admin加上了' ',表示它是一个值;第二个语句则没有给admin加上' ',这样admin就被理解为是一个关键字,就引发了sql语法错误.

这样看来,好像我们所有情况都使用#{ }的方式获取不就避免这个问题了吗?但是,我们有时传入的参数可能就是作为sql语句的关键字,比如在排序的时候,我们给出一个语句:

select * from userinfo where age = 22 order by id desc; 

这里的desc就表示降序,那如果我们要决定是升序还是降序地排列数据,就需要传入desc这个参数,它不同于上面的admin,给这个desc加上' '反而会报语法错误,一次这时就需要使用${ }的方式进行参数的获取.

但是,这又会引入新的问题:sql注入.由于使用${ }的方式不会给获取到的参数自动加上' ',那么如果别人给我们传的字符串不在我们预期内呢?比如,传入一个";xxxxx",直接给我们一个分号结束了这个sql语句,后面的sql语句他就可以自己写了,对我们数据的安全有威胁,这就是sql注入问题.

还有在使用like模糊查询时,直接使用#{}会报错:

where username like '%#{key}%'

但是使用${}就不会报错,此时为了解决sql注入的问题,就可以使用内置函数concat():

 where username like concat('%',#{key},'%')

sql注入的预防我们主要可以通过后端的逻辑判断加以限制,这里不过多赘述.

3.5 更多标签

if标签

我们来思考这样一个sql语句:

<select id="queryByConditions" resultType="com.lzq.mybatis.demo.model.UserInfo">
        select * from userinfo where username = #{username}
            and password = #{password} and age = #{age}
</select>

如果username,password和age三个参数都不为空,当然是可以执行成功的,但是如果其中一个不为空呢?它就获取不到参数数据,所以这一部分的判断就无效了,那么有没有什么办法可以让我们动态地操作,如果为空就不拼接这段sql语句.if标签可以帮我们完成这一操作.下面是if标签的使用:

<select id="queryByConditions" resultType="com.lzq.mybatis.demo.model.UserInfo">
        select * from userinfo where
        <if test="username!=null">
            username = #{username}
        </if>
        <if test="password!=null">
            and password #{password}
        </if>
        <if test="age!=null">
            and age = #{age}
        </if>
 </select>

if标签里面有test,里面的值是Java实体类的属性的判断信息.

但是这么写了之后,会发现如果username为空,前面一段不拼接,password不为空,where后面就会多出来一个and,这也会导致sql语句的语法错误.有没有办法能够解决这个问题呢?

trim标签

trim标签可以帮助我们解决这个难搞的问题.

trim标签里有四个属性:prefix,suffix,prefixOverrides,suffixOverrides.

prefix和suffix分别表示在这部分语句前面或后面加上某个符号,prefixOverrides和suffixOverrides分别表示在这部分语句前面或后面去除某个符号.比如,我们这种情况就需要去除前面可能多出来的and.

<select id="queryByConditions" resultType="com.lzq.mybatis.demo.model.UserInfo">
    <trim prefixOverrides="and">
         select * from userinfo where
        <if test="username!=null">
            username = #{username}
        </if>
        <if test="password!=null">
            and password #{password}
        </if>
        <if test="age!=null">
            and age = #{age}
        </if>
    <trim>       
</select>
where标签

where标签用来帮助我们少写一个where关键字,并且在后面参数全部为空时,会自动地被去除,预防了sql语法错误.

    <select id="queryByConditions" resultType="com.lzq.mybatis.demo.model.UserInfo">
        select * from userinfo
        <where>
            <trim suffixOverrides="," prefixOverrides="and">
                <if test="username!=null">
                     username = #{username}
                </if>
                <if test="password!=null">
                    and password #{password}
                </if>
                <if test="age!=null">
                    and age = #{age}
                </if>
                <if test="gender!=null">
                    and gender = #{gender}
                </if>
                <if test="phone!=null">
                    and phone = #{phone}
                </if>
            </trim>
        </where>
    </select>
set标签

set标签用来帮助我们少写一个set关键字,并且在后面参数全部为空时,会自动地被去除,预防了sql语法错误.

    <update id="update">
        update userinfo 
        <set>
            username = #{username}
        </set>
        <where>
            username = 'admin'
        </where>
    </update>
foreach标签

遍历集合中的每一个对象进行批量操作,foreach标签中的几个属性:

collection:绑定方法参数中的集合,如List,Set,Map或数组对象

item:遍历时的每⼀个对象

open:语句块开头的字符串

close:语句块结束的字符串

separator:每次遍历之间间隔的字符串

 <delete id="deleteByIds">
     delete from userinfo where id in
     <foreach collection="ids" item="id" separator="," open="(" close=")">
         #{id}
     </foreach>
 </delete>
include标签

include标签是用来防止我们重复多次地写一些相同冗余的语句的标签,类似于把代码块抽象成一个方法,方便我们直接调用.

 <sql id="allColumn">
     id, username, age, gender, phone, delete_flag, create_time, update_time
 </sql>
 <select id="queryAllUser" resultMap="BaseMap">
     select
     <include refid="allColumn"></include>
     from userinfo
 </select>

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值