JavaEE高阶---MyBatis

一 : 什么是MyBatis

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

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

二 : MyBatis的作用

在这里插入图片描述

JDBC的工作流程非常繁琐 , 主要包括 :

1.创建数据库连接池DataSource
2.通过DataSource获取数据库连接Connection
3. 编写要执行带 ? 占位符的 SQL 语句
4. 通过 Connection 及 SQL 创建操作命令对象 Statement
5. 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
6. 使用 Statement 执行 SQL 语句
7. 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
8. 处理结果集
9. 释放资源

而MyBatis可以帮助我们更方便、更快速的操作数据库 !

MyBatis组成

在这里插入图片描述

为什么要这样实现呢?

MyBatis要想实现增删改查,就必须要写SQL。写SQL就有两种选择:

  1. 写到Java的类里面;
  2. 放在xml里面。

如果选择第一种方式,那么你只能将SQL语句放在双引号中,如果你需要进行比较复杂的查询,比如联表查询,此时双引号中的字符串内容非常长,且里面包含大量的表名,字段名等等,这种做法出错的概率很高;

此时目光转到第二种方式。

在这里插入图片描述

三 : 学习重点

MyBatis 学习分为两部分:

  1. 配置 MyBatis 开发环境;
  2. 使用MyBatis模式和语法操作数据库 .

四 : 配置MyBatis开发环境

4.1 创建数据库和表

要使用MyBatis 的模式来读取用户表中的所有用户 , 需要先建库和建表 , 示范SQL语句如下 :

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

-- 使用数据数据
use myblog;

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

-- 添加一个用户信息
INSERT INTO `myblog`.`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 创建项目

创建一个Spring MVC项目,并添加MyBatis框架支持.

在这里插入图片描述

4.3 设置数据库和MyBatis配置

4.3.1 配置数据库的连接信息(连接哪台数据库)

在resources文件夹下创建application.yml文件。
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/myblog?characterEncoding=utf8
    username: root
    password: 111111
    driver-class-name: com.mysql.cj.jdbc.Driver

在这里插入图片描述

在这里插入图片描述

4.3.2 配置MyBatis XML文件存放位置和命名规则

仍然在application.yml中进行操作。

在这里插入图片描述

此时同时需要在resources路径下创建mapper文件夹,如下图所示:

在这里插入图片描述

五:使用MyBatis实现增删改查功能

5.1 查询所有信息

首先回顾一下我们数据库中的内容 :

在这里插入图片描述

下面我们来实现,使用MyBatis进行查询。

实现步骤很简单,只要你理解了MyBatis的设计思路,其实不在话下。

步骤:
1.创建一个接口;
2.创建与上面接口对应的xml文件。

1.创建一个接口。

注意:一定要添加@Mapper注解,表示这是mybatis的接口!!!!!!

在这里插入图片描述

package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper //mybatis 接口
public interface UserMapper {
    //查询所有的信息
    public List<UserInfo> getAll();
}

2.创建与上面接口对应的xml文件。

第一个问题,这个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="">
   
</mapper>

针对当前的测试项目,完整内容如下:

<?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.demo.mapper.UserMapper">
    <select id="getAll" resultType="com.example.demo.model.UserInfo">
        select * from userinfo
    </select>
</mapper>

在这里插入图片描述

到此为止,我们已经实现了MyBatis查询数据的功能。

那我们这个实现到底对不对呢?这里我们插入“单元测试”的内容,以检验代码的正确性。

有关单元测试的内容,详见这篇文章:

SpringBoot单元测试

5.2 传参查询

package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper //mybatis 接口
public interface UserMapper {
    //查询所有的信息
    List<UserInfo> getAll();
    //传参查询
    UserInfo getUserById(@Param("uid") Integer id);
}


此处需要使用一个注解:@Param

<?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.demo.mapper.UserMapper">
    <select id="getAll" resultType="com.example.demo.model.UserInfo">
        select * from userinfo
    </select>

    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where id=#{uid}
    </select>
</mapper>

在这里插入图片描述

生成单元测试:

package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;

@SpringBootTest//当前测试的上下文环境为springboot
class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    void getAll() {
        List<UserInfo> list = userMapper.getAll();
        for (UserInfo user : list) {
            System.out.println(user.toString());
        }
    }

    @Test
    void getUserById() {
        UserInfo userInfo = userMapper.getUserById(1);
        System.out.println(userInfo.toString());
    }
}


在这里插入图片描述

运行结果:

在这里插入图片描述

因为当前确实有id为1的一条记录,所以就成功查询出来了。

类比传参查询,我们就可以实现登录操作了,无非是传参时传递用户名和密码两个参数。

5.3 添加数据

前置工作,先创建一张文章表,代码如下:

-- 创建文章表
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';

在这里插入图片描述

创建文章的实体类

package com.example.demo.model;

import lombok.Data;

import java.util.Date;

@Data
public class ArticInfo {
    private int id;
    private String title;
    private String content;
    private Date createtime;
    private Date updatetime;
    private int uid;
    private int rcount; // 访问量
    private int state; // 状态(预览字段)
    //..
}

注意创建位置:

在这里插入图片描述

在查询数据时,如果你创建的实体类的对象名和你表中的字段名不一致,那么查询会失败;而在插入数据时,即使你创建的实体类的对象名和表中的字段名不一致,比如表中是titile,你写成了private String t,此时在插入数据时,我们只需要在xml中将插入的对象名写为t,即可成功插入。

基于规范性,我们要求:实体类的对象名和表中的字段名要一一对应!

添加接口及xml实现

此处有2中方法,一种加注解,一种不加注解:

方法一:加注解

package com.example.demo.mapper;

import com.example.demo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface ArticleInfoMapper {
    // 添加方法
    public int add(@Param("articleInfo") ArticleInfo articleInfo);
}

在这里插入图片描述

<?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.demo.mapper.ArticleInfoMapper">
    <insert id="add">
        insert into articleinfo(title,content,uid)
        values(#{articleinfo.title},#{articleinfo.content},#{articleinfo.uid});
    </insert>
</mapper>

在这里插入图片描述

进行单元测试:

在这里插入图片描述

在这里插入图片描述

方法二:不加注解

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

进行单元测试:

在这里插入图片描述

在这里插入图片描述

以上这两种写法下,控制台返回的都是受影响的行数:1。

在这里插入图片描述

5.4 添加得到用户的自增id

如何在添加的同时得到用户的自增id呢?需要在xml的insert标签中添加两个键值对,具体操作方法如下:

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

单元测试:

在这里插入图片描述

运行结果:

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

你可能会疑惑,这里的自增id不应该是3吗 ?没错,事实是我在这中间测试时候添加了两条数据,之后又删除了它们,所以你会看到自增id是5。

5.5 删除数据

删除功能很简单,仍然沿袭前面的步骤:

  1. 添加方法;
  2. 修改xml文件;
  3. 编写单元测试。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

同理也可将id=2的文章删除。

5.6 修改数据

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

单元测试:

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

我们上面的代码都是MyBatis的操作,但是没有提供相应的入口。下面我简单介绍一下如何提供入口,即创建控制器层和服务器层。以查询用户信息为例:

在这里插入图片描述

package com.example.demo.controller;


import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController//标识其为控制器层
@RequestMapping("/user")//访问路由
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/getall")//访问路由
    public List<UserInfo> getUser(){
        return userService.getAll();//调用服务层的getAll方法
    }
}

package com.example.demo.service;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Service//标识其为服务层
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public List<UserInfo> getAll() {
        return userMapper.getAll();//调用接口的getAll方法
    }
}

然后userMapper基于MyBatis,在数据库中查找相应的内容。启动DemoApplication类:

在这里插入图片描述

成功查询出所有信息!

六:参数占位符问题

前面在进行参数匹配时,我们使用了#{ } 。实际上我们还有另一个参数占位符,就是${ }。

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

6.1 #{}

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

#{}: 解析为SQL时,会将形参变量的值取出,并自动给其添加引号。 例如:当实参username="Amy"时,传入下Mapper映射文件后:

......
    <select id="findByName">
    select * from user where username=#{value}
    </select>
    ....

SQL将解析为:

select * from user where username="Amy"

6.2 ${}

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

${}: 解析为SQL时,将形参变量的值直接取出,直接拼接显示在SQL中

例如:当实参username="Amy"时,传入下Mapper映射文件后:

......
    <select id="findByName">
        select * from user where username=${value}
    </select>
    ....

SQL将解析如下:

select * from user where username=Amy

显然该SQL无法正常执行,故需要在mppaer映射文件中的${value}前后手动添加引号,如下所示:

......
    <select id="findByName" parameterType="String" resultMap="studentResultMap">
        SELECT * FROM user WHERE username='${value}'
    </select>
    ....

SQL将解析为:

SELECT * FROM user WHERE username='Amy'

6.3 SQL注入

${}方式是将形参和SQL语句直接拼接形成完整的SQL命令后,再进行编译,所以可以通过精心设计的形参变量的值,来改变原SQL语句的使用意图从而产生安全隐患,即为SQL注入攻击。现举例说明:

在这里插入图片描述

当前我们数据库中有一条数据,其用户名和密码都是"admin"。正常情况下,我们使用#{}作为参数占位符号,此时正确输入用户名和密码,查询可以成功,代码如下:

1.添加方法:

   //登录查询,需要提供用户名和密码
    UserInfo getUserToLogin(@Param("username") String username,@Param("password")String password);

2.配置xml文件

    <select id="getUserToLogin" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username=#{username} and password=#{password}
    </select>

3.进行单元测试

    @Test
    void getUserToLogin() {
        UserInfo userInfo = userMapper.getUserToLogin("admin","admin");
        System.out.println(userInfo);
    }

查询结果:

在这里插入图片描述

此时没有问题。

如果我们错误地输入了用户名或密码,当然不能查询出结果,如下所示:

在这里插入图片描述

现在我们使用${}作为参数占位符号,此时正确输入用户名和密码,查询可以成功,代码如下:

    <select id="getUserToLogin" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username='${username}' and password='${password}'
    </select>

查询结果:

在这里插入图片描述

如果我们错误地输入了用户名或密码,也不能查询出结果。但是,如果我们稍微设计一下输入的密码,那么即使用户名和密码都是错误地,仍然能够查询出结果,如下图所示:

    @Test
    void getUserToLogin() {
        UserInfo userInfo = userMapper.getUserToLogin("admin","' or 1='1");
        System.out.println(userInfo);
    }

在这里插入图片描述

这就是使用${}作为参数占位符所存在的最大的问题,它是不安全的,存在SQL注入的风险。

在这里插入图片描述
那么${}这个占位符是否一无是处呢?有一种场景下是需要使用到 ${} 的。比如我们要实现对数据库中的数据进行排序,但是需要以传参指定排序的方式。代码如下:

先向数据库中插入一条数据,以便于观察结果:

在这里插入图片描述

    // 查询所有的信息(根据排序条件进行排序)
    public List<UserInfo> getAllByOrder(@Param("order") String order);
    <select id="getAllByOrder" resultType="com.example.demo.model.UserInfo">
        select * from userinfo order by id ${order}
    </select>
    @Test
    void getAllByOrder() {
        List<UserInfo> list = userMapper.getAllByOrder("desc");
        for (UserInfo userInfo : list) {
            System.out.println(userInfo);
        }
    }

运行结果:

在这里插入图片描述

当需要传入关键字时,我们就要用到${},因为如果使用#{}的话,在进行预处理阶段,desc这样的关键字会被加上双引号,直接解析为字符串了。

6.4 like查询

like 使用 #{} 报错。

 <select id="findUserByName2" resultType="com.example.demo.model.User">
 select * from userinfo where username like '%#{username}%';
 </select>

相当于: select * from userinfo where username like ‘%‘username’%’;

此时可以考虑使用mysql的内置函数concat处理,代码如下:

<select id="findUserByName3" resultType="com.example.demo.model.User">
 select * from userinfo where username like concat('%',#{usernam
e},'%');
</select>

6.5 多表查询

例如在articleinfo表查询出某篇文章的信息后,在userinfo表中查询出这篇文章的作者。

步骤如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

单元测试:

在这里插入图片描述

查询此时数据库中表的信息:
在这里插入图片描述

查看单元测试结果:

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

6.6 字段重命名

程序中的属性和数据库中的字段名不一致时,可使用resultMap来解决。当你和数据库管理员分属于不同部门时,如果他使用了一些“魔法数字”作为字段名,比如将currentTime命名为ct,这显然不太符合规范。此时我们可以在程序中使用不同的字段名,并返回字典映射:resultMap。

在这里插入图片描述

package com.example.demo.model;

import lombok.Data;

import java.util.Date;

@Data
public class UserInfo {
    private int author_id;
    private String username;
    private String password;
    private String photo;
    private Date createtime;
    private Date updatetime;
    private int state;
}
    <resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
        <id column="id" property="author_id"></id>
        <result column="username" property="username"></result>
        <result column="password" property="password"></result>
    </resultMap>

    <select id="getAll" resultMap="BaseMap">
        select * from userinfo
    </select>

在这里插入图片描述

单元测试:

    @Test
    void getAll() {
        List<UserInfo> list = userMapper.getAll();
        for (UserInfo user : list) {
            System.out.println(user.toString());
        }
    }

在这里插入图片描述

七:MyBatis动态SQL

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

你可以参阅MyBatis的官方文档,进行动态SQL的学习。

7.1 < if >标签

在注册用户的时候,通常存在一些必填项字段和选填项字段,例如手机号、用户名、密码通常都是必填字段,而年龄、性别、生日等则是选填项字段。那如果在添加用户的时候有不确定的字段传入,程序应该如何实现呢?此时就需要使用到MyBatis的动态标签< if >了。

在这里插入图片描述

例如userinfo表中的photo字段就是一个非必填字段,具体实现如下:

添加方法:

    //添加用户
    public int add(@Param("username") String username,
                   @Param("password") String password,
                   @Param("photo") String photo);

配置xml文件:

    <insert id="add">
        insert into userinfo(username,
        <if test="photo != null">
                photo,
        </if>
        password)
        values(#{username},
        <if test="photo != null">
            #{photo},
        </if>
        #{password})
    </insert>

进行单元测试:

    @Test
    void add() {
        int result = userMapper.add("zhangsan","123456",null);
        System.out.println("受影响行数 " + result);
    }

在这里插入图片描述

注意:

在这里插入图片描述

7.2 < trim >标签

之前的插入用户功能,只是有⼀个photo字段可能是选填项,如果所有字段都是非必填项,就考虑使用< trim >标签结合< if >标签,对多个字段都采取动态生成的方式。

在这里插入图片描述

调整 UserMapper.xml 的插入语句为:

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

进行单元测试:

    @Test
    void add() {
        int result = userMapper.add("telier","156552","telier.png");
        System.out.println("受影响行数 " + result);
    }

在这里插入图片描述

解释:

在这里插入图片描述

7.3 < where >标签

传入的用户对象,根据属性做 where 条件查询,用户对象中属性不为 null 的,都为查询条件。

例如根据用户名查询符合条件的用户,如果用户名不匹配,应该显示数据库中的所有用户:

    // 根据用户姓名匹配查询
    public List<UserInfo> getUserByName(@Param("username") String username);
    <select id="getUserByName" resultType="com.example.demo.model.UserInfo">
        select * from userinfo
        <where>
            <if test="username != null">
                and username=#{username}
            </if>
        </where>
    </select>

单元测试:

测试一:

    @Test
    void getUserByName() {
        List<UserInfo> list = userMapper.getUserByName(null);
        for (UserInfo user: list) {
            System.out.println(user);
        }
    }

运行结果:

在这里插入图片描述

测试二:

    void getUserByName() {
        List<UserInfo> list = userMapper.getUserByName("zhangsan");
        for (UserInfo user: list) {
            System.out.println(user);
        }
    }

运行结果:

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

7.4 < set >标签

根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容。

示例:UserMapper 接口中修改用户方法:根据传入的用户 id 属性,修改其他不为 null 的属性。

    //根据用户id修改用户数据
    int updateById(UserInfo userInfo);
    <update id="updateById" parameterType="com.example.demo.model.UserInfo">
        update userInfo
        <set>
            <if test="username != null">
                username=#{username},
            </if>
            <if test="password != null">
                password=#{password},
            </if>
            <if test="photo != null">
                photo=#{photo},
            </if>
            <if test="createtime != null">
                createtime=#{createtime},
            </if>
            <if test="updatetime != null">
                updatetime=#{updatetime},
            </if>
            <if test="state != null">
                state=#{state},
            </if>
        </set>
        where id=#{id}
    </update>

进行单元测试:

    @Test
    void updateById() {
        UserInfo userInfo = userMapper.getUserById(7);
        userInfo.setUsername("caixukun");
        userInfo.setPhoto("jinitaimei.png");
        int result = userMapper.updateById(userInfo);
        System.out.println("受影响行数 " + result);
    }

测试前用户表中的数据:

在这里插入图片描述

测试后用户表中的数据:

在这里插入图片描述

在这里插入图片描述

7.5 < for each >标签

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

在这里插入图片描述
示例:根据多个用户 id 来删除用户数据。

    // 多条用户的删除
    public int delByIds(List<Integer> ids);
    <delete id="delByIds">
        delete from userinfo where id in
        <foreach collection="ids" item="item" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </delete>

进行单元测试:

    @Test
    void delByIds() {
        List<Integer> list = new ArrayList<>();
        list.add(6);
        list.add(8);
        list.add(9);
        userMapper.delByIds(list);
    }

在这里插入图片描述

已经成功删除了id为6,8,9号的用户。

在这里插入图片描述


以上就是MyBatis的全部内容 !

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值