JavaEE13-MyBatis查询数据库

目录

1.MyBatis是什么?

2.为什么要学习MyBatis?

3.怎么学MyBatis?

4.搭建MyBatis开发环境

4.0.准备工作:创建数据库和表

4.1.添加MyBatis框架支持

4.1.1.老项目添加MyBatis(对自己之前的Spring项目进行升级)

4.1.2.新项目添加MyBatis(创建一个全新的MyBatis和Spring Boot的项目)

4.2.配置数据库的连接字符串和MyBatis的XML保存路径

4.2.1.在配置文件中配置数据库的连接字符串(系统的,key固定,value不固定)

4.2.2.配置MyBatis的XML保存路径(要使用XML的方式来操作MyBatis)

5.使用MyBatis模式操作数据库(添加业务代码)实现CRUD中的查询操作

PS:MyBatis模式

5.1.添加实体类

5.2.创建@Mapper接口文件

5.3.使用XML实现@Mapper接口的方法

5.4.验收环节(测试所写代码是否有效)

5.4.1.使用常规的请求方法(创建controller和service层,补全层级结构,让UserController调用UserService,UserService调用UserMapper,再把结果层层返回)

--->PS:关于Idea专业版UserService类中@Autowired报错但不影响执行的问题的原因分析:(社区版功能简陋,不会报错)

--->PS:MyBatis在整个框架中的定位/框架交互流程图:

5.4.2.使用Spring Boot单元测试

6.使用MyBatis模式操作数据库(添加业务代码)实现CRUD中的增/删/改操作

6.1.添加功能实现

6.1.1.传2个参数,返回1个受影响的行数

6.1.2.传1个对象,返回1个添加的用户的id(自增id)

6.2.修改功能实现

6.3.删除功能实现

7.更复杂的查询操作

7.1.单表查询

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

7.1.2.${} 优点

7.1.3.SQL注入问题

7.1.4.like查询

--->PS:当报错信息不明确时,可以在配置文件application.xml中进行配置,打印MyBatis最终执行的SQL,这样有助于排查问题。

7.2.多表查询

7.2.1.返回类型/结果对象:resultType

7.2.2.返回字典映射:resultMap

7.2.3.多表查询之一对一:一篇文章一个用户

7.2.4.多表查询之一对多:一个用户多篇文章

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

8.1.if标签

​8.2.trim标签

8.3.where标签

8.4.set标签

8.5.foreach标签


前言:前面已经学习了Spring,Spring Boot,Spring MVC这3个框架,接下来学习第4个框架MyBatis(国内用的多):将前端传递的数据存储起来(前身IBatis)或者查询数据库里面的数据。

1.MyBatis是什么?

MyBatis是一款优秀的持久层框架(ORM框架),它支持自定义SQL存储过程 (以程序里写方法的方式来写SQL,问题:无法一步步调试。企业里几乎不用) 以及高级映射。MyBatis去除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML注解配置映射原始类型接口JavaPOJO (Plain Old Java Objects,普通老式Java对象) 为数据库中的记录

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

优点:支持中文,灵活度高;缺点:跨数据库方面很差。

MyBatis官网https://mybatis.org/mybatis-3/zh/index.htmlorg -> 开源框架域名(非盈利性的公益组织)

MyBatis支持的操作方式:

①3.1版本之前:支持XML的操作方式(主流90%企业在用)重点是写SQL自由度灵活度大;

②3.1版本之后:添加了使用注解实现数据库的操作方式(企业里几乎不用)实现特殊业务时会非常复杂。

PS:不同版本号区别

  • 3.5.1 -> 3.5的第一个版本
  • 3.5.10 -> 3.5的第十个版本

2.为什么要学习MyBatis?

对于后端开发来说,程序是由①后端程序②数据库这两部分组成的。

JavaEE13-MyBatis查询数据库_第1张图片

而这两部分要通讯,就要依赖数据库连接工具(有JDBC,MyBatis)。

JDBC操作流程:

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

JDBC 操作示例回顾:通过 JDBC 的 API 向数据库中添加⼀条记录,修改⼀条记录,查询⼀条记录:

-- 创建数据库
create database if not exists `library` default character set utf8mb4;
-- 使⽤数据库
use library;
-- 创建表
create table if not exists `soft_bookrack` (
 `book_name` varchar(32) NOT NULL,
 `book_author` varchar(32) NOT NULL,
 `book_isbn` varchar(32) NOT NULL primary key
) ;
import lombok.Data;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SimpleJdbcOperation {
    private final DataSource dataSource;

    public SimpleJdbcOperation(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 添加⼀本书
     */
    public void addBook() {
        Connection connection = null;
        PreparedStatement stmt = null;
        try {
            //获取数据库连接
            connection = dataSource.getConnection();
            //创建语句
            stmt = connection.prepareStatement("insert into soft_bookrack (book_name, book_author, book_isbn) values (?,?,?);");
            //参数绑定
            stmt.setString(1, "Spring in Action");
            stmt.setString(2, "Craig Walls");
            stmt.setString(3, "9787115417305");
            //执⾏语句
            stmt.execute();
        } catch (SQLException e) {
            //处理异常信息
        } finally {
            //清理资源
            try {
                if (stmt != null) {
                    stmt.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                //
            }
        }
    }

    /**
     * 更新⼀本书
     */
    public void updateBook() {
        Connection connection = null;
        PreparedStatement stmt = null;
        try {
            //获取数据库连接
            connection = dataSource.getConnection();
            //创建语句
            stmt = connection.prepareStatement("update soft_bookrack set book_author=? where book_isbn=?;");
            //参数绑定
            stmt.setString(1, "张卫滨");
            stmt.setString(2, "9787115417305");
            //执⾏语句
            stmt.execute();
        } catch (SQLException e) {
            //处理异常信息
        } finally {
            //清理资源
            try {
                if (stmt != null) {
                    stmt.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                //
            }
        }
    }

    /**
     * 查询⼀本书
     */
    public void queryBook() {
        Connection connection = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        Book book = null;
        try {
            //获取数据库连接
            connection = dataSource.getConnection();
            //创建语句
            stmt = connection.prepareStatement("select book_name, book_author, book_isbn from soft_bookrack where book_isbn =?");
            //参数绑定
            stmt.setString(1, "9787115417305");
            //执⾏语句
            rs = stmt.executeQuery();
            if (rs.next()) {
                book = new Book();
                book.setName(rs.getString("book_name"));
                book.setAuthor(rs.getString("book_author"));
                book.setIsbn(rs.getString("book_isbn"));
            }
            System.out.println(book);
        } catch (SQLException e) {
            //处理异常信息
        } finally {
            //清理资源
            try {
                if (rs != null) {
                    rs.close();
                }
                if (stmt != null) {
                    stmt.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                //
            }
        }
    }

    @Data
    public static class Book {
        private String name;
        private String author;
        private String isbn;
    }
}

从上述代码和操作流程可以看出JDBC操作⾮常繁琐,不但要拼接每⼀个参数,⽽且还要按照模板代码的⽅式,⼀步步操作数据库,并且在每次操作完,还要⼿动关闭连接等,⽽所有的这些操作步骤都需要在每个⽅法中重复书写。

而MyBatis可以帮助我们更简单,更⽅便,更快速地操作数据库。

3.怎么学MyBatis?

MyBatis 学习分为两部分:

  • 配置 MyBatis 开发环境(建立在Spring Boot和Spring MVC的基础上)。
  • 使⽤ MyBatis 模式和语法操作数据库。

4.搭建MyBatis开发环境

4.0.准备工作:创建数据库和表

-- 创建数据库
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,'javatitle','http://www.baidu.com',1);

JavaEE13-MyBatis查询数据库_第2张图片

4.1.添加MyBatis框架支持

4.1.1.老项目添加MyBatis(对自己之前的Spring项目进行升级)

JavaEE13-MyBatis查询数据库_第3张图片

JavaEE13-MyBatis查询数据库_第4张图片

 JavaEE13-MyBatis查询数据库_第5张图片

JavaEE13-MyBatis查询数据库_第6张图片

JavaEE13-MyBatis查询数据库_第7张图片

 一定要记得点击重新导入:

4.1.2.新项目添加MyBatis(创建一个全新的MyBatis和Spring Boot的项目)

JavaEE13-MyBatis查询数据库_第8张图片

JavaEE13-MyBatis查询数据库_第9张图片

JavaEE13-MyBatis查询数据库_第10张图片

JavaEE13-MyBatis查询数据库_第11张图片

JavaEE13-MyBatis查询数据库_第12张图片

删除4个无效文件后就是ssm项目了。

注:针对Idea社区版,此时因为没有配置连接的数据库服务器地址,所以如果启动项目就会报错。而专业版会帮助我们自动生成关于数据库的配置信息。

JavaEE13-MyBatis查询数据库_第13张图片

4.2.配置数据库的连接字符串和MyBatis的XML保存路径

4.2.1.在配置文件中配置数据库的连接字符串(系统的,key固定,value不固定)

使用yml写法更简单:

# 配置数据库的连接字符串
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8mb4  #utf8不支持一些复杂的中文
    username: root
    password: 12345678
    driver-class-name: com.mysql.cj.jdbc.Driver  #底层驱动的名称(8.0之前版本不加.cj,8.0之后版本加.cj)

JavaEE13-MyBatis查询数据库_第14张图片

JavaEE13-MyBatis查询数据库_第15张图片

4.2.2.配置MyBatis的XML保存路径(要使用XML的方式来操作MyBatis)

MyBatis的XML中保存的是查询数据库的具体操作SQL。

# 配置mybatis xml的文件路径,在resource包下建立mybatis(其命名自定义)包,在resource/mybatis创建所有表的xml文件
mybatis:
  mapper-locations: classpath:mybatis/**Mapper.xml

JavaEE13-MyBatis查询数据库_第16张图片

JavaEE13-MyBatis查询数据库_第17张图片

5.使用MyBatis模式操作数据库(添加业务代码)实现CRUD中的查询操作

PS:MyBatis模式

JavaEE13-MyBatis查询数据库_第18张图片

使用MyBatis的XML的操作方式:@Mapper文件(定义方法,没有方法实现)+XML会实现@Mapper的方法体

其每一个功能都必须要去操作2部分:

  • Interface(方法定义):就是普通的接口,会在这个接口里面定义需要实现的方法(有方法名称和传递的参数,没有方法体),需要通过一个注解@Mapper来将Inteface中的方法映射到一个XML文件中。(当然在xml文件中也需要表明是哪个方法的映射)

【@Mapper注解:来自ibatis(它是mybatis的前身(改名了),二者是一个东西)。

数据持久层是一个层级,底下有很多种实现,@Repository(仓库)只是其中的一种实现;而现在的实现是MyBatis(实现程序和数据库的一个映射,将数据库的某一张表对应到程序中的某一个类中,进而实现增删改查),它是通过@Mapper(映射)来实现数据持久层的】

【此时就学了7个类注解了:@Controller,@Service,@Repository,@Component,

@Configuration,@RestController,@Mapper。

它们都能把当前的类在Spring Boot项目启动时直接进行初始化/实例化】

  • XML(方法实现):是Interface实现的一个子类,具体的操作SQL就是在XML中实现的。(为什么要使用一个xml来实现一个interface?因为在MyBatis里面写的SQL放在类里只能是String类型,看起来很别扭;而放在xml里看起来会协调很多)在xml里,只需配置要实现的Interface是谁,要实现的SQL是啥。
  • mybatis会将Interface和XML里的内容合在一起,生成一个实例对象,其方法是含方法体的完善的方法。最终生成要执行的SQL。
  • 通过这个SQL语句,去操作数据库。将操作数据库得到的结果直接返回给Interface中方法的返回值,再返回给服务层;服务层,再返回给控制层;控制层,再把结果交给用户。这样就完成了一次交互。

之所以可以这样,是因为MyBatis是一个ORM框架,是一个对象和数据库映射的框架。

查询单条数据:

5.1.添加实体类

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * 实体类
 */
@Setter
@Getter //这两个注解可以用@Data一个注解代替
@ToString //这样打印对象的时候打印的是对象里的属性和值而不是对象的地址
public class UserInfo {
    //实体类里的字段和数据库创建的字段一定要保证一致。一些特殊情况下也可以不一致,而mybatis也是可以处理这种情况的。
    private int id;
    private String username;
    private String password;
    private String photo;
    private String createtime;
    private String updatetime;
    private int state;
}

5.2.创建@Mapper接口文件

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

@Mapper
public interface UserMapper {
    //查询方法定义完成
    public UserInfo getUserById(Integer id);
}

JavaEE13-MyBatis查询数据库_第19张图片

JavaEE13-MyBatis查询数据库_第20张图片

5.3.使用XML实现@Mapper接口的方法

数据持久层的实现mybatis的固定xml格式的配置文件(不用记,保存起来即可):








    

  • 标签:需要指定namespace属性,表示命名空间,值为mapper接口的完整路径:包名.类名。其中namespace的"xxx"是java源代码下的路径。

JavaEE13-MyBatis查询数据库_第21张图片

  •   select * from userinfo where id=#{id}

    再向数据库中添加一条数据:

    JavaEE13-MyBatis查询数据库_第47张图片

    生成第2个单元测试方法:

    JavaEE13-MyBatis查询数据库_第48张图片

    JavaEE13-MyBatis查询数据库_第49张图片

     JavaEE13-MyBatis查询数据库_第50张图片

    import com.example.demo.model.UserInfo;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import javax.annotation.Resource;
    import java.util.List;
    
    //1.标识当前测试的类的运行环境为spring boot
    @SpringBootTest
    class UserMapperTest {
    
        //2.补充测试的业务代码
        @Resource
        private UserMapper userMapper;
    
        @Test //每一个添加@Test注解的方法都可以单独执行的
        void getUserById() {
            //测试的具体业务
            UserInfo userInfo = userMapper.getUserById(1);
            //使用断言:如果结果不等于null表示是成功的
            Assertions.assertNotNull(userInfo);
        }
    
        @Test
        void save() {
            System.out.println("你好,单元测试。");
        }
    
        @Test
        void getAll() {
            List list = userMapper.getAll();
            System.out.println(list); //条数是动态改变的,这里不写断言
        }
    }

    JavaEE13-MyBatis查询数据库_第51张图片

    或者将controller,service,mapper对应类中所有的代码补充完,单元测试可以测试UserController类。

    6.使用MyBatis模式操作数据库(添加业务代码)实现CRUD中的增/删/改操作

    对应MyBatis的标签:

    • 标签:插入语句
    • 标签:修改语句
    • 标签:删除语句

    6.1.添加功能实现

    6.1.1.传2个参数,返回1个受影响的行数

    import com.example.demo.model.UserInfo;
    import org.apache.ibatis.annotations.Mapper;
    import java.util.List;
    
    @Mapper
    public interface UserMapper {
        //查询方法定义完成
        public UserInfo getUserById(Integer id);
    
        //查询多条数据
        public List getAll();
    
        //添加用户,默认情况返回一个受影响的行数
        //save配合find使用;add配合get使用
        public int add(String username, String password); 
    }

    JavaEE13-MyBatis查询数据库_第52张图片

    
    
    
        
        
    
        
        
    
        
        
            insert into userinfo(username,password)
            values(#{name},#{password})
        
    
    
    import com.example.demo.model.UserInfo;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import javax.annotation.Resource;
    import java.util.List;
    
    //1.标识当前测试的类的运行环境为spring boot
    @SpringBootTest
    class UserMapperTest {
    
        //2.补充测试的业务代码
        @Resource
        private UserMapper userMapper;
    
        @Test //每一个添加@Test注解的方法都可以单独执行的
        void getUserById() {
            //测试的具体业务
            UserInfo userInfo = userMapper.getUserById(1);
            //使用断言:如果结果不等于null表示是成功的
            Assertions.assertNotNull(userInfo);
        }
    
        @Test
        void save() {
            System.out.println("你好,单元测试。");
        }
    
        @Test
        void getAll() {
            List list = userMapper.getAll();
            System.out.println(list); //条数是动态改变的,这里不写断言
        }
    
        @Test
        void add() {
            int result = userMapper.add("李四", "123");
            Assertions.assertEquals(1, result);
        }
    }

    JavaEE13-MyBatis查询数据库_第53张图片 JavaEE13-MyBatis查询数据库_第54张图片

    JavaEE13-MyBatis查询数据库_第55张图片

    大多数时候数据库里的字段名和方法中对象的属性名还是保持一致好,会减少不必要的麻烦。

    6.1.2.传1个对象,返回1个添加的用户的id(自增id)

    主要的区别在xml中:

    JavaEE13-MyBatis查询数据库_第56张图片

    useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false。

    keyColumn:设置⽣成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第⼀列的时候,是必须设置的。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。

    keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或 insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。

    //添加用户2,返回自增id,id不能当作返回值返回回去,但可以把id赋值到对象里的某一个属性里
    public void add2(UserInfo userInfo); //此处返回值用int(返回一个受影响的行数)也行,但没啥用而且容易出现歧义
    
    
        insert into userinfo(username,password)
        values(#{username},#{password}) 
    
    @Test
    void add2() {
       UserInfo userInfo = new UserInfo();
       userInfo.setUsername("老6");
       userInfo.setPassword("123");
       userMapper.add2(userInfo); //此行代码执行完没有任何返回值为void
       System.out.println("自增id:" + userInfo.getId()); //没设置之前id是等于0或null的
    }

    JavaEE13-MyBatis查询数据库_第57张图片

    JavaEE13-MyBatis查询数据库_第58张图片

    6.2.修改功能实现

    在UserMapper接口中添加方法声明:

    //修改操作,根据id修改username
    //方法返回值int默认也是返回一个受影响的行数
    //方法命名update随便命
    //方法传参随便传
    public int update(@Param("id") int id, @Param("username") String username);

    在UserMapper.xml中添加对应标签:

    
    
        update userinfo set username=#{username} where id=#{id}
    

    在UserMapperTest中添加测试方法:

    @Test
    void update() {
        int result = userMapper.update(5, "老五");
        Assertions.assertEquals(1, result);
    }

    JavaEE13-MyBatis查询数据库_第59张图片

    6.3.删除功能实现

    //删除方法,返回受影响的行数
    public int del(@Param("id") int id);
    
    
         delete from userinfo where id=#{id}
    
    @Transactional //单元测试的类或方法上加上此注解,表示当前测试的数据不会影响数据库
    @Test
    void del() {
        int result = userMapper.del(5);
        Assertions.assertEquals(1, result);
    }

    JavaEE13-MyBatis查询数据库_第60张图片 JavaEE13-MyBatis查询数据库_第61张图片

    7.更复杂的查询操作

    7.1.单表查询

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

    二者在UserMapper.xml中执行效果一样。

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

    String sql = "select * from userinfo where id=?";
    statement.setInt(1, id); //设置占位符

    会认为是一个参数,而不是一个SQL语句。?里即使有不安全的SQL脚本也执行不了。调用有参的mysql方法。

    没有安全问题/漏洞。

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

    String sql = "select * from userinfo where id="+id;

    是进行拼接的,会认为拼接后的整体就是一个SQL语句,所以当id传的不是一个参数值,而是一个危险的SQL脚本时,会原样执行拼接好的SQL。调用无参的mysql方法。

    存在安全漏洞。

    JavaEE13-MyBatis查询数据库_第62张图片

    在UserMapper中写方法声明:
    //根据名称查询用户对象(非模糊查询,全字查询)
    public UserInfo getUserByName(@Param("username") String username);

    在UserMapper.xml中写对应 select * from userinfo where username=#{username}

    在UserMapperTest中写测试方法:

    @Test
    void getUserByName() {
        UserInfo userInfo = userMapper.getUserByName("李四");
        System.out.println(userInfo);
    }

    JavaEE13-MyBatis查询数据库_第63张图片

    当在xml中将#{}换成${}再去执行时:

    JavaEE13-MyBatis查询数据库_第64张图片

     无脑添加单引号存在问题:①麻烦;②性能降低;③安全性问题;

    JavaEE13-MyBatis查询数据库_第65张图片

    二者区别(头等舱和经济舱乘机分离的故事):
    • 在坐⻜机的时候头等舱和经济舱的区别是很⼤的,⼀般航空公司乘机都是头等舱和经济舱分离的,头等舱的⼈先登机,登机完之后,封闭头等舱,然后再让经济舱的乘客登机,这样的好处是可以避免经济舱的⼈混到头等舱的情况。这就相当于预处理,可以解决程序中不安全(越权处理)的问题。

    JavaEE13-MyBatis查询数据库_第66张图片

    • ⽽直接替换的情况相当于,头等舱和经济舱不分离的情况,这样经济舱的乘客在通过安检之后可能越权摸到头等舱。这就相当于参数直接替换,它的问题是可能会带来越权查询和操作数据等问题,⽐如 SQL 注⼊问题。

    JavaEE13-MyBatis查询数据库_第67张图片

    7.1.2.${} 优点

    使⽤ ${sort} 可以实现排序查询,⽽使⽤ #{sort} 就不能实现排序查询了,因为当使⽤ #{sort} 查询时, 如果传递的值为 String 则会加单引号,就会导致 sql 错误。
    JavaEE13-MyBatis查询数据库_第68张图片
    使用#{}
    //根据时间排序查询所有的用户
    public List getAllOrderByCreateTime(@Param("order") String order); //不怕方法名字长,就怕方法语义不明确
    @Test
    void getAllOrderByCreateTime() {
        List list = userMapper.getAllOrderByCreateTime("desc");
        System.out.println(list);
    }

    JavaEE13-MyBatis查询数据库_第69张图片

    使用${}

    JavaEE13-MyBatis查询数据库_第70张图片

    ${}使用场景:当传递的参数是一个SQL语句(而非一个某个参数的值)(的一部分)时,只能使用${}的形式。比如传递排序的desc或asc时,它是SQL语句的一部分,而非某一个参数的值,只能用${},用#{}会报错。

    7.1.3.SQL注入问题

    JavaEE13-MyBatis查询数据库_第71张图片

    //登录方法
    public UserInfo login(@Param("username") String username, @Param("password") String password);
    @Test
    void login() {
        String username = "admin";
        String password = "admin";
        UserInfo userInfo = userMapper.login(username, password);
        System.out.println(userInfo);
    }

    JavaEE13-MyBatis查询数据库_第72张图片

    若输入错误的密码:

    JavaEE13-MyBatis查询数据库_第73张图片

    @Test
    void login() {
        String username = "xxx";
        String password = "' or 1='1"; //SQL注入
        UserInfo userInfo = userMapper.login(username, password);
        System.out.println(userInfo);
    }

    JavaEE13-MyBatis查询数据库_第74张图片

    不仅可以在用户名和密码都乱输入的情况下查询到用户所有信息,还可以注入SQL语句删库!非常危险!

    原因:

    select * from userinfo where username='${username}' and password='${password}'

    直接替换为:

    select * from userinfo where username='xxx' and password='' or 1='1'

    select * from userinfo where username='xxx' and password='' or 1='1'

    JavaEE13-MyBatis查询数据库_第75张图片

    解决:将${}换成#{}。

    JavaEE13-MyBatis查询数据库_第76张图片

    结论:用于查询的字段,尽量使用#{}预查询的方式;若非要使用${}业务要传SQL脚本,一定要对参数进行效验,将要传的值规定为固定的某一/几个值,效验通过执行,否则不执行。

    7.1.4.like查询

    ①使用#{}拼接:执行报错,不可使用。

    //根据名称进行模糊查询
    public List getUserByLikeName(@Param("username") String username);
    
    
    @Test
    void getUserByLikeName() {
        String username = "admin";
        List list = userMapper.getUserByLikeName(username);
        System.out.println(list);
    }

    JavaEE13-MyBatis查询数据库_第77张图片

    --->PS:当报错信息不明确时,可以在配置文件application.xml中进行配置,打印MyBatis最终执行的SQL,这样有助于排查问题。

    # 配置mybatis xml的文件路径,在resource包下建立mybatis(其命名自定义)包,在resource/mybatis创建所有表的xml文件
    mybatis:
      mapper-locations: classpath:mybatis/**Mapper.xml
      configuration:  # 配置打印MyBatis最终执行的SQL
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    # 配置打印MyBatis最终执行的SQL
    logging:
      level:
        com:
          example:
            demo: debug

    JavaEE13-MyBatis查询数据库_第78张图片

    再去执行查看报错:

    JavaEE13-MyBatis查询数据库_第79张图片

    ②使用${}拼接:可实现功能,但存在安全风险。

    JavaEE13-MyBatis查询数据库_第80张图片

    虽然看起来没问题,但有SQL注入风险;而且需要进行业务效验处理(前提是结果可以穷举,否则无法做SQL注入的判定)。username无法穷举,不能使用。

    ③使用MySQL提供的内置函数concat()将结果集进行拼接操作。

    
    

    JavaEE13-MyBatis查询数据库_第81张图片

    JavaEE13-MyBatis查询数据库_第82张图片

    7.2.多表查询

    多表查询分为:一对一,一对多,多对多。但多对多的业务更加复杂,牵扯到3张表,查询时较麻烦,需要进行数据的组装。此处只考虑一对一和一对多。

    如果是增,删,改返回受影响的行数,在mapper.xml中可以不设置返回类型;然而查询必须要设置返回的类型,否则会报错。

    select * from articleinfo

    创建测试类ArticleMapperTest:

    import com.example.demo.model.ArticleInfo;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import javax.annotation.Resource;
    import java.util.List;
    
    @SpringBootTest
    class ArticleMapperTest {
        @Resource
        private ArticleMapper articleMapper;
    
        @Test
        void getAll() {
            List list = articleMapper.getAll();
            System.out.println(list);
        }
    }

    JavaEE13-MyBatis查询数据库_第84张图片

    解决:将resultType改为resultMap。

    只配置不同的项(title和name),不配置其他项的写法:单表查询没问题,但在多表查询中会有问题:

    
    
    
        
            
        
        
        
    

    JavaEE13-MyBatis查询数据库_第85张图片

    PS:

    JavaEE13-MyBatis查询数据库_第86张图片

    JavaEE13-MyBatis查询数据库_第87张图片

    7.2.3.多表查询之一对一:一篇文章一个用户

    import lombok.Data;
    
    /**
     * 文章实体类
     */
    @Data
    public class ArticleInfo {
        private int id;
        private String name;
        private String content;
        private String createtime;
        private String updatetime;
        private int uid;
        private int rcount;
        private int state;
        //所属用户
        private UserInfo userInfo;
    }
    import com.example.demo.model.ArticleInfo;
    import org.apache.ibatis.annotations.Mapper;
    import java.util.List;
    
    @Mapper
    public interface ArticleMapper {
        //查询articleinfo单张表的信息
        public List getAll();
    
        //查询articleinfo + userinfo多表的信息
        public List getAll2();
    }
    JavaEE13-MyBatis查询数据库_第88张图片
    
    
    
        
            
        
    
        
    
        
    
    
    import com.example.demo.model.ArticleInfo;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import javax.annotation.Resource;
    import java.util.List;
    
    @SpringBootTest
    class ArticleMapperTest {
        @Resource
        private ArticleMapper articleMapper;
    
        @Test
        void getAll() {
            List list = articleMapper.getAll();
            System.out.println(list);
        }
    
        @Test
        void getAll2() {
            List list = articleMapper.getAll2();
            System.out.println(list);
        }
    }

    JavaEE13-MyBatis查询数据库_第89张图片

    解决:一对一映射要使用标签(只支持resultMap不支持resultType)(一篇文章只对应一个作者):

    只配置不同的项(title和name),不配置其他项的写法:单表查询没问题,但在多表查询中会有问题:

    在UserMapper.xml中添加:

     
         
         
         
         
         
         
         
    

    若不写username:

    JavaEE13-MyBatis查询数据库_第90张图片

    在ArticleMapper.xml中写:

    
    
    
        
            
            
            
            
        
    
        
    

    JavaEE13-MyBatis查询数据库_第91张图片

    在UserMapper.xml中写上username:

    JavaEE13-MyBatis查询数据库_第92张图片

    所以多表查询中所有项都要写完整,缺一不可。补上:

    在ArticleMapper.xml中需要写:

    
    
    
        
            
            
            
            
            
            
            
            
            
            
            
        
    
        
    

     这样就看起来没问题了:

    JavaEE13-MyBatis查询数据库_第93张图片

    但是如果进行改动:

    JavaEE13-MyBatis查询数据库_第94张图片

    JavaEE13-MyBatis查询数据库_第95张图片

    没问题。but:

    JavaEE13-MyBatis查询数据库_第96张图片

    解决:在ArticleMapper.xml中修改两处内容,即最终代码实现:

    
    
    
        
            
            
            
            
            
            
            
            
            
            
            
        
    
        
    

    JavaEE13-MyBatis查询数据库_第97张图片

    JavaEE13-MyBatis查询数据库_第98张图片

    以上使用 标签,表示一对一的结果映射
    • property 属性:指定 ArticleInfo 中对应的属性,即⽤户userInfo。
    • resultMap 属性:指定关联的结果集映射,将基于该映射配置来组织⽤户数据。
    • columnPrefix 属性:绑定⼀对⼀对象时,是通过columnPrefix+association.resultMap.column 来映射结果集字段。 association.resultMap.column是指 标签中 resultMap属性,对应的结果集映射中,column字段。columnPrefix如果省略,并且恰好2个表中有相同的字段,那么前一个字段会覆盖后一个字段,查询会出错。

    7.2.4.多表查询之一对多:一个用户多篇文章

    在UserInfo中添加字段:

    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    import java.util.List;
    
    /**
     * 实体类
     */
    @Setter
    @Getter //这两个注解可以用@Data一个注解代替
    @ToString //这样打印对象的时候打印的是对象里的属性和值而不是对象的地址
    public class UserInfo {
        //实体类里的字段和数据库创建的字段一定要保证一致。一些特殊情况下也可以不一致,而mybatis也是可以处理这种情况的。
        private int id;
        private String username;
        private String password;
        private String photo;
        private String createtime;
        private String updatetime;
        private int state;
        //多对多添加的字段
        List alist;
    }
    

    先完善数据库里的数据:

    JavaEE13-MyBatis查询数据库_第99张图片JavaEE13-MyBatis查询数据库_第100张图片

    在UserMapper接口中添加方法声明:

    //查询所有数据(联表查询userinfo & articleinfo)
    public List getAll2();

    在UserMapper.xml中添加 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

    在UserMapperTest测试类中添加测试方法:

    @Test
    void getAll2() {
        List list = userMapper.getAll2();
        System.out.println(list);
    }

    JavaEE13-MyBatis查询数据库_第101张图片8.复杂情况:动态SQL使用

    动态sql是MyBatis的强大特性之一,能够完成不同条件下不同sql的拼接,都是去解决非必传项的问题的。下面讲的几种标签解决了CRUD(增删查改)场景。

    可参考官方文档:MyBatis动态sqlhttps://mybatis.org/mybatis-3/zh/dynamic-sql.html

    8.1.if标签

    场景:做插入/添加数据操作。

    JavaEE13-MyBatis查询数据库_第102张图片

    那么如果在添加用户时有不确定的字段传入,就需要使用动态标签来判断了。

    【不传的按理应该不设置(性能更好)(会走默认约束:若设置了默认的值,会放默认的值;若没设置默认的值,会放null,null不是表示这个值为null,而是表示目前这个字段没有任何东西。)

    而不是设置为空字符串(表示这个字段有值,只不过这个值是空。)

    二者意义完全不同】

    JavaEE13-MyBatis查询数据库_第103张图片

    在UserMapper接口中添加方法声明:

    //添加用户3(使用动态sql if)
    public int add3(UserInfo userInfo);

     在UserMapper.xml中写动态标签:

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

    注:在test中的photo是传入对象中的属性,不是数据库中的字段。

    在UserMapperTest中添加测试方法:

    先不传图片:(没有非必传项)

    @Test
    void add3() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("张三");
        userInfo.setPassword("123");
      //userInfo.setPhoto(null); //这样写不写执行结果都一样
        int result = userMapper.add3(userInfo);
        System.out.println("受影响的行数:" + result);
    }

    JavaEE13-MyBatis查询数据库_第104张图片

    传图片:(有非必传项)

    @Test
    void add3() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("李四");
        userInfo.setPassword("456");
        userInfo.setPhoto("lisi.png");
        int result = userMapper.add3(userInfo);
        System.out.println("受影响的行数:" + result);
    }

    JavaEE13-MyBatis查询数据库_第105张图片

    JavaEE13-MyBatis查询数据库_第106张图片8.2.trim标签

    场景:做插入/添加数据操作。

    之前在JQuery中也有使用:清空多余的空白字符。

    刚刚的插⼊功能,只是有⼀个 photo 字段可能是选填项,如果所有/多个字段都是非必填项,就考虑使⽤标签结合标签,对多个字段都采取动态⽣成的⽅式。

    标签中有如下属性:

    • prefix:添加前缀。表示整个语句块,以prefix的值作为前缀。(可以不添加)
    • suffix:添加后缀。表示整个语句块,以suffix的值作为后缀。(可以不添加)
    • prefixOverrides:去掉前缀。表示整个语句块要去除掉的前缀。 (宝藏属性)
    • suffixOverrides:去掉后缀。表示整个语句块要去除掉的后缀。(宝藏属性)
    //添加用户4(使用动态sql trim + if)
    public int add4(UserInfo userInfo); //假设所有字段都是可选项
    
    
        insert into userinfo
        
            
                username,
            
            
                password,
            
            
                photo
            
        
        values
        
            
                #{username},
            
            
                #{password},
            
            
                #{photo}
            
        
    
    @Test
    void add4() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("王五");
        userInfo.setPassword("456");
        int result = userMapper.add4(userInfo);
    }

    8.3.where标签

    场景:做where条件查询。会生成where关键字。

    情景1/亮点1:当标签中所有的参数都不传递时,那么整个where部分的sql都会省略,生成的SQL语句不会含有where部分。

    //根据名称或密码查询用户列表(使用动态sql where)
    public List getListByNameOrPwd(String username, String password);
    
    
    
        
            
            
            
            
            
            
            
            
            
        
    
        
        
    
    @Test
    void getListByNameOrPwd() {
        List list = userMapper.getListByNameOrPwd(null, null);
        System.out.println(list);
    }

    JavaEE13-MyBatis查询数据库_第107张图片

    情景2:传递前面的一个参数

    @Test
    void getListByNameOrPwd() {
        List list = userMapper.getListByNameOrPwd("admin", null);
        System.out.println(list);
    }

    JavaEE13-MyBatis查询数据库_第108张图片

    改动写法:

    
    
    
        
            
            
            
            
            
            
            
            
            
        
    
        
        
    

    JavaEE13-MyBatis查询数据库_第109张图片

    情景3:传递后一个参数:

    
    
    
        
            
            
            
            
            
            
            
            
            
        
    
        
        
    
    @Test
    void getListByNameOrPwd() {
        List list = userMapper.getListByNameOrPwd(null, "123");
        System.out.println(list);
    }

    JavaEE13-MyBatis查询数据库_第110张图片

    亮点2标签可以自动去掉最前面的and关键字。

    以上标签也可以使⽤ 替换。是特殊的,更简洁;功能更强大。

    
    
    
        
            
            
            
            
            
            
            
            
            
        
    
        
        
    

    JavaEE13-MyBatis查询数据库_第111张图片

    @Test
    void getListByNameOrPwd() {
        List list = userMapper.getListByNameOrPwd(null, null);
        System.out.println(list);
    }

    JavaEE13-MyBatis查询数据库_第112张图片

    8.4.set标签

    场景:做更新/修改数据。使用标签来指定动态内容。会生成set关键字,会将最后的逗号自动删除。

    public int updateById(UserInfo userInfo);

    JavaEE13-MyBatis查询数据库_第113张图片

    
        update userinfo
        
            
                username=#{username},
            
            
                password=#{password},
            
            
                photo=#{photo}
            
         
         where id=#{id}
    
    @Test
    void updateById() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(9);
        userInfo.setUsername("老五");
        int result = userMapper.updateById(userInfo);
        System.out.println("受影响行数:" + result);
    }

    JavaEE13-MyBatis查询数据库_第114张图片

    JavaEE13-MyBatis查询数据库_第115张图片

    以上标签也可以使⽤ 替换。

    
        update userinfo
        
             
                 username=#{username},
             
             
                 password=#{password},
             
             
                 photo=#{photo}
             
         
         where id=#{id}
    

    8.5.foreach标签

    场景:做删除数据。

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

    • collection:绑定⽅法参数中的集合名称(二者要一致),如 List,Set,Map或数组对象。
    • item:遍历时的每⼀个对象(给对象起的别名,命名随意)。
    • open:语句块开头的字符串。
    • close:语句块结束的字符串。
    • separator:每次遍历之间间隔的字符串。  
    • index:每次for循环的下标(不常用)。
    • nullable(不常用)。
    public int delByIds(List ids);

    JavaEE13-MyBatis查询数据库_第116张图片

    
        delete from userinfo where id in
        
            #{id}
        
    
    @Test
    void delByIds() {
        List list = new ArrayList<>();
        list.add(6);
        list.add(8);
        int result = userMapper.delByIds(list);
        System.out.println("受影响行数:" + result);
    }

    JavaEE13-MyBatis查询数据库_第117张图片

    JavaEE13-MyBatis查询数据库_第118张图片

你可能感兴趣的:(JavaEE,mybatis,spring,java)