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

【后端开发】MyBatis

文章目录

  • MyBatis入门
    • 定义
    • 依赖
    • 数据准备
      • 数据库
      • 实体类
      • 企业开发规范
    • 数据库配置
    • 基础实现与测试
      • Mapper层
      • 测试
  • MyBatis日志
  • 增删改查
    • 增--Insert
    • 增--insert
      • @Param
      • 插入成功返回ID--@Options
    • 删--delete
    • 改--update
    • 查--select
      • 起别名
      • 结果映射
      • 开启驼峰命名
  • 参数传递
    • #{} 和 ${}区别
      • 案例
      • 区别
        • 预编译SQL和即时SQL
        • #{}的效率更高
        • #{}更安全(防止sql注入)
    • like查询
    • 传递多个参数
  • xml实现
    • 简单插入示例--insert
    • 简单删除示例--delete
    • 简单修改示例--delete
    • 简单查询示例--delete
      • 多表查询
        • 准备工作
  • 动态SQL
    • < if >和< trim >
    • < where >
    • < set >
    • < foreach >
    • < include >

MyBatis入门

定义

MyBatis是⼀款优秀的持久层框架,⽤于简化JDBC的开发。

依赖

        <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.4</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency>

数据准备

数据库

-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;-- 使用数据数据
USE mybatis_test;-- 创建表[用户表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`username` VARCHAR ( 127 ) NOT NULL,`password` VARCHAR ( 127 ) NOT NULL,`age` TINYINT ( 4 ) NOT NULL,`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2`在这里插入代码片`-女 0-默认',`phone` VARCHAR ( 15 ) DEFAULT NULL,`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now(),PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;-- 添加用户信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );

在这里插入图片描述

实体类

对应上述实现出来的数据库表,实现对应的实体类 UserInfo 。

@Data
public class UserInfo {private Integer id;private String username;private String password;private Integer age;private Integer gender;private String phone;private Integer deleteFlag;private Date createTime;private Date updateTime;
}

企业开发规范

  1. 表名:字段名使用小写字母或数字,单词之间以下划线分割,尽量避免出现数字开头或者两个下划线,中间只出现数字。
  2. 表必备三字段:id(主键)、create_time(创建时间)、 update_time(更新时间),有同等含义的字段即可,字段名不做强制要求。
  3. 在表查询中,避免使用 * 作为查询的字段列表。

数据库配置

这里展示 application.yml 配置文件。

spring:application:name: mybatis-demodatasource:url: jdbc:mysql://localhost:3306/mybatis_test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver

基础实现与测试

Mapper层

Mybatis的持久层接口规范一般叫 XxxMapper。
@Mapper注解:表示是MyBatis中的Mapper接口,程序运行时,框架会自动生成接口的实现类对象(代理对象),并给交Spring的IoC容器管理。
@Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容。

@Mapper
public interface UserInfoMapper {@Select("select * from userinfo")List<UserInfo> getUserInfoAll();
}

测试

IDEA可以自动生成测试类,在需要测试的Mapper接口中,右键->Generate ->Test,setUp/@Before方法会出现在执行该测试的前面,tearDown/@After会出现在执行该测试的后面,一定要将@SpringBootTest注解到该类,以及通过@Autowired注入Mapper对象。
在这里插入图片描述

@SpringBootTest
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@BeforeEachvoid setUp() {System.out.println("before each");}@Testvoid getUserInfoAll() {System.out.println(userInfoMapper.getUserInfoAll());}@AfterEachvoid tearDown() {System.out.println("after each");}

MyBatis日志

在Mybatis中可以借助日志查看到sql语句的执行、执行传递的参数以及执行结果,在配置文件中进行配置即可,配置内容如下:

mybatis:configuration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImpl

增删改查

增–Insert

//Mapper类@Insert("insert into userinfo (username, password, age, gender) " +"values (#{username},#{password},#{age},#{gender})")Integer insert(UserInfo userinfo);//测试类@Testvoid insert() {UserInfo userinfo = new UserInfo();userinfo.setUsername("laojun");userinfo.setPassword("123456");userinfo.setAge(18);userinfo.setGender(1);System.out.println(userInfoMapper.insert(userinfo));}

在这里插入图片描述
在这里插入图片描述

增–insert

//Mapper类@Insert("insert into userinfo (username, password, age, gender) " +"values (#{username},#{password},#{age},#{gender})")Integer insert(UserInfo userinfo);//测试类@Testvoid insert() {UserInfo userinfo = new UserInfo();userinfo.setUsername("laojun");userinfo.setPassword("123456");userinfo.setAge(18);userinfo.setGender(1);System.out.println(userInfoMapper.insert(userinfo));}

在这里插入图片描述
在这里插入图片描述

@Param

如果设置了 @Param 属性,#{…}对象就只能是一个整体需要使用 参数.属性 来获取。

    @Insert("insert into userinfo (username, password, age, gender) " +"values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender})")Integer insert1(@Param("userinfo") UserInfo userinfo);

插入成功返回ID–@Options

返回主键
如果想要拿到 ID ,需要在Mapper接口的方法上添加一个Options的注解。
keyProperty:指定能够唯一识别对象的属性。

//Mapper类@Options(useGeneratedKeys = true, keyProperty = "id")@Insert("insert into userinfo (username, password, age, gender) " +"values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender})")Integer insert1(@Param("userinfo") UserInfo userinfo);//测试类@Testvoid insert1() {UserInfo userinfo = new UserInfo();userinfo.setUsername("laojun2");userinfo.setPassword("1234562");userinfo.setAge(12);userinfo.setGender(2);System.out.println(userInfoMapper.insert1(userinfo));System.out.println("id: "+userinfo.getId());}

在这里插入图片描述

删–delete

//Mapper类@Delete("delete from userinfo where id = #{id}")Integer delete(Integer id);//测试类@Testvoid delete() {System.out.println(userInfoMapper.delete(6));}

在这里插入图片描述在这里插入图片描述

改–update

//Mapper类@Update("update userinfo set password = #{password} where id = #{id}")Integer update(UserInfo userInfo);//测试类@Testvoid update() {UserInfo userinfo = new UserInfo();userinfo.setId(1);userinfo.setPassword("123456");System.out.println(userInfoMapper.update(userinfo));}

在这里插入图片描述
在这里插入图片描述

查–select

//Mapper类@Select("select * from userinfo")List<UserInfo> queryAllUser();//测试类@Testvoid queryAllUser() {System.out.println(userInfoMapper.queryAllUser());}

在这里插入图片描述
从结果中可以看出,实体类与数据库字段名不同的结果为null,无法接收到相应的数据。
原因:

当自动映射查询结果时,MvBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性。
实体类的属性名与数据库字段名不同。(deleteFlag–delete_flag、createTime–create_time、updateTime–update_time)
在这里插入图片描述

解决办法:

起别名
结果映射
开启驼峰命名

起别名

//Mapper类@Select("select id, username, password, age, gender, phone, " +"delete_flag as deleteFlag, create_time as createTime, update_time as updateTime " +"from `userinfo`")List<UserInfo> selectUserInfos();//测试类@Testvoid selectUserInfos() {System.out.println(userInfoMapper.selectUserInfos());}

在这里插入图片描述

结果映射

//Mapper类@Results(id ="resultMap" , value = {@Result(column = "delete_flag", property = "deleteFlag"),@Result(column = "create_time", property = "createTime"),@Result(column = "update_time", property = "updateTime")})@Select("select * from userinfo")List<UserInfo> selectUserInfo2();//测试类@Testvoid selectUserInfo2() {System.out.println(userInfoMapper.selectUserInfo2());}

在这里插入图片描述

开启驼峰命名

通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而 java 属性一般遵循驼峰命名法约定,为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true,进行如下配置即可。

//Mapper类@Select("select * from userinfo")List<UserInfo> queryAllUser();//测试类@Testvoid queryAllUser() {System.out.println(userInfoMapper.queryAllUser());}
mybatis:configuration:map-underscore-to-camel-case: true

参数传递

将方法中的参数,传给SQL语句,使用 #{}或${}的方式获取方法中的参数。

#{} 和 ${}区别

案例

#{}:

    @Select("select * from userinfo where username = #{username}")List<UserInfo> getUserInfoByUsername(String username);@Testvoid getUserInfoByUsername() {System.out.println(userInfoMapper.getUserInfoByUsername("admin"));}

在这里插入图片描述
${}:

    @Select("select * from userinfo where username = ${username}")List<UserInfo> getUserInfoByUsername1(String username);@Testvoid testGetUserInfoByUsername1() {System.out.println(userInfoMapper.getUserInfoByUsername1("admin"));}

报错
在这里插入图片描述
修改:

    @Select("select * from userinfo where username = '${username}'")List<UserInfo> getUserInfoByUsername1(String username);@Testvoid testGetUserInfoByUsername1() {System.out.println(userInfoMapper.getUserInfoByUsername1("admin"));}

在这里插入图片描述

区别

预编译SQL和即时SQL

#{}:使用的是预编译SQL,通过?占位的方式,提前对SQL进行编译,然后把参数填充到SQL语句中,# 会根据参数类型,自动拼接引号" “。
${}:直接进行字符替换,一起对SQL进行编译,如果参数为字符串,需要加上引号” "。

#{}的效率更高

绝大多数情况下,某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同,如果每次都需要经过 语法解析-SQL优化-SQL编译等,则效率就明显不行了。
预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输入的参数不同),省去了解析优化等过程,以此来提高效率。

#{}更安全(防止sql注入)

因为${}会直接进行字符替换,一起对SQL进行编译,很有可能造成sql注入。

SQL注入:通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
' or 1='1,这就是一个经典的sql注入语句。

#会根据参数类型,如果参数类型为Sting,自动拼接引号" “,当不想要自动拼接引号” "时,选择使用${},就一定要考虑SQL注入。

示例(排序):

    @Select("select * from userinfo order by id ${sort}")public List<Userinfo> queryAllUser2(String sort);

此时${sort}拼接的是desc或者asc,但是这种其实也就两种情况,为了安全起见,最好还是将其写死,不需要参数引用,写为两个方法最好不过。

like查询

在这里插入图片描述

	@Select("select * from userinfo where username like '#${key}%'")List<UserInfo> queryAllUser3(String key);@Select("select * from userinfo where username like '%${key}%'")List<UserInfo> queryAllUser3(String key);

在这里插入图片描述
在这里插入图片描述

#{}会SQL错误,但是${}存在SQL注入的问题,所以不能直接使用 ${}解决办法,使用 mysql 的内置函数 concat()来处理。

    @Select("select * from userinfo where username like concat('%',#{username},'%')")List<UserInfo> queryAllUser4(String key);

在这里插入图片描述

传递多个参数

1.如果SQL语句中只有一个变量,变量名不需要与参数名对应。
2.如果SQL语句中有多个变量,变量名必需与参数名对应。
3.会自动生成对应参数比如param1自动对应函数的第一个参数,也可以通过 @Param,设置参数的别名,如果使用 @Param 设置别名, #{…}里面的属性名必须和@Param 设置的一样。

xml实现

配置对应的xml文件存放的位置,calsspath对应

简单插入示例–insert

首先,配置yml文件,将xml文件的路径配置在yml文件中。

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

其次,创建UserInfoXmlMapper接口,作为Mapper层,此时注意该Mapper接口所在的文件的路径。

package com.example.mybatis.mapper;@Mapper
public interface UserInfoXmlMapper {Integer insert(UserInfo userInfo);
}

随后,xml的路径下创建xxMapper.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"><insert id="insert">insert into userinfo (username, password, age, gender)values (#{username}, #{password}, #{age}, #{gender})</insert>
</mapper>

最后,进行测试。

    @Testvoid insert() {UserInfo userinfo = new UserInfo();userinfo.setUsername("laojun2");userinfo.setPassword("1234562");userinfo.setAge(12);userinfo.setGender(2);System.out.println(userInfoXmlMapper.insert(userinfo));}

在这里插入图片描述

简单删除示例–delete

    Integer deleteById(Integer id);@Testvoid deleteById() {System.out.println(userInfoXmlMapper.deleteById(14));}
    <delete id="deleteById">delete from userinfo where id = #{id}</delete>

在这里插入图片描述

简单修改示例–delete

    Integer updateById(UserInfo userInfo);@Testvoid updateById() {UserInfo userinfo = new UserInfo();userinfo.setId(1);userinfo.setPassword("admin");System.out.println(userInfoXmlMapper.updateById(userinfo));}
    <update id="updateById">update userinfo set password = #{password} where id = #{id}</update>

在这里插入图片描述

简单查询示例–delete

    List<UserInfo> selectAllUserInfo();@Testvoid selectAllUserInfo() {System.out.println(userInfoXmlMapper.selectAllUserInfo());}

这里需要注意的是resultType指定返回的类型。

    <select id="selectAllUserInfo" resultType="com.example.mybatis.model.UserInfo">select * from userinfo</select>

同时,还需要注意的是,此时我的配置文件中配置了自动驼峰转换。
在这里插入图片描述

多表查询

多表查询和单表查询类似,只是SOL不同而已。

准备工作

上面建了一张用户表,这里再建一张文章表,进行多表关联查询。

-- 创建⽂章表
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);

对应实体类:

@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;
}

原始sql语句:根据uid查询作者的名称等相关信息

SELECTta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender 
FROMarticleinfo taLEFT JOIN userinfo tb ON ta.uid = tb.id 
WHEREta.id =1

补充实体类:

@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;
}

Mapper接口:

@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);
}

其对应的xml未改一下就行。

动态SQL

< if >和< trim >

是存在非必填字段问题的解决方法。简单来说,< if >标签就是一个判断语句,test 里是要进行的判断,如果为true,则会填充标签里的内容。< trim >标签可以修饰< if >标签填充的不合法SQL语句。

< trim >标签属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀

  • 使用< trim >标签动态生成SQL语句:
    第一个< trim>处理列名部分
    第二个< trim>处理值部分
    自动去除末尾多余的逗号

  • 使用< if>条件判断:
    只有当字段不为null时(如username!=null)才会包含该字段
    gender字段比较特殊,只检查是否存在(不为空)而不判断null

  • 最终生成的SQL会根据传入参数动态变化,只包含非空的字段,避免插入null值。

<insert id="insert">insert into userinfo<trim prefix="(" suffix=")" suffixOverrides=","><if test="username!=null">username,</if><if test="password!=null">password,</if><if test="age!=null">age,</if><if test="gender">gender</if></trim>values<trim prefix="(" suffix=")" suffixOverrides=","><if test="username!=null">#{username},</if><if test="password!=null">#{password},</if><if test="age!=null">#{age},</if><if test="gender">#{gender}</if></trim></insert>

如果只传了username和password,生成的SQL会是:

insert into userinfo(username, password) values(#{username}, #{password})

< where >

如果我们想根据 id,password 来对表进行删除操作,仅使用< if>标签和< trim>标签:

<delete id="delete">delete from userinfo<trim prefix="where" prefixOverrides="and"><if test="id!=null">id = #{id}</if><if test="password!=null">and password = #{password}</if></trim></delete>

面对条件判断使用< where>标签可以化简,使用< where>标签对上述代码化简:

<delete id="delete1">delete from userinfo<where><if test="id!=null">id = #{id}</if><if test="password!=null">and password = #{password}</if></where></delete>

注意:

  1. < where>只会在子元素有内容的情况下才插入where子句
  2. 会自动去除子句的开头的AND或 OR

< set >

根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容,与< where>标签的使用类似
根据传入的用户 id 属性,修改其他不为 null 的属性:

 <update id="update">update userinfo<set><if test="username!=null">username=#{username}</if></set>where id=#{id}</update>

< set> :动态的在SQL语句中插⼊set关键字,并会删掉额外的逗号

< foreach >

对集合进行遍历时可以使用该标签。

  1. collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
  2. item:遍历时的每一个对象
  3. open:语句块开头的字符串
  4. close:语句块结束的字符串
  5. separator:每次遍历之间间隔的字符串

根据多个id,删除用户数据

    <delete id="delete4">delete from userinfowhere id in<foreach collection="ids" open="(" close=")" separator="," item="id" >#{id}</foreach></delete>

< include >

在xml映射文件中配置的SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码。可以对重复的代码片段进行抽取,将其通过< sql >标签封装到一个SQL片段,然后再通过< include >标签进行引用。

< sql >:定义可重用的SQL片段
< include >:通过属性refid,指定包含的SQL片段

<sql id="baseColumns">id, username, age, gender
</sql><select id="selectAllUser" resultType="com.example.myBatis.model.UserInfo">SELECT<include refid="baseColumns"/>FROM userinfo
</select>

相关文章:

  • 26考研 | 王道 | 数据结构 | 第六章 图
  • C#/.NET/.NET Core技术前沿周刊 | 第 35 期(2025年4.14-4.20)
  • GESP2024年12月认证C++八级( 第一部分选择题(6-10))
  • ArcPy Mapping 模块基础(下)
  • 我的电视 1.0.2 | 提供央视、卫视及地方直播服务的软件
  • 学习笔记—C++—string(一)
  • 论文阅读:2024 ICML Is DPO Superior to PPO for LLM Alignment? A Comprehensive Study
  • Java 线程池
  • Redis日常学习(一)
  • 存储/服务器内存的基本概念简介
  • 系统思考与理性决策
  • 基于FPGA的四层电梯设计系统设计和实现
  • leetcode205.同构字符串
  • 【读书笔记·VLSI电路设计方法解密】问题63:为什么可测试性设计对产品的财务成功至关重要
  • 个人所得税
  • 缓存 --- Redis性能瓶颈和大Key问题
  • Python内存管理之隔代回收机制详解
  • windows docker desktop 无法访问容器端口映射
  • 线程安全总结
  • 关于编译树莓派内核系统的总结
  • 五一假期出行预订进入高潮:酒店搜索热度翻倍,“请4休11”拼假带动长线游
  • 李家超:香港特区政府积极推进十五运会各项筹办工作
  • 凯德投资申报旗下首支中国消费基础设施公募REIT,资产规模约28亿元
  • 数读“十四五”| 长三角41城,谁在做基础研究
  • 山西“订婚强奸案”二审宣判:驳回上诉,维持原判