当前位置: 首页 > news >正文

注解退散!纯XML打造MyBatis持久层的终极形态

继上一篇文章讲解了如何使用注解来实现MyBatis的开发,本篇文章将讲解第二种方式——XML

一、XML文件配置

        在 resources/mapper 目录下配置 MyBatis XML 文件路径,并为所有数据表创建对应的 XML 映射文件

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

二、XML实现持久层代码 

2.1、添加Mapper接口

        我们先定义一个简单的接口,里面声明一个方法

@Mapper
public interface UserInfoMapperXML {List<UserInfo> selectAll();
}

2.2、实现UserInfoMapper.xml

2.2.1、配置标准模板

        在XML文件中添加 MyBatis 的标准配置模板

<?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="org.aokey.mybatisdemo.mapper.UserInfoMapperXML"></mapper>

 2.2.2、MybatisX 插件

        当然,此功能并非IDEA自带的功能,而是需要插件才能生效,按照以下步骤:

2.3、单元测试

         点击 Generate statement 后发现xml文件中多了 <select> 标签

<?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="org.aokey.mybatisdemo.mapper.UserInfoMapperXML"><select id="selectAll" resultType="org.aokey.mybatisdemo.model.UserInfo">select * from user_info</select></mapper>

以下是对标签的详细说明:

  • mapper 标签必须指定 namespace 属性,其值为对应 mapper 接口的全限定名(完整包名+类名)

  • 查询标签:用于执行数据库查询操作,包含以下关键属性:

    • Id 与接口中定义的方法名称一致,表示对该方法的具体实现
    • resultType指定返回结果的数据类型,通常为定义的实体类

        然后在 <select> 标签之间写入目标SQL语句,返回接口处,如下自动生成测试类

@SpringBootTest
class UserInfoMapperXMLTest {@Autowiredprivate UserInfoMapperXML userInfoMapperXML;@Testvoid selectAll() {userInfoMapperXML.selectAll().stream().forEach(x->System.out.println(x));}
}

        运行后观察到,成功查询到所有用户信息

 三、XML实现增删改查

3.1、增(insertr)

3.1.1、传参插入数据

         继续在接口处添加插入方法

Integer insertUserInfo(UserInfo userInfo);
    <insert id="insertUserInfo">insert into user_info (username, password, age, gender,phone) values(#{username},#{password},#{age},#{gender},#{phone})</insert>
    @Testvoid insertUserInfo() {UserInfo userInfo=new UserInfo();userInfo.setId(9);userInfo.setUsername("灰太狼");userInfo.setPassword("123456");userInfo.setAge(18);userInfo.setGender(1);userInfo.setPhone("19999999999");userInfoMapperXML.insertUserInfo(userInfo);}

        观察运行结果:显示成功插入!

 3.1.2、@Param注解

        使用@Param设置参数名称时,其用法与注解类似,当参数为对象时,传入的属性应采用 对象.属性 的形式

    <insert id="insertUser">insert into user_info (username, `password`, age, gender, phone) values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})</insert>
3..1.3、返回自增 Id

        保持接口定义不变,在 xml 中实现时设置 useGeneratedKeys 和 keyProperty 属性

    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into user_info (username, `password`, age, gender, phone) values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})</insert>

3.2、删(delete)

    Integer deleteUserInfo(Integer id);
    <delete id="deleteUserInfo">delete from user_info where id = 10</delete>

3.3、改(update)

    Integer updateUserInfo(UserInfo userInfo);
    <update id="updateUserInfo">update user_info set phone = #{phone} where id = #{id}</update>

3.4、查(select)

        同样地,采用XML格式进行查询也存在数据封装的问题,上述查询代码能够正常显示全部数据的原因是 yml 配置文件中配置了驼峰自动转换

        当然除此之外,还有注解和起别名的方法(参考上一篇),Aokey已经测试过使用 as 关键字起别名方法对于 xml 同样适用,那么我们来使用第三种方法解决数据封装问题:

    <resultMap id="BaseMap" type="org.aokey.mybatisdemo.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="selectAll" resultMap="BaseMap">select id, username,`password`, age, gender, phone, delete_flag,create_time, update_time from user_info</select>

        只需在要执行的SQL标签中添加 resultMap 的 ID 即可

四、# { } 和 $ { }

4.1、传递 Interger 类型参数

        我们在代码中大量使用了 # { } 进行变量插入赋值

    <select id="selectByAge" resultType="org.aokey.mybatisdemo.model.UserInfo">select id, username,`password`, age, gender, phone from user_info where age = #{age}</select>
    @Testvoid selectByAge() {List<UserInfo> userInfos = userInfoMapperXML.selectByAge(20);userInfos.stream().forEach(x->System.out.println(x));}

        查看运行结果:

        输入的参数并未直接拼接在后面,而是使用 " ? " 作为占位符来传递 id 值。这种SQL语句被称为  " 预编译SQL "

        预编译SQL(Prepared Statement)是一种数据库操作技术,先将SQL语句模板发送到数据库服务器进行编译和优化,后续只需传递参数即可重复执行,无需重新解析SQL结构

        我们修改 # { } $ { } 后重新查看日志输出:

        上图可观察到:输入参数直接拼接到SQL语句后面了

4.2、传递 String 类型的参数

    <select id="selectByName" resultType="org.aokey.mybatisdemo.model.UserInfo">select id, username,`password`, age, gender, phone from user_infowhere username = #{username}</select>

        完成控制台日志输出:
        我们修改 # { } $ { } 后重新查看日志输出:

    <select id="selectByName" resultType="org.aokey.mybatisdemo.model.UserInfo">select id, username,`password`, age, gender, phone from user_infowhere username = ${username}</select>

         报错 :where中的子句喜羊羊在未知列;仔细观察可见,此处喜羊羊直接拼接在SQL语句后面,但是传递参数是字符串,而喜羊羊并未加引号,我们手动添加引号后再次查找:

    <select id="selectByName" resultType="org.aokey.mybatisdemo.model.UserInfo">select id, username,`password`, age, gender, phone from user_info where username = "${username}"</select>

        查询结果显示成功,"喜羊羊"参数作为字符串类型被直接拼接到SQL语句中

从上述两个示例可以看出:

        # { } 采用预编译 SQL 的方式,通过 ? 占位符提前对 SQL 进行编译,再将参数填充到语句中。它会根据参数类型自动添加引号 ''

        $ { } 会直接进行字符串替换,与 SQL 一起编译。如果参数是字符串类型,需要添加引号 ''

4.3、# { } 和 $ { } 的区别

        # { } $ { } 的区别就是 预编译SQL 和 即时SQL 的区别

4.3.1、性能

        通常情况下,同一条SQL语句往往会被重复执行,或者每次执行时仅参数值有所不同。如果每次都要完整经历语法解析、SQL优化和编译过程,执行效率就会明显降低

        预编译SQL会将编译后的语句缓存起来,当后续执行相同SQL时(仅参数不同),无需重复编译。这种方式避免了重复解析和优化过程,从而显著提高了执行效率

4.3.2、SQL注入问题

        SQL注入:通过篡改输入数据来修改预定义的SQL语句,从而实现恶意代码执行并攻击服务器的技术手段(由于未对用户输入进行有效验证,同时采用字符串拼接方式构造SQL语句,攻击者可在输入参数中嵌入SQL关键字,从而篡改SQL执行逻辑,实现恶意攻击目的 )

        正常访问:

    @Testvoid selectByName() {UserInfo userInfo=userInfoMapperXML.selectByName("喜羊羊");System.out.println(userInfo);}
    <select id="selectByName" resultType="org.aokey.mybatisdemo.model.UserInfo">select id, username,`password`, age, gender, phone from user_infowhere username = '${username}'</select>

        访问结果:

         SQL注入场景:

    @Testvoid selectByName() {List<UserInfo> userInfo=userInfoMapperXML.selectByName("' or 1 = '1");userInfo.stream().forEach(x->System.out.println(x));}

         访问结果:

        从查询结果可以看出,当前获取的数据与预期不符,因此,建议在查询字段中使用 # { } 预编译方式(若使用 $ { } 时参数加上单引号,或者加上参数校验,另外SQL注入问题并非使用 $ { } 就一定会出现,只是有可能)

4.4、排序功能

        日常生活中,我们在淘宝、京东或者拼多多等网站购买一颗给商品时,最注重的问题无非是价格和质量,我们搜索一件商品后, 通过会点击价格或者销量排序,此功能就是 $ { } 实现的应用场景

        我们对用户表进行排序:

        我们在代码中进行实现:

    <select id="selectByOrder" resultType="org.aokey.mybatisdemo.model.UserInfo">SELECT * FROM `user_info` order by age #{order}</select>
   //测试代码@Testvoid selectByOrder() {userInfoMapperXML.selectByOrder("desc").stream().forEach(x->System.out.println(x));}//接口处代码List<UserInfo> selectByOrder(String order);

        观察控制台输出日志:

         而在MySQL中排序时 DESC(降序) 和 ASC(升序)这两个关键词是不加单引号的,否则会报错,这时 $ { } 不加引号的机制就能应用在此场景了,观察查询结果:

    <select id="selectByOrder" resultType="org.aokey.mybatisdemo.model.UserInfo">SELECT * FROM `user_info` order by age ${order}</select>

         注意:除排序功能外,若把表名作为参数,也必须使用 $ { }

4.5、like 查询 

    //测试用例@Testvoid selectByLike() {userInfoMapperXML.selectByLike("18").stream().forEach(x->System.out.println(x));}//接口处List<UserInfo> selectByLike(String likes);
    <select id="selectByLike" resultType="org.aokey.mybatisdemo.model.UserInfo">SELECT * FROM `user_info` where phone like '#{likes}%'</select>

        观察控制台输出日志:

        更改为:$ { } 再次观察输出结果

        MySQL 的 CONCAT() 函数 用于将两个或多个字符串连接成一个字符串;支持任意数量的参数,若参数中包含 Null,则返回结果为 Null

        解决方法:利用 MySQL 内置的 CONCAT() 函数进行处理,具体实现代码如下:

    <select id="selectByLike" resultType="org.aokey.mybatisdemo.model.UserInfo">SELECT * FROM `user_info` where phone like concat('%',#{likes},'%')</select>

http://www.lryc.cn/news/606696.html

相关文章:

  • 第11届蓝桥杯Python青少组_国赛_高级组_2020年10月真题
  • 人员定位卡人脸智能充电发卡机
  • 赛博算命之八字测算事业运势的Java实现(四柱、五行、十神、流年、格局详细测算)
  • Python match-case 模式匹配详解
  • Unity优化技巧:自动隐藏视野外的3D模型
  • Python爬虫实战:研究pycares技术构建DNS解析系统
  • 玻尔兹曼分布与玻尔兹曼探索
  • 从比划沟通到指令同步:声网让跨国游戏升级
  • 什么是爬虫协议?
  • Unity相机控制
  • Sklearn 机器学习 文本数据 TF-IDF实现文本向量化
  • 噪声对比估计(NCE):原理、演进与跨领域应用
  • git SSL certificate problem: self-signed certificate in certificate chain 解决办法
  • ZED 2/2i 相机安装与调试完整指南 | Ubuntu 20.04 + CUDA 11.8
  • 从本地到云端:将Linux文件夹上传至GitHub仓库的完整指南
  • 如何填写PDF表格的例子
  • iOS 抓不到包怎么办?全流程排查思路与替代引导
  • uniapp基础 (一)
  • 逻辑回归召回率优化方案
  • uniapp无线(WIFI)运行调试APP(真机)
  • Java设计模式之行为型模式(解释器模式)实现方式详解
  • RabbitMQ 延时队列插件安装与使用详解(基于 Delayed Message Plugin)
  • 在uni-app中引入本地日志插件
  • 开发者体验如何度量?
  • android APT技术
  • 嵌入式系统教学范式演进:云端仿真平台如何重构温湿度监测实验教学
  • JavaScript语法树简介:AST/CST/词法/语法分析/ESTree/生成工具
  • 2025 腾讯广告算法大赛 Baseline 项目解析
  • gd32modbus从机移植
  • 烽火HG680-KX-海思MV320芯片-2+8G-安卓9.0-强刷卡刷固件包