在使用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>