MyBatis操作数据库

MyBatis是一个优秀的持久层框架,简化了JDBC的繁琐代码,支持自定义SQL和映射。本文介绍了MyBatis的基本概念、为何学习、环境配置、查询操作、参数传入、结果映射、动态SQL以及多表查询,帮助理解MyBatis如何方便地进行数据库交互。
摘要由CSDN通过智能技术生成

1.MyBatis是什么?

MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。

简单来说 MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具。

2.为什么要学习MyBatis?

对于后端开发来说,程序是由以下两个重要的部分组成的:

  1. 后端程序
  2. 数据库

在这里插入图片描述
对于 JDBC 来说,整个操作⾮常的繁琐,我们不但要拼接每⼀个参数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接等,⽽所有的这些操作步骤都需要在每个⽅法中重复书写。于是我们就想,那有没有⼀种⽅法,可以更简单、更⽅便的操作数据库呢?
答案是肯定的,这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更⽅便、更快速的操作数据库。

3.学习MyBatis

MyBatis 学习分为两部分:

  • 配置 MyBatis 开发环境;
  • 使⽤ MyBatis 模式和语法操作数据库。

4.第一个MyBatis查询

开始搭建 MyBatis 之前,我们先来看⼀下 MyBatis 在整个框架中的定位,框架交互流程图:
在这里插入图片描述在MyBatis中,程序员需要做如下几件事;

  1. SQL语句 (可以写在java代码中,也可以写在MyBatis配置文件中)
  2. 传参(非必须)
  3. 结果映射(可以写在java代码中,也可以写在MyBatis配置文件中)

4.1 创建数据库和表

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;

-- 使⽤数据数据
use mycnblog;
-- 创建表[⽤户表]
drop table if exists userinfo;
create table userinfo(
 id int primary key auto_increment,
 username varchar(100) not null,
 password varchar(32) not null,
 photo varchar(500) default '',
 createtime datetime default now(),
 updatetime datetime default now(),
 `state` int default 1
) default charset 'utf8mb4';

-- 添加⼀个⽤户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`,`createtime`, `updatetime`, `state`) VALUES(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

4.2 添加MyBatis框架⽀持

新项目添加MyBatis

在创建项目是添加MyBatis依赖:
在这里插入图片描述

老项目添加MyBatis

1.下载插件

在这里插入图片描述

2.右击generate

在这里插入图片描述

3.搜索”MyBatis“添加

在这里插入图片描述

4.3 配置连接字符串和MyBatis

配置连接字符串

在配置文件中添加如下内容,这里在application.yml文件中添加:

# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
    username: root
    password: 111111
    driver-class-name: com.mysql.cj.jdbc.Driver

配置 MyBatis 中的 XML 路径

# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

在配置文件中建一个mapper文件夹:
在这里插入图片描述

4.4 添加业务代码

按照如下流程来实现MyBatis查询所有用户的功能:
在这里插入图片描述

添加实体类

添加用户的实体类(属性需要和字段名一致):

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private Date createtime;
    private Date updatetime;
}

添加mapper接口

@Mapper
public interface userMapper {
    //查询
    public List<User> queryAll();
}

添加sql语句,实现接口
在配置文件的mapper文件夹中,定义UserMapper.xml 查询所有⽤户的具体实现 SQL:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati
s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatisdemo.mapper.UserMapper">
    <select id="queryAll" resultType="com.example.mybatisdemo.model.User">
        select * from userinfo
    </select>
</mapper>

以下是对以上标签的说明:

  • mapper标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接⼝的全限定 名,包括全包名.类名。
  • select查询标签:是⽤来执⾏数据库的查询操作的:
  • id:是和 Interface(接⼝)中定义的⽅法名称⼀样的,表示对接⼝的具体实现⽅法。
  • resultType:是返回的数据类型,也就是开头我们定义的实体类

测试一下: 在接口页面右键选择generate,点击test:
在这里插入图片描述
选择要测试的方法:
在这里插入图片描述
会自动生成测试方法。

测试一下

UserMapperTest.java代码:

@Slf4j
@SpringBootTest
class UserMapperTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    void queryAll() {
        List<User> users = userMapper.queryAll();
        log.info(users.toString());
    }
}

如何传参

添加sql语句:根据id查询:通过 “#” 传参

    <select id="queryById" resultType="com.example.mybatisdemo.model.User">
        select * from userinfo where id=#{uid}
    </select>

添加mapper接口:

    //根据id查询
    User queryById(@Param("uid") Integer id);

@Param是重命名

补充: 当只有一个参数时,可以不加注解,并且参数名称可以不一样,如下图所示:

在这里插入图片描述

5.增删改操作

  • insert标签:插⼊语句
  • update标签:修改语句
  • delete标签:删除语句

5.1 增加数据

方法声明:

    //插入
    Integer insert(User user);

添加sql语句:

    <insert id="insert">
        insert into userinfo(username,password,photo) values(#{username},#{password},#{photo})
    </insert>

通过"#"传参:
在这里插入图片描述

如果设置了param注解,就必须要使用param注解的命名:

方法声明:

Integer insert1(@Param("userinfo") User user);

SQL语句:

    <insert id="insert1">
        insert into userinfo(username,password,photo) values(#{userinfo.username},#{userinfo.password},#{userinfo.photo})
    </insert>

特殊的添加:返回自增的id

默认情况下返回的是受影响的⾏号,如果想要返回⾃增 id,具体实现如下:

方法声明:

Integer insert1(@Param("userinfo") User user);

UserMapper.xml:

    <insert id="insert1" useGeneratedKeys="true" keyProperty="id" >
        insert into userinfo(username,password,photo) values(#{userinfo.username},#{userinfo.password},#{userinfo.photo})
    </insert>
  • useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false。
  • keyColumn:设置⽣成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第⼀列的时候,是必须设置的。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。
  • keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或 insert 语句的selectKey ⼦元素设置它的值,默认值:未设置(unset)。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。

5.2 修改数据

方法声明:

    //修改
    void update(User user);

UserMapper.xml:

    <update id="update">
        update userinfo set username=#{username},password=#{password} where id=#{id}
    </update>

5.3 删除数据

方法声明:

    //删除
    void delete(Integer id);

UserMapper.xml:

    <delete id="delete">
        delete from userinfo where id=#{id}
    </delete>

6.表单查询

6.1 参数占位符 #{} 和 ${}

  • #{}:预编译处理。
  • ${}:字符直接替换。

预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement的 set ⽅法来赋值。

直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。

举个例子来看看这两者的区别:

  1. ⼀般航空公司乘机都是头等舱和经济舱分离的,头等舱的⼈先登机,登机完之后,封闭经济舱,然后再让经济舱的乘客登机,这样的好处是可以避免浑⽔摸⻥,经济舱的⼈混到头等舱的情况,这就相当于预处理,可以解决程序中不安全(越权处理)的问题
  2. ⽽直接替换的情况相当于,头等舱和经济舱不分离的情况,这样经济舱的乘客在通过安检之后可能越权摸到头等舱,这就相当于参数直接替换,它的问题是可能会带来越权查询和操作数据等问题。比如6.3的sql注入问题。

6.2 使用 ${} 在排序查询中的优势

使用 #{} 进行排序查询

方法声明:

    //排序
    List<User>queryByOrder(String order);

使用 #{} 进行排序查询:

    <select id="queryByOrder" resultType="com.example.mybatisdemo.model.User">
        select * from userinfo order by #{order}
    </select>

查询测试:

    void queryByOrder() {
        List<User>users=userMapper.queryByOrder("desc");
        log.info(users.toString());
    }

运行结果如下所示:
在这里插入图片描述
这是因为当使⽤ #{sort} 查询时,如果传递的值为 String 则会加单引号,就会导致 sql 错误。

使用 ${} 进行排序查询

排序时,只能使用 $ 。

使用 ${} 进行排序查询则查询成功:

    <select id="queryByOrder" resultType="com.example.mybatisdemo.model.User">
        select * from userinfo order by id ${order}
    </select>

但是它有可能会引起sql注入问题。即用户输入的参数中,有可能带有恶意sql。

解决方法:可以在前端只能让用户进行点击,选择排序方式,参数由后端控制输入,后端在查询之前,对参数进行校验,只能传 desc 和 asc 两个值。

6.3 sql注入

SQL注入(SQL lnjection)是发生在Web程序中数据库层的安全漏洞,是比较常用的网络攻击方式之一,他不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至修改数据库。也就是说,SQL注入就是在用户输入的字符串中添加SQL语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的SQL语句就会被数据库服务器误认为是正常的SQL语句而运行,攻击者就可以执行计划外的命令或者访问未授权的数据。

6.4 like 查询

like 查询是一个模糊查询。使⽤ #{} 会报错,而这个又不能直接使用 ${},会有sql注入问题,可以考虑使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:

concat ()方法用于连接字符串

方法声明:

    List<User>queryByLike(String name);

UserMapper.xml:

    <select id="queryByLike" resultType="com.example.mybatisdemo.model.User">
        select * from userinfo where username like concat('%',#{name},'%')
    </select>

6.4 如何打印sql日志

在配置文件中添加如下文件即可:

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

7.多表查询

增、删、改返回影响的⾏数,那么在 mapper.xml 中是可以不设置返回的类型的,然⽽即使是最简单查询⽤户的名称也要设置返回的类型。

对于 select 查询标签来说⾄少需要两个属性:

  • id 属性:⽤于标识实现接⼝中的那个⽅法;
  • 结果映射属性:结果映射有两种实现标签:resultType和resultMap

7.1 返回类型:resultType

绝⼤数查询场景可以使⽤ resultType 进⾏返回,如下代码所示:

    <select id="queryAll" resultType="com.example.mybatisdemo.model.User">
        select * from userinfo
    </select>

它的优点是使⽤⽅便,直接定义到某个实体类即可。

7.2 返回字典映射:resultMap

resultMap 使⽤场景:

  • 字段名称和程序中的属性名不同的情况,可使⽤ resultMap 配置映射;
  • ⼀对⼀和⼀对多关系可以使⽤ resultMap 映射并查询数据。

字段名称和程序中的属性名不同的情况

在这里插入图片描述
使用下面的代码查询就会报错:

	<select id="queryAll" resultType="com.example.mybatisdemo.model.User">
        select * from userinfo
    </select>

这个时候就可以使⽤ resultMap 了,resultMap 的使⽤如下:

    <resultMap id="baseMap" type="com.example.mybatisdemo.model.User">
        <id column="id" property="id"></id>
        <result column="username" property="name"></result>
        <result column="password" property="pwd"></result>
    </resultMap>
    
    <select id="queryAll" resultType="com.example.mybatisdemo.model.User">
        select * from userinfo
    </select>

在这里插入图片描述
查询成功:
在这里插入图片描述

7.3 多表查询

在多表查询时,如果使⽤ resultType 标签,在⼀个类中包含了另⼀个对象是查询不出来被包含的对象
的,所以这里使用resultMap标签。

前面创建了一个user表,这里再创建一个文章表articleinfo。

创建数据库和表

-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
 id int primary key auto_increment,
 title varchar(100) not null,
 content text not null,
 createtime datetime default now(),
 updatetime datetime default now(),
 uid int not null,
 rcount int not null default 1,
 `state` int default 1
)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 Date createtime;
    private Date updatetime;
    private Integer rcount;
    //作者相关信息
    private User user;
    private Integer userId;
    private String username;
}

方法声明

    List<ArticleInfo> queryArticle2();

添加mapper接口

	 <resultMap id="BaseMap2" type="com.example.mybatisdemo.model.ArticleInfo">
        <id property="id" column="id"></id>
        <result property="title" column="title"></result>
        <result property="content" column="content"></result>
        <result property="createtime" column="createtime"></result>
        <result property="updatetime" column="updatetime"></result>
        <result property="userId" column="uid"></result>
        <result property="username" column="username"></result>
    </resultMap>
    <select id="queryArticle2" resultMap="BaseMap2">
        select ta.*,tb.id as userId,tb.username as username from articleinfo ta left join userinfo tb on ta.uid = tb.id
    </select>

8.复杂情况:动态SQL的使用

动态SQL:根据输入的参数不同,动态的拼接SQL。

8.1 if 标签

在注册⽤户的时候,可能会有这样⼀个问题,如下图所示:
在这里插入图片描述
注册分为两种字段:必填字段和⾮必填字段,那如果在添加⽤户的时候有不确定的字段传⼊,程序应该如何实现呢?
这个时候就需要使⽤动态标签 来判断了。

在7.3创建的articleinfo表中,state属性为非必填字段,以它为例具体实现代码如下:

方法声明

 void insertByCondition(ArticleInfo articleInfo);

添加mapper接口

    <insert id="insertByCondition">
        insert into articleinfo(title,content,uid
        <if test="state!=null">
            ,state
        </if>
        )
        values
        (#{title},#{content},#{userId}
        <if test="state!=null">
            ,#{state}
        </if>
        )
    </insert>

补充:test后面的内容是Java对象的属性字段,不是MySQL字段!!!

测试:不填写state

    @Test
    void insertByCondition() {
        ArticleInfo articleInfo=new ArticleInfo();
        articleInfo.setTitle("测试文章");
        articleInfo.setContent("测试内容");
        articleInfo.setUserId(2);
        articleMapper.insertByCondition(articleInfo);
    }

结果如下,state自动填写了默认值。
在这里插入图片描述

测试:填写state

    @Test
    void insertByCondition() {
        ArticleInfo articleInfo=new ArticleInfo();
        articleInfo.setTitle("测试文章1");
        articleInfo.setContent("测试内容1");
        articleInfo.setUserId(3);
        articleInfo.setState(2);
        articleMapper.insertByCondition(articleInfo);
    }

这次填写了state,结果如下:
在这里插入图片描述

8.2 trim标签

之前的插⼊⽤户功能,只是有⼀个 sex 字段可能是选填项,如果所有字段都是⾮必填项,就考虑使⽤trim标签结合if标签,对多个字段都采取动态⽣成的⽅式。

trim标签中有如下属性:

  • prefix:表示整个语句块,以prefix的值作为前缀
  • suffix:表示整个语句块,以suffix的值作为后缀
  • prefixOverrides:表示整个语句块要去除掉的前缀
  • suffixOverrides:表示整个语句块要去除掉的后缀

示例:在articleinfo表中,设置title,content,uid,state都为非必填字段:

添加mapper接口

<insert id="insertByCondition">
        insert into articleinfo
        <trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides="," >
            <if test="title!=null">
                title
            </if>
            <if test="content!=null">
                ,content
            </if>
            <if test="userId!=null">
                ,uid
            </if>
            <if test="state!=null">
                ,state
            </if>
        </trim>
        values
        <trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=",">
            <if test="title!=null">
                #{title}
            </if>
            <if test="content!=null">
                ,#{content}
            </if>
            <if test="userId!=null">
                ,#{userId}
            </if>
            <if test="state!=null">
                ,#{state}
            </if>
        </trim>
    </insert>

8.3 where标签

where标签的作用:

  • 生成where关键字
  • 去掉多余的字符(比如下面示例中uid前面的and)

示例:

 select * from articleinfo where uid=? and state=?;

方法声明

    //多个参数需要重命名
    List<ArticleInfo> queryByCondition(@Param("uid") Integer uid, @Param("state") Integer state);

添加mapper接口

    <select id="queryByCondition" resultType="com.example.mybatisdemo.model.ArticleInfo">
        select * from articleinfo
        <where>
            <if test="uid!=null">
                and uid=#{state}
            </if>
            <if test="state!=null">
                and state=#{state}
            </if>
        </where>
    </select>

8.4 set标签

set标签作用:

  • 生成set关键字
  • 去除最后一个逗号(如下面示例,state为非必填字段,当不填写state时,uid后面就会多一个逗号,导致语句出错,set则可以去除这个逗号,也可以使用trim标签)

示例:

update articleinfo set uid=?,state=?;

方法声明

    void updateByCondition(@Param("uid") Integer uid, @Param("state") Integer state);

添加mapper接口

<update id="updateByCondition">
        update articleinfo
        <set>
            <if test="uid!=null">
                uid=#{uid},
            </if>
            <if test="state!=null">
                state=#{state}
            </if>
        </set>
    </update>

8.5 foreach标签

对集合进⾏遍历时可以使⽤该标签。foreach标签有如下属性:

  • collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
  • item:遍历时的每⼀个对象
  • open:语句块开头的字符串
  • close:语句块结束的字符串
  • separator:每次遍历之间间隔的字符串

示例:

delete from articleinfo where id in ( ? , ? )

方法声明

void batchDelete(List<Integer> ids);

添加mapper接口

    <delete id="batchDelete">
        delete from articleinfo where id in
        <foreach collection="list" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>

补充:使用注解实现mybatis查询

myBatis有两种实现方式:

  • 使用xml (本文用的就是这种方式)
  • 使用注解

举例

查询全部:

	@Select("select * from userinfo")
    List<User> queryAll();

根据id查询:

    @Select("select * from userinfo where id=#{id}")
    User queryById(Integer id);

一个项目中,注解和xml的方式,可以并存,对于简单的SQL,注解写起来非常简单。但是,对于复杂的SQL,注解写起来就会非常的复杂。可以根据自己项目的需求进行选择。

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值