• MyBatis查询数据库


    1.MyBatis 是什么?

    MyBatis(前身IBatis)定义:MyBatis 是一款优秀的持久层框架(ORM框架)

    MyBatis支持的操作方式:

    1.支持XML的操作方式(MyBatis 3.1 之前)【主流的操作方式】。
    2.使用注解实现数据的操作(MyBatis 3.1 之后)

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

    2.学习目标:

    1. 搭建一个MyBatis开发环境(建立在Spring Boot 和Spring MVC 的基础上)。
    2. 基于MyBatis的开发模式,操作数据库。

    3.搭建MyBaits开发环境

    3.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';
    
    -- 创建文章表
    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';
    
    -- 创建视频表
    drop table if exists videoinfo;
    create table videoinfo(
      	vid int primary key,
      	`title` varchar(250),
      	`url` varchar(1000),
    		createtime datetime default now(),
    		updatetime datetime default now(),
      	uid int
    )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);
    
    -- 文章添加测试数据
    insert into articleinfo(title,content,uid)
        values('Java','Java正文',1);
        
    -- 添加视频
    insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    3.2在项目中添加Mybatis框架

    3.2.1新项目添加Mybatis

    s框架
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    耐心等待项目加载完成即可。
    在这里插入图片描述

    3.2.2原有项目添加Mybatis

    前置条件:添加插件Editstarters:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    之后导入包即可。
    注意事项:
    经过以上操作,就创建了SSM(Spring Boot/Spring MVC/MyBatis)项目,但此时因为没有配置连接的数据库服务器地址,如果此时启动项目,就会报错

    3.3 配置连接字符串和MyBatis的XML报错路径

    此步骤需要进行两项设置,数据库连接字符串设置和 MyBatis 的 XML 文件配置。

    3.3.1 配置连接字符串

    如果是 application.yml 添加如下内容:

    #配置数据库的连接字符串
    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8mb4
        username: root
        password: 12345678
        driver-class-name: com.mysql.cj.jdbc.Driver
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.3.2 配置MyBatis中XML保存路径(使用XML方式操作MyBatis)

    在这里插入图片描述

    #配置MyBatis中XML保存路径
    mybatis:
      mapper-locations: classpath:mybatis/**Mapper.xml
    
    • 1
    • 2
    • 3

    4.使用MyBatis模式实现增删改查(CRUD)操作

    MyBatis模式就是两个文件:@Mapper文件(定义方法,没有方法实现)+XML会实现@Mapper的方法体

    4.1 创建@Mapper接口文件(方法声明,没有方法实现)

    import com.example.demo.model.UserInfo;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface UserMapper {
        //查询方法定义完成
        public UserInfo getUserById(Integer id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.2 使用XML实现@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">
        <!-- 查询单条数据(根据id) -->
        <select id="getUserById" resultType="com.example.demo.model.UserInfo">
            <!--        select * from userinfo where id=#{id}-->
            select * from userinfo where id=#{id}
        </select>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    关于IDEA专业版@Autowired报错但不影响执行的问题原因分析:

    @Autowired属于Spring注解,而@Mapper属于MyBatis的注解,Spring中的@Autowired不能识别MyBatis的@Mapper注解,所以会报错。

    解决方案:
    将Service里的Spring的@Autowireg更换为JDK的@Resource

    4.3添加功能实现

    方法实现:

        <!-- 用户添加(返回受影响的行数) -->
        <insert id="add">
            insert into userinfo(username,password)
            values(#{username},#{password})
        </insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
        <!-- 用户添加(返回自增 ID) -->
        <insert id="add2" useGeneratedKeys="true" keyProperty="id">
            insert into userinfo(username,password) values(#{username},#{password})
        </insert>
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    方法声明:

        //添加用户(返回受影响行数)
        public int add(String username,String password);
    
    • 1
    • 2
        // 添加用户 2(返回的自增 ID)
        public void add2(UserInfo userInfo);
    
    • 1
    • 2

    在这里插入图片描述

    4.4修改功能实现

    方法声明:

        //修改操作(根据id修改username,返回受影响的行数)
        public int update(@Param("id") int id, @Param("username") String username);
    
    • 1
    • 2

    方法实现:

        <!-- 根据 id 修改 username(返回受影响的行数) -->
        <update id="update">
            update userinfo set username=#{username} where id=#{id}
        </update>
    
    • 1
    • 2
    • 3
    • 4

    4.5 删除用户操作

    方法声明:

        //删除方法(返回受影响的行数)
        public int del(@Param("id") int id);
    
    • 1
    • 2

    方法实现:

        <!-- 根据 id 进行删除(返回受影响的行数) -->
        <delete id="del">
            delete from userinfo where id=#{id}
        </delete>
    
    • 1
    • 2
    • 3
    • 4

    扩展功能:单元测试之后,将测试的数据进行回滚,也就是使用单元测试不污染数据库的数据

    @Transactional 注解

        @Transactional // 单元测试的类或者方法上加上此注解,表示当前测试的数据不会影响数据库
        @Test
        void del() {
            int result = userMapper.del(3);
            Assertions.assertEquals(1, result);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.查询操作

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

    #{}:预编译处理,不存在安全问题。
    ${}:字符直接替换,存在安全问题。

    ${}使用场景:当传递的是一个SQL语句时(而非一个某个参数的值),那么只能使用 ${}的形式,比如传递排序的 desc 或者是 asc 的时候,他是一个SQL语句,而非某一个参数的值。

        <!-- 根据时间排序查询所有的用户 -->
        <select id="getAllOrderByCreateTime" resultType="com.example.demo.model.UserInfo">
            select * from userinfo order by createtime ${order}
        </select>
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使用 PreparedStatement 的set 方法来赋值。
    直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。

    ${} 优点

    使用 ${sort} 可以实现排序查询,而使用 #{sort} 就不能实现排序查询了,因为当使用 #{sort} 查询时,如果传递的值为 String 则会加单引号,就会导致 sql 错误。

    SQL 注入问题

    方法声明:

        //登录方法
        public UserInfo login(@Param("username") String username,
                              @Param("password") String password);
    
    • 1
    • 2
    • 3

    方法实现(使用${}):

        <!-- 登录方法 -->
        <select id="login" resultType="com.example.demo.model.UserInfo">
            select * from userinfo where username='${username}' and password='${password}'
        </select>
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    结论:用于查询的字段,尽量使用 #{} 预查询的方式。

    扩充知识:打印MyBatis最终执行的SQL

    如图配置即可:
    在这里插入图片描述

    like 查询

    1.like 使用 #{} 报错

        <!-- 根据用户名称进行模糊查询 -->
        <select id="getUserByLikeName" resultType="com.example.demo.model.UserInfo">
            select * from userinfo where username like '%#{username}%'
        </select>
    
    • 1
    • 2
    • 3
    • 4

    2.使用 ${}

        <!-- 根据用户名称进行模糊查询 -->
        <select id="getUserByLikeName" resultType="com.example.demo.model.UserInfo">
            select * from userinfo where username like '%${username}%'
        </select>
    
    • 1
    • 2
    • 3
    • 4

    可以实现功能,但是存在安全风险。
    3.可以考虑使用 mysql 的内置函数 concat() 来处理,实现代码如下:

        <!-- 根据用户名称进行模糊查询 -->
        <select id="getUserByLikeName" resultType="com.example.demo.model.UserInfo">
            select * from userinfo where username like concat('%',#{username},'%')
        </select>
    
    • 1
    • 2
    • 3
    • 4

    5.2多表查询

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

    1. id 属性:用于标识实现接口中的那个方法;
    2. 结果映射属性:结果映射有两种实现标签:resultMap和 resultType

    5.2.1返回类型:resultType

    绝大数查询场景可以使用 resultType 进行返回,它的优点是使用方便,直接定义到某个实体类即可。

    5.2.2返回字典映射:resultMap

    resultMap 使用场景:

    1. 字段名称和程序中的属性名不同的情况,可使用 resultMap 配置映射;
    2. 多表关联查询(一对一和一对多)关系可以使用 resultMap 映射并查询数据。

    字段名和属性名不同的情况
    在这里插入图片描述
    程序中属性如下:
    在这里插入图片描述
    mapper.xml 代码如下:

        <select id="getAll" resultType="com.example.demo.model.ArticleInfo">
            select * from articleinfo
        </select>
    
    
    • 1
    • 2
    • 3
    • 4

    查询出来name=NULL;

    resultMap 的使用如下:
    在这里插入图片描述
    resultMap 基础使用:
    在这里插入图片描述
    在这里插入图片描述

    5.2.3多表查询

    5.2.3.1 一对一的表映射

    一对一映射要使用 association标签,具体实现如下(一篇文章只对应一个作者):
    ArticleMapper.xml实现代码:

        <resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">
            <id column="id" property="id"></id>
            <result column="title" property="name"></result>
            <result column="content" property="content"></result>
            <result column="createtime" property="createtime"></result>
            <result column="updatetime" property="updatetime"></result>
            <result column="uid" property="uid"></result>
            <result column="rcount" property="rcount"></result>
            <result column="state" property="state"></result>
            <!-- 一对一关系的实现 -->
            <association property="userInfo"
                         resultMap="com.example.demo.mapper.UserMapper.BaseMap"
                         columnPrefix="u_">
            </association>
        </resultMap>
    
        <select id="getAll2" resultMap="BaseMap">
            select a.*,u.id u_id,u.username u_username,u.password u_password from articleinfo a left join userinfo u on
            a.uid=u.id
        </select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    以上使用 association标签,表示一对一的结果映射:

    • property 属性:指定 Article 中对应的属性,即用户。
    • resultMap 属性:指定关联的结果集映射,将基于该映射配置来组织用户数据。
    • columnPrefix 属性:绑定一对一对象时,是通过columnPrefix+association.resultMap.column 来映射结果集字段。
      association.resultMap.column是指 标签中 resultMap属性,对应的结果集映射中,column字段。
      在这里插入图片描述
      userMapper.xml代码:
        <resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
            <id column="id" property="id"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="photo" property="photo"></result>
            <result column="createtime" property="createtime"></result>
            <result column="updatetime" property="updatetime"></result>
            <result column="state" property="state"></result>
            <collection property="alist"
                        resultMap="com.example.demo.mapper.ArticleMapper.BaseMap"
                        columnPrefix="a_">
            </collection>
        </resultMap>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    实体类:
    在这里插入图片描述

    5.2.3.2一对多:一个用户多篇文章案例

    一对多需要使用 collection 标签,用法和 association相同,如下所示:

        <resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
            <id column="id" property="id"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="photo" property="photo"></result>
            <result column="createtime" property="createtime"></result>
            <result column="updatetime" property="updatetime"></result>
            <result column="state" property="state"></result>
            <!-- 一对多关系的实现 -->
            <collection property="alist"
                        resultMap="com.example.demo.mapper.ArticleMapper.BaseMap"
                        columnPrefix="a_">
            </collection>
        </resultMap>
    
        <select id="getAll2" resultMap="BaseMap">
            select u.*,a.id a_id,a.title a_title,a.content a_content
            from userinfo u
            left join articleinfo a
            on u.id=a.uid
        </select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    6.动态SQL使用

    动态 sql 是Mybatis的强大特性之一,能够完成不同条件下不同的 sql 拼接。

    6.1 < if> 标签

    注册分为两种字段:必填字段和非必填字段,那如果在添加用户的时候有不确定的字段传入,程序应该如何实现呢?
    这个时候就需要使用动态标签 < if> 来判断了,比如添加的时候性别 sex 为非必填字段,具体实现如下:

        <!-- 用户添加(使用动态 sql if -->
        <insert id="add3">
            insert into userinfo(username,
            <if test="photo!=null">
                photo,
            </if>
            password)
            values(#{username},
            <if test="photo!=null">
                #{photo},
            </if>
            #{password})
        </insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

    6.2 < trim> 标签

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

    trim 标签中有如下属性:

    • prefix:表示整个语句块,以prefix的值作为前缀
    • suffix:表示整个语句块,以suffix的值作为后缀
    • prefixOverrides:表示整个语句块要去除掉的前缀
    • suffixOverrides:表示整个语句块要去除掉的后缀
        <!-- 用户添加(使用动态 sql trim + if -->
        <insert id="add4">
            insert into userinfo
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="username!=null">
                    username,
                </if>
                <if test="password!=null">
                    password,
                </if>
                <if test="photo!=null">
                    photo
                </if>
            </trim>
            values
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="username!=null">
                    #{username},
                </if>
                <if test="password!=null">
                    #{password},
                </if>
                <if test="photo!=null">
                    #{photo}
                </if>
            </trim>
        </insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述
    在以上 sql 动态解析时,会将第一个 trim 部分做如下处理:

    • 基于 prefix 配置,开始部分加上 (
    • 基于 suffix 配置,结束部分加上 )
    • 多个 组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于suffixOverrides 配置去掉最后一个 ,
    • 注意 6.3 < where> 标签
          <!-- 根据名称或密码查询用户列表(使用动态 sql where) -->
          <select id="getListByNameOrPwd" resultMap="BaseMap">
              select * from userinfo
              <where>
                  <if test="username!=null">
                      username=#{username} and
                  </if>
                  <if test="password!=null">
                      password=#{password}
                  </if>
              </where>
          </select>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      情景一:当不传递任何参数时,传递的SQL如下:
      在这里插入图片描述
      情景二:传递前面一个参数
      在这里插入图片描述
      使用以下代码可以解决这个问题:

          <!-- 根据用户名称进行模糊查询 -->
          <select id="getUserByLikeName" resultType="com.example.demo.model.UserInfo">
              select * from userinfo where username like concat('%',#{username},'%')
          </select>
      
          <!-- 根据名称或密码查询用户列表(使用动态 sql where) -->
          <select id="getListByNameOrPwd" resultMap="BaseMap">
              select * from userinfo
              <where>
                  <if test="username!=null">
                      username=#{username}
                  </if>
                  <if test="password!=null">
                      and password=#{password}
                  </if>
              </where>
          </select>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      在这里插入图片描述

      情景三:传递后面一个参数

       <!-- 根据用户名称进行模糊查询 -->
          <select id="getUserByLikeName" resultType="com.example.demo.model.UserInfo">
              select * from userinfo where username like concat('%',#{username},'%')
          </select>
      
          <!-- 根据名称或密码查询用户列表(使用动态 sql where) -->
          <select id="getListByNameOrPwd" resultMap="BaseMap">
              select * from userinfo
              <where>
                  <if test="username!=null">
                      username=#{username}
                  </if>
                  <if test="password!=null">
                      and password=#{password}
                  </if>
              </where>
          </select>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      在这里插入图片描述
      在这里插入图片描述
      也可以使用< trim>标签进行该写代码:

      <!-- 根据名称或密码查询用户列表(使用动态 sql where) -->
          <select id="getListByNameOrPwd" resultMap="BaseMap">
              select * from userinfo
              <trim prefix="where" prefixOverrides="and">
                  <if test="username!=null">
                      username=#{username}
                  </if>
                  <if test="password!=null">
                      and password=#{password}
                  </if>
              </trim>
          </select>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      6.4< set>标签

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

          <update id="updateById">
              update userinfo
              <set>
                  <if test="username!=null">
                      username=#{username},
                  </if>
                  <if test="password!=null">
                      password=#{password},
                  </if>
                  <if test="photo!=null">
                      photo=#{photo}
                  </if>
              </set>
              where id=#{id}
          </update>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      执行的SQL:
      在这里插入图片描述

      set标签已经自动将最后一个逗号删除掉
      使用< trim>标签也可以实现< set>的功能:

          <update id="updateById">
              update userinfo
                      <trim prefix="set" suffixOverrides=",">
                          <if test="username!=null">
                              username=#{username},
                          </if>
                          <if test="password!=null">
                              password=#{password},
                          </if>
                          <if test="photo!=null">
                              photo=#{photo}
                          </if>
                      </trim>
              where id=#{id}
          </update>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      6.5 < foreach>标签

      对集合进行遍历时可以使用该标签。< foreach>标签有如下属性:

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

      接口方法:

      public int delByIds(List<Integer> ids);
      
      • 1

      删除SQL:

          <delete id="delByIds">
              delete from userinfo where id in
              <foreach collection="ids" item="id" open="(" close=")" separator=",">
                  #{id}
              </foreach>
          </delete>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
  • 相关阅读:
    13.1 Go 反射(Reflection)
    elementplus、vxe、surely 三种表格组件的封装
    flink sql实战案例
    WPF 实现点击按钮跳转页面功能
    C++模板元模板(异类词典与policy模板)- - - 后篇
    【C++编程能力提升】
    IE 浏览器正式退休
    AD——PCB布线实用技巧
    广东2022年下半年系统集成项目管理工程师上午真题及答案解析
    2022聚·创赢未来 | GBASE数据库中华行已赴青城之约
  • 原文地址:https://blog.csdn.net/mzpqq/article/details/125972735