SpringBoot系列—MyBatis(xml使用)

上篇文章:

SpringBoot系列---MyBatis(注解使用)https://blog.csdn.net/sniper_fandc/article/details/148978348?fromshare=blogdetail&sharetype=blogdetail&sharerId=148978348&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目录

[1 基本使用](#1 基本使用)

[1.1 查](#1.1 查)

(1)首先需要在配置文件中添加配置信息

(2)创建Mapper接口

(3)实现xml文件

(4)xml方式的字段属性映射问题

[1.2 增](#1.2 增)

[1.3 删](#1.3 删)

[1.4 改](#1.4 改)

[2 多表联查](#2 多表联查)

[3 #{}和{}区别](#{}和{}区别)

[3.1 #{}是预编译SQL,{}是即时SQL](#{}是预编译SQL,{}是即时SQL)

[3.2 #{}的查询速度比{}查询速度略快](#{}的查询速度比{}查询速度略快)

[3.3 {}存在SQL注入问题](#3.3 {}存在SQL注入问题)

(1)定义

(2)问题原因

[4 {}的使用场景](#4 {}的使用场景)

[4.1 查询结果排序](#4.1 查询结果排序)

[4.2 like模糊查询](#4.2 like模糊查询)

[5 数据库连接池](#5 数据库连接池)

[6 动态SQL](#6 动态SQL)

[6.1 <if>标签](#6.1 <if>标签)

[6.2 <trim>标签](#6.2 <trim>标签)

[6.3 <where>标签](#6.3 <where>标签)

[6.4 <set>标签](#6.4 <set>标签)

[6.5 <foreach>标签](#6.5 <foreach>标签)

[6.6 <include>标签](#6.6 <include>标签)


实现的第二种方式是xml方式,可以写更复杂的SQL语句。

1 基本使用

1.1 查

(1)首先需要在配置文件中添加配置信息

XML 复制代码
mybatis:

  mapper-locations: classpath:mapper/**Mapper.xml #xml文件映射路径

这表示xml文件是在mapper包下的所有后缀为Mapper.xml的文件找MyBatis的实现,而classpath表示项目中resources目录。

(2)创建Mapper接口

XML 复制代码
@Mapper

public interface UserInfoXMLMapper {

    List<UserInfo> queryAllUser();

}

Mapper接口中不再使用注解,而是把SQL的实现放在xml文件中。

(3)实现xml文件

xml文件通常放在resources/mapper目录下(这对应配置文件配置的路径),固定格式为:

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.example.mybatis.mapper.UserInfoXMlMapper">



</mapper>

其中namespace是要映射的mapper接口所在的路径。如果是查询操作,就需要使用select标签:

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.example.mybatis.mapper.UserInfoXMLMapper">

    <select id="queryAllUser" resultType="com.example.mybatis.model.UserInfo">

        select * from userinfo

    </select>

</mapper>

id的值必须是接口中的方法名,resultType的值是接收查询结果的类型,即UserInfo类型,注意写包的全路径。select标签中写查询语句。

注意:同一个方法如果已经使用了注解的方式实现了,就不能再用xml方式对已经实现的方法再实现(即在同一个接口中对于某个方法前有SQL注解,再使用xml映射该接口的该方法实现SQL,就会报错)。

(4)xml方式的字段属性映射问题

xml方式实现的SQL查询也存在字段属性不一致的映射问题,解决办法也是3种:起别名和自动驼峰转化配置解决方式类似注解的解决方式,这里介绍一下xml的结果映射。

XML 复制代码
    <resultMap id="BaseMap" type="com.example.mybatis.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>

    

    <select id="queryAllUser" resultMap="BaseMap">

        select * from userinfo

    </select>

在xml文件中使用resultMap标签注册映射关系,id则是为这种映射起名,可以让所有有需求的方法使用这种映射:resultMap=resultMap标签的id名。type表示映射的对象,id标签是主键,result标签是其他需要映射字段名和属性名,column对应数据库字段名,property对应对象属性名。

1.2 增

java 复制代码
@Mapper

public interface UserInfoXMLMapper {

    Integer insert(UserInfo userInfo);

}

也可以使用@Param注解对方法参数重命名。如果要返回自增主键id,就需要在xml文件的该方法的实现中添加@Options注解的内容:

XML 复制代码
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">

        insert into userinfo (username, password, age, gender, phone)

        values (#{username}, #{password}, #{age},#{gender},#{phone})

    </insert>

1.3 删

java 复制代码
@Mapper

public interface UserInfoXMLMapper {

    Integer deleteById(Integer id);

}
XML 复制代码
    <delete id="deleteById">

        delete from userinfo where id = #{id}

    </delete>

可以发现,使用xml方式没有使用注解的问题:即如果方法参数命=#{参数名}时,方法参数不进行重命名就无法被识别为SQL的参数。

1.4 改

java 复制代码
@Mapper

public interface UserInfoXMLMapper {

    Integer updateObjectById(UserInfo userInfo);

}
XML 复制代码
    <update id="updateObjectById">

        update userinfo set username=#{username}, password=#{password}, gender=#{gender}

        where id=#{id}

    </update>

2 多表联查

多表联查和单表查询类似,就是使用SQL中的LEFT JOIN ON来联合两个表的某字段,从而可以让一次查询返回多个表的内容。

注意:多表联查会产生很长的SQL,查询速度很慢,又称为慢SQL问题。这是项目中重点优化的目标,通常多次查询多个表,再通过Java代码把查询结果连接起来来解决该问题。

首先除了用户表,再准备一张文章表,来多表查询文章和作者信息:

sql 复制代码
-- 创建文章表

DROP TABLE IF EXISTS articleinfo;



CREATE TABLE articleinfo (

 id INT PRIMARY KEY auto_increment,

 title VARCHAR ( 100 ) NOT NULL,

 content TEXT NOT NULL,

 uid INT NOT NULL,

 delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',

 create_time DATETIME DEFAULT now(),

 update_time DATETIME DEFAULT now()

) DEFAULT charset 'utf8mb4';

-- 插入测试数据

INSERT INTO articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1);

uid就是文章对应的作者的id(用户表的id)。

MyBatis查询返回结果实际上就是映射为对象,也就是说,只要有和查询结果字段对应的属性,就可以完成对象映射,因此创建结果对应的实体类:

java 复制代码
@Data

public class ArticleInfo {

    private Integer id;

    private String title;

    private String content;

    private Integer uid;

    private Integer deleteFlag;

    private Date createTime;

    private Date updateTime;

    //用户相关信息

    private String username;

    private Integer age;

    private Integer gender;

}

ArticleInfo类中注释前的属性对应数据库的文章表的字段,而注释后的内容是用户表对应的字段,也是我们希望联合查询的值。

java 复制代码
@Mapper

public interface ArticleInfoMapper {

    @Select("SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender " +

    "FROM articleinfo ta LEFT JOIN userinfo tb ON ta.uid = tb.id " +

    "WHERE ta.id = #{id}")

    ArticleInfo queryUserByUid(Integer id);

}

采用注解的方式实现多表联查,这里没有什么难度,重点是SQL的写法,并且返回的值一定要在实体类有属性对应,才能接收到返回值。

3 #{}和${}区别

3.1 #{}是预编译SQL,${}是即时SQL

${}和#{}都可以进行SQL参数的传递,如果使用#{}查询:

java 复制代码
@Select("select * from userinfo where id=#{id}")

查询的SQL语句的参数部分使用?来占位,再把参数替换占位符,形成真正可以执行的SQL,这种就属于预编译SQL。

如果使用${}查询:

java 复制代码
@Select("select * from userinfo where id=${id}")

查询的SQL语句直接把参数放到${}的位置上,这种可以直接执行的SQL就是即时SQL。

传递的参数是Integer类型,还发现不了区别,如果传递String类型,就会发现:

java 复制代码
@Select("select * from userinfo where username=#{username}")
java 复制代码
@Select("select * from userinfo where username=${username}")

这是因为****#{}传递的String参数会自动添加**** '' ,而${}传递的参数是以直接拼接的方式,即字符串的值直接拼接到SQL中,而没有添加'',因此就出现SQL语法错误了,解决办法就需要手动在SQL中添加'':

java 复制代码
@Select("select * from userinfo where username='${username}'")

3.2 #{}的查询速度比${}查询速度略快

MySQL服务器接受到SQL后会进行以下流程:

1.解析语法和语义,校验SQL语句是否正确。

2.优化SQL语句,制定执行计划。

3.执行并返回结果。

上述执行流程也是即时SQL的执行流程,但是如果每一条SQL都按照这样执行,就会很浪费时间。

很多SQL具有类似的结构,比如按id查询,每次都只改变where中的id=?,即预编译SQL。把SQL编译一次后缓存起来,形成预编译SQL,id的值传递到SQL中,就省去步骤1,因此节省了一定时间。

3.3 ${}存在SQL注入问题

(1)定义

SQL注入:通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。

比如:select * from userinfo where id='' or 1='1',这里把' or 1='1作为传递的参数,从而形成即时SQL,该SQL的where语句会执行1='1',结果为true,因此该语句相当于select * from userinfo。对于数据量很大的数据库,如果查询所有的数据,将会非常耗时,从而可能影响服务器。因此,SQL注入是常用的攻击服务器的方式。

(2)问题原因

****为什么{}会存在SQL注入问题而#{}不会存在该问题?****这是因为{}传递字符串会直接拼接字符串的值,为了避免这个问题,在SQL语句就要手动添加,即:select * from userinfo where username='${username}',此时username如果是' or 1='1,直接拼接后就变为select * from userinfo where username='' or 1='1',因此就把所有的用户信息全查出来了。

而#{}的方式就会把' or 1='1作为用户名,查询该用户名,从而查询不到任何用户信息(很少有用户把' or 1='1作为名字吧)。

通过SQL注入,如果服务器不对String类型的值进行校验的话,就可以达成很多目的,比如使用';drop table XX;--作为参数,使用${}的话就变成where xx='';drop table XX;--',那么where xx='';是前一条查询语句,drop table XX;是另一条删表语句,--'则是注释'的作用(早期没有mybatis时会遇到这种问题,现在mybatis已经做了一定的措施解决该问题)。

因此解决SQL注入问题除了使用#{},还需要服务器进行一定的参数校验,确保参数的正常。

4 ${}的使用场景

既然{}不安全,为什么还需要使用{},它有以下两种常用的场景:

4.1 查询结果排序

在很多时候需要对查询结果排序,比如按时间排序、按价格排序等等,需要使用order by语句,而排序有asc(升序)和desc(降序)两种取值,这两种取值不需要加''。

java 复制代码
@Select("select * from userinfo order by id #{sort}")

List<UserInfo> queryAllUserBySort(String sort);

这里传入的参数是String类型,如果使用#{},按升序排序查询语句就变成select * from userinfo order by id 'asc',从而导致SQL语句出错。如果是${}:

java 复制代码
@Select("select * from userinfo order by id ${sort}")

List<UserInfo> queryAllUserBySort(String sort);

查询语句就变成select * from userinfo order by id asc,此时SQL即可达到排序目的。

如果表名、列名作为参数,也应该使用${}。

4.2 like模糊查询

假设需要查询所有名字包含admin的用户,用户表已经存在admin和admin2两个用户,模糊匹配使用like,即select * from userinfo where username like '%admin%';

如果使用#{}:

java 复制代码
@Select("select * from userinfo where username like '%#{username}%'")

public List<UserInfo> queryAllUserByName(String username);

因为#{}会自动添加'',从而导致SQL出错。如果使用${}就不会出现问题:

java 复制代码
@Select("select * from userinfo where username like '%${username}%'")

public List<UserInfo> queryAllUserByName(String username);

但是${}存在SQL注入问题,因此如果要模糊查询,应该使用:@Select("select * from userinfo where username like concat('%',#{username},'%')"),concat方法会把字符串进行拼接,从而避免了SQL注入问题。

5 数据库连接池

传统SQL执行时,每次都需要创建连接,然后执行SQL,最后释放连接,连接的建立和销毁开销很大。

数据库连接池类似线程池,会在程序启动时创建一定数量的连接对象,不销毁,而放入数据库连接池,执行SQL时会从数据库连接池获取一个连接对象,然后执行SQL,执行结束后把连接对象放入数据库连接池。

有了数据库连接池:1.减少了网络开销2.资源重用3.提升了系统的性能。

常用的数据库连接池:C3P0、DBCP、Druid、Hikari。比较流行的是Druid(alibaba实现的)、Hikari,Hikari是SpringBoot默认使用的数据库连接池:

如果想要更换数据库连接池,比如使用Druid,在pom.xml文件中添加配置即可:

XML 复制代码
<dependency>

    <groupId>com.alibaba</groupId>

    <artifactId>druid-spring-boot-starter</artifactId>

    <version>1.1.17</version>

</dependency>

6 动态SQL

动态SQL就是根据条件的不同来拼接不同的SQL来灵活的实现功能。

6.1 <if>标签

用于做条件判断,场景:某些字段不是必传的,但是有默认值,因此不能简单的赋为null,而是不再SQL中拼接该字段,就需要使用<if>标签。

java 复制代码
@Mapper

public interface UserInfoXMLMapper {

    Integer insertByIf(UserInfo userInfo);

}
XML 复制代码
    <insert id="insertByIf" useGeneratedKeys="true" keyProperty="id">

        insert into userinfo

        (username,

         password,

         age,

         <if test="gender!=null">

             gender,

         </if>

         phone)

        values

        (#{username},

         #{password},

         #{age},

        <if test="gender!=null">

            #{gender},

        </if>

         #{phone})

    </insert>
java 复制代码
@Slf4j

@SpringBootTest

class UserInfoXMLMapperTest {

    @Autowired

    public UserInfoXMLMapper userInfoXMLMapper;

    @Test

    void insertByIf() {

        UserInfo userInfo = new UserInfo();

        userInfo.setUsername("zhaoliu");

        userInfo.setPassword("123456");

        userInfo.setAge(20);

        userInfo.setPhone("13811111111");

        Integer result = userInfoXMLMapper.insertByIf(userInfo);

        log.info("受影响的条数:" + result + " id:" + userInfo.getId());

    }

}

可以发现,并没有传参gender,if标签的test判断条件就为false,因此不拼接gender字段。

6.2 <trim>标签

如果使用if标签拼接的字段多,就需要添加分隔符",",比如:

XML 复制代码
    <insert id="insertByTrim" useGeneratedKeys="true" keyProperty="id">

        insert into userinfo

        (username,

        password,

        age,

        <if test="gender!=null">

            gender,

        </if>

        <if test="phone!=null">

            phone

        </if>

        )

        values

        (#{username},

        #{password},

        #{age},

        <if test="gender!=null">

            #{gender},

        </if>

        <if test="phone!=null">

            #{phone}

        </if>

        )

    </insert>

如果此时phone不传值,就不会拼接phone,那么SQL就会变成insert into userinfo(username, password, age, gender, ) values(#{username}, #{password}, #{age}, #{gender},),这是个错误的SQL,因为多加了","在gender后面。

<trim>标签就是用来处理SQL代码块的前缀和后缀,其中有四个属性:prefix(添加前缀)、suffix(添加后缀)、prefixOverrides(删除前缀)和suffixOverrides(删除前缀),具体使用:

java 复制代码
@Mapper

public interface UserInfoXMLMapper {

    Integer insertByTrim(UserInfo userInfo);

}
XML 复制代码
    <insert id="insertByTrim" useGeneratedKeys="true" keyProperty="id">

        insert into userinfo

        <trim prefix="(" suffix=")" suffixOverrides=",">

            username,

            password,

            age,

            <if test="gender!=null">

                gender,

            </if>

            <if test="phone!=null">

                phone

            </if>

        </trim>

        values

        <trim prefix="(" suffix=")" suffixOverrides=",">

            #{username},

            #{password},

            #{age},

            <if test="gender!=null">

                #{gender},

            </if>

            <if test="phone!=null">

                #{phone}

            </if>

        </trim>

    </insert>

该标签会把","的后缀删除,再添加前缀"("和后缀")"。

6.3 <where>标签

<where>标签用于where语句的条件的拼接,如果拼接的条件有and前缀(或or前缀),就会删除该前缀,保证SQL的合法,如果整个where都没有条件,那就退化成select XXX from XXX。

java 复制代码
@Mapper

public interface UserInfoXMLMapper {

    List<UserInfo> queryAllUserByWhere(@Param("username") String username,@Param("deleteFlag")Integer deleteFlag);

}
XML 复制代码
    <select id="queryAllUserByWhere" resultMap="BaseMap">

        select * from userinfo

        <where>

            <if test="username!=null">

                username=#{username}

            </if>

            <if test="deleteFlag!=null">

                and delete_flag=#{deleteFlag}

            </if>

        </where>

    </select>
java 复制代码
@Slf4j

@SpringBootTest

class UserInfoXMLMapperTest {

    @Autowired

    public UserInfoXMLMapper userInfoXMLMapper;

    @Test

    void queryAllUserByWhere() {

        log.info(userInfoXMLMapper.queryAllUserByWhere(null,0).toString());

    }

}

注意:该标签也可以用<trim>标签来代替。

6.4 <set>标签

<set>标签可以自动拼接set关键字,并且如果赋值语句后面有多余的分隔符",",就会自动去除。

java 复制代码
@Mapper

public interface UserInfoXMLMapper {

    Integer updateObjectBySet(UserInfo userInfo);

}
XML 复制代码
    <update id="updateObjectBySet">

        update userinfo

        <set>

            <if test="username!=null">

                username=#{username},

            </if>

            <if test="password!=null">

                password=#{password},

            </if>

            <if test="gender!=null">

                gender=#{gender}

            </if>

        </set>

        where id=#{id}

    </update>
java 复制代码
@Slf4j

@SpringBootTest

class UserInfoXMLMapperTest {

    @Autowired

    public UserInfoXMLMapper userInfoXMLMapper;

    @Test

    void updateObjectBySet() {

        UserInfo userInfo = new UserInfo();

        userInfo.setId(13);

        userInfo.setUsername("xiaoming");

        userInfo.setPassword("123456");

        userInfoXMLMapper.updateObjectBySet(userInfo);

    }

}

注意:该标签也可以用<trim>标签来代替。

6.5 <foreach>标签

如果需要批量执行,就需要用到in关键字,此时就需要使用<foreach>标签来拼接in后面的内容。

collection属性表示输入的集合(注意这里如果使用aliyun的框架也会出现命名问题,mybatis默认提供的参数名是list或array,因此不使用默认参数名就需要@Param),item属性表示迭代项的名称(类似for循环中int i = 0,用i来表示元素每一项),separator属性表示添加的分隔符,open属性表示添加的前缀,close表示添加的后缀。

java 复制代码
@Mapper

public interface UserInfoXMLMapper {

    Integer deleteByBatch(@Param("ids") List<Integer> ids);

}
XML 复制代码
    <delete id="deleteByBatch">

        delete from userinfo where id in

        <foreach collection="ids" item="id" separator="," open="(" close=")">

            #{id}

        </foreach>

    </delete>
java 复制代码
@Slf4j

@SpringBootTest

class UserInfoXMLMapperTest {

    @Autowired

    public UserInfoXMLMapper userInfoXMLMapper;

    @Test

    void deleteByBatch() {

        userInfoXMLMapper.deleteByBatch(Arrays.asList(10,11,12,13));

    }

}

6.6 <include>标签

如果xml文件中有很多重复的SQL片段,就可以使用<sql>标签把重复的片段封装起来,然后使用<include>标签来引入重复的片段。

XML 复制代码
    <sql id="col">

        id, username, password, age, gender, phone, delete_flag, create_time, update_time

    </sql>

    <select id="queryAllUser" resultMap="BaseMap">

        select

        <include refid="col"></include>

        from userinfo

    </select>

上述各种标签,可以使用注解实现,但是比较麻烦,首先需要在注解SQL字符串前后添加<script></script>标签,然后注意如果SQL中有字符串,注意不要和Java冲突(Java使用""表示字符串,使用''表示字符,SQL中两种都可以,建议使用''表示)。比如:

java 复制代码
@Insert("<script>" +

 "INSERT INTO userinfo (username,`password`,age," +

 "<if test='gender!=null'>gender,</if>" +

 "phone)" +

 "VALUES(#{username},#{age}," +

 "<if test='gender!=null'>#{gender},</if>" +

 "#{phone})"+

 "</script>")

下篇文章:

SpringBoot系列---MyBatis-plushttps://blog.csdn.net/sniper_fandc/article/details/148979284?fromshare=blogdetail&sharetype=blogdetail&sharerId=148979284&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

相关推荐
胚芽鞘6815 小时前
关于java项目中maven的理解
java·数据库·maven
岁忧5 小时前
(LeetCode 面试经典 150 题 ) 11. 盛最多水的容器 (贪心+双指针)
java·c++·算法·leetcode·面试·go
CJi0NG5 小时前
【自用】JavaSE--算法、正则表达式、异常
java
Hellyc6 小时前
用户查询优惠券之缓存击穿
java·redis·缓存
今天又在摸鱼6 小时前
Maven
java·maven
老马啸西风6 小时前
maven 发布到中央仓库常用脚本-02
java·maven
代码的余温6 小时前
MyBatis集成Logback日志全攻略
java·tomcat·mybatis·logback
ladymorgana7 小时前
【spring boot】三种日志系统对比:ELK、Loki+Grafana、Docker API
spring boot·elk·grafana
一只叫煤球的猫8 小时前
【🤣离谱整活】我写了一篇程序员掉进 Java 异世界的短篇小说
java·后端·程序员
斐波娜娜8 小时前
Maven详解
java·开发语言·maven