【JavaEE】-- MyBatis操作数据库(1)

文章目录

  • [1. 什么是MyBatis](#1. 什么是MyBatis)
  • [2 MyBatis入门](#2 MyBatis入门)
    • [2.1 准备工作](#2.1 准备工作)
      • [2.1.1 创建工程](#2.1.1 创建工程)
    • [2.2 配置数据库连接字符串](#2.2 配置数据库连接字符串)
    • [2.3 写持久层代码](#2.3 写持久层代码)
    • [2.4 单元测试](#2.4 单元测试)
  • [3. MyBatis的基础操作](#3. MyBatis的基础操作)
    • [3.1 打印日志](#3.1 打印日志)
    • [3.2 参数传递](#3.2 参数传递)
    • [3.3 增(insert)](#3.3 增(insert))
      • [3.3.1 返回主键](#3.3.1 返回主键)
    • [3.4 删(delete)](#3.4 删(delete))
    • [3.5 改(update)](#3.5 改(update))
    • [3.6 查(select)](#3.6 查(select))
      • [3.6.1 起别名](#3.6.1 起别名)
      • [3.6.2 结果映射](#3.6.2 结果映射)
      • [3.6.3 开启驼峰命名(推荐)](#3.6.3 开启驼峰命名(推荐))
  • [4. MyBatis XML配置文件](#4. MyBatis XML配置文件)
    • [4.1 配置连接字符串和MyBatis](#4.1 配置连接字符串和MyBatis)
    • [4.2 写持久层代码](#4.2 写持久层代码)
      • [4.2.1 添加mapper接口](#4.2.1 添加mapper接口)
      • [4.2.2 添加UserInfoXMLMapper.xml](#4.2.2 添加UserInfoXMLMapper.xml)
      • [4.2.3 单元测试](#4.2.3 单元测试)
    • [4.3 增删改查操作](#4.3 增删改查操作)
  • 5.其他查询操作
    • 5.1多表查询
      • [5.1.1 数据查询](#5.1.1 数据查询)
    • [5.2 #{}和{}](#{}和{})
      • [5.2.1 #{}和{}使用](#{}和{}使用)
        • [5.2.1.1 Interger类型的参数](#5.2.1.1 Interger类型的参数)
        • [5.2.1.1 String类型的参数](#5.2.1.1 String类型的参数)
      • [5.2.2 #{}和{}区别](#{}和{}区别)
    • [5.3 排序功能](#5.3 排序功能)
    • [5.4 like查询](#5.4 like查询)
  • [6. 数据库连接池](#6. 数据库连接池)
    • [6.1 介绍](#6.1 介绍)
    • [6.2 使用](#6.2 使用)
  • 7.总结
    • [7.1 MySQL开发企业规范](#7.1 MySQL开发企业规范)
    • [7.2 #{} 和{} 区别](#{} 和{} 区别)

1. 什么是MyBatis

  • MyBatis是一款优秀的持久层框架,用于简化JDBC的开发。
  • 持久层:指的就是持久化操作的层,通常指数据访问层(dao),用来操作数据库的。
  • 官网:MyBatis中文网

2 MyBatis入门

2.1 准备工作

Mybatis操作数据库的步骤:

  1. 准备工作(创建springboot工程、数据库表准备、实体类)
  2. 引入Mybatis的相关依赖,配置Mybatis(数据库连接信息)
  3. 编写SQL语句(注解/XML)
  4. 测试

2.1.1 创建工程

创建springboot工程,并导入mybatis的起步依赖、mysql的驱动包

项目工程创建完成后,自动在pom.xml文件中,导入Mybatis依赖和MySQL驱动依赖。

版本会随着SpringBoot版本发生变化SpringBoot3.X对用MyBatis版本为3.X

对应关系参考:https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

java 复制代码
 <!--Mybatis 依赖包-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.2</version>
</dependency>

 <!--mysql驱动包-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

2.2 配置数据库连接字符串

MyBatis中需要连接数据库,需要数据库相关参数配置

MySQL驱动类

登录名

密码

数据库连接字符串

如果是application.yml文件,配置内容如下:

java 复制代码
# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 111111
    driver-class-name: com.mysql.cj.jdbc.Driver

【注意】:如果使用MySQL是5.x之前的使用的是"com.mysql.jdbc.Driver",如果是大于5.x使用的是"com.mysql.cj.jdbc.Driver".

如果是application.properties文件,配置内容如下:

java 复制代码
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url 
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncoding=utf8&useSSL=false
#连接数据库的??名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=root

2.3 写持久层代码

java 复制代码
import com.example.mybatis.model.UserInfo;
import org.apache.ibatis.annotations.*;
import org.springframework.beans.factory.support.ScopeNotActiveException;

import java.util.List;

@Mapper
public interface UserInfoMapper {
    @Select("select * from user_info")
    List<UserInfo> selectAll();
}
  1. MyBatis的持久层接口规范一般都叫XxxMapper.
  2. @Mapper注解:表示的是MyBatis中的Mapper接口。

2.1. 程序运行时, 框架会自动生成接口的实现类对象(代理对象),并交给Spring的IOC容器管理。

2.2. @Select注解:代表的就是select查询,也就是注解对应方法的具体实现内容。

2.4 单元测试

在对应的mapper文件中:鼠标右键-->generate--->test

java 复制代码
import com.example.mybatis.model.UserInfo;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;
    @Test
    void selectAll() {
        System.out.println(userInfoMapper.selectAll());
    }
}
  • 测试类上添加@SpringBootTest注解,该测试类在运行时,就会自动加载Spring的运行环境。
  • 通过@Autowired这个注解,注入我们所要测试的mapper类,就可以开始进行测试了。

运行结果:

3. MyBatis的基础操作

3.1 打印日志

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

application.yml文件:

java 复制代码
mybatis:
  configuration: # 配置打印MyBatis⽇志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

重新运行程序,可以看到SQL执行内容,以及传递参数和执行结果:

  1. 查询语句
  2. 传递参数及类型
  3. SQL执行类型

3.2 参数传递

需求:查找id=4的用户,对应的SQL就是:select*fromuser_infowhereid=4

java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where id= 4 ")
UserInfo queryById();

但是这样的话,只能查找id=4的数据,所以SQL语句中的id值不能写成固定数值,需要变为动态的数值.

解决方案:在queryByld方法中添加一个参数(id),将方法中的参数,传给SQL语句.使用#{}的方式获取方法中的参数。

java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where id = #{id} ")
UserInfo queryById();

如果mapper接口方法形参只有一个普通类型的参数,#}里面的属性名可以随便写,如:#{id]、#{value]。建议和参数名保持一致.

添加测试用例

java 复制代码
 @Test
 void queryById() {
     UserInfo userInfo = userInfoMapper.queryById(4);
     System.out.println(userInfo);
 }

运行结果:

也可以通过@Param,设置参数的别名,如果使用@Param设置别名,#.里面的属性名必须和@Param设置的一样.

java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where id = #{userid} ")
UserInfo queryById(@Param("userid") Integer id);

3.3 增(insert)

SQL 语句:

java 复制代码
insert into user_info (username, `password`, age, gender, phone) values ("zhaoliu","zhaoliu",19,1,"18700001234")

把SQL中的常量替换为动态的参数,Mapper接口:

java 复制代码
 @Insert("insert into user_info (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")
 Integer insert(UserInfo userInfo);

测试代码:

java 复制代码
@Test
void insert() {
    UserInfo userInfo = new UserInfo();
    userInfo.setUsername("zhaoliu");
    userInfo.setPassword("zhaoliu");
    userInfo.setGender(2);
    userInfo.setAge(21);
    userInfo.setPhone("18612340005");
    userInfoMapper.insert(userInfo);
}

运行后,观察数据库执行结果

如果设置了@Param属性,#{...}需要使用参数.属性的方式来获取。

java 复制代码
@Insert("insert into user_info (username, `password`, age, gender, phone) values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})")
Integer insert(@Param("userInfo") UserInfo userInfo);

3.3.1 返回主键

Insert语句默认返回的是受影响的行数.但有些情况下,数据插入之后,还需要有后续的关联操作,需要获取到新插入数据的id.

比如订单系统:当我们下完订单之后,需要通知物流系统、库存系统、结算系统等,这时候就需要拿到订单ID。

如果想要拿到自增id,需要在Mapper接口的方法上添加一个@Options的注解。

java 复制代码
 @Options(useGeneratedKeys = true, keyProperty = "id")
 @Insert("insert into user_info (username, age, gender, phone) values (#{userInfo.username},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})")
 Integer insert(@Param("userInfo") UserInfo userInfo);

useGeneratedKeys:这会令MyBats使用JDBC的getGeneratedKeys方法来取出数据库内部生成的主键,默认值:false.

keyProperty:指定能够唯一识别对象的属性,MyBatis会使用getGeneratedKeys的返回值或insert语句的selectKey子元素设置它的值,默认值:未设置(unset)

测试数据:

java 复制代码
 @Test
 void insert() {
     UserInfo userInfo = new UserInfo();
     userInfo.setUsername("zhaoliu");
     userInfo.setPassword("zhaoliu");
     userInfo.setGender(2);
     userInfo.setAge(21);
     userInfo.setPhone("18612340005");
     Integer count = userInfoMapper.insert(userInfo);
     System.out.println("添加数据条数:" +count +", 数据ID:" + userInfo.getId());
 }

运行结果:

注意:设置useGeneratedKeys=true之后,方法返回值依然是受影响的行数,自增id会设置在上述keyProperty指定的属性中.

3.4 删(delete)

SQL语句:

java 复制代码
delete from user_info where id=6

把SQL中的常量替换为动态的参数,Mapper接口:

java 复制代码
 @Delete("delete from user_info where id = #{id}")
 void delete(Integer id);

3.5 改(update)

SQL语句:

java 复制代码
update user_info set username="zhaoliu" where id=5

把SQL中的常量替换为动态的参数,Mapper接口:

java 复制代码
@Update("update user_info set username=#{username} where id=#{id}")
void update(UserInfo userInfo);

3.6 查(select)

我们在上面查询时发现,有几个字段时没有赋值的,只有Java对象属性和数据库字段一模一样时,才会进行赋值。

java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from user_info")
List<UserInfo> queryAllUser();

查询结果:

从运行结果上可以看到,我们SQL语句中,查询delete_flag,create_time,update_time,但是这几个属性却没有赋值。

  1. MyBatis会根据方法返回的结果进行赋值。
  2. 方法用对象UserInfo接收返回结果,MySQL查询出来数据为一条,就会自动赋值给对象。
  3. 方法用List< UserInfo >接收返回结果,MySQL查询出来数据为一条或多条时,也会自动赋值给list,但如果MySQL查询返回多条,但是方法使用UserInfo接收,MyBatis执行就会报错。

原因分析:

当自动映射查询结果时,MyBatis会获取结果中返回的列名并在Java类中查找相同名字的属性(忽略大小写)。这意味着如果发现了ID列和id属性,MyBatis会将列ID的值赋给id属性。

有三种解决方法:

  1. 起别名
  2. 结果映射
  3. 开启驼峰命名

3.6.1 起别名

在SQL语句中,给列名起别名,保持别名和实体类属性名一样。

java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from user_info")
public List<UserInfo> queryAllUser();

【注意】:SQL语句太长时,使用+号进行字符串拼接。

3.6.2 结果映射

java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from user_info")
@Results({
         @Result(column = "delete_flag",property = "deleteFlag"),
         @Result(column = "create_time",property = "createTime"),
         @Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();

如果其他SQL,也希望可以复用这个映射关系,可以给这个Result定义一个名称。

java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, 
create_time, update_time from user_info")
@Results(id = "resultMap",value = {
        @Result(column = "delete_flag",property = "deleteFlag"),
        @Result(column = "create_time",property = "createTime"),
        @Result(column = "update_time",property = "updateTime")
})

List<UserInfo> queryAllUser();

@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from user_info where id= #{userid} ")
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);

使用id属性给该result定义别名,使用@ResulutMap注解来复用其他定义的Result Map

3.6.3 开启驼峰命名(推荐)

通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而Java属性一般遵循驼峰命名法约定。

为了在这两种命名方式之间启用自动映射,需要将mapUnderscoreToCamelCasei设置为true。

java 复制代码
mybatis:
  configuration: 
    map-underscore-to-camel-case: true #配置驼峰⾃动转换

驼峰命名规则:abc_xyz===>abcXyz

  • 表中字段名:abc_xyz
  • 类中属性名:abcXyz

Java代码不做任何处理

java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from user_info")
public List<UserInfo> queryAllUser();

添加上述配置,运行代码:

字段全部进行正确赋值。

4. MyBatis XML配置文件

MyBatis的开发有两种方式:

  1. 注解
  2. XML

MyBatis XML的方式需要以下两步:

  1. 配置数据库连接字符串和MyBatis
  2. 写持久层代码

4.1 配置连接字符串和MyBatis

application.yml文件:

java 复制代码
spring:
  # 数据库连接配置
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 111111
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

4.2 写持久层代码

持久层代码分为两部分:

  1. 方法定义Interface
  2. 方法实现:XXX.xml

4.2.1 添加mapper接口

数据持久层的接口定义:

java 复制代码
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserInfoXMlMapper {
   List<UserInfo> queryAllUser();
}

4.2.2 添加UserInfoXMLMapper.xml

数据持久层的实现,MyBatis的固定xml格式:

java 复制代码
<?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.UserInfoMapper">

</mapper>

创建UserInfoXMLMapper.xml,路径参考yml文件中的配置。

查询所有用户的具体实现:

java 复制代码
 <?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.UserInfoXMlMapper">
     <select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
         select username,`password`, age, gender, phone from user_info
     </select>
 </mapper>
  • <mapper>标签:需要指定namespace属性,表示命名空间,值为mapper接口的全限定名,包括全包名.类名。
  • <select>查询标签:是用来执行数据库的查询操作的。
  • id : 是和Interface(接口)中定义的方法名称一样,表示对接口的具体实现方法。
  • resultType : 是返回的数据类型,也就是开头我们定义的实体类。

4.2.3 单元测试

java 复制代码
@SpringBootTest
class UserInfoMapperTest {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Test
    void queryAllUser() {
        List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
        System.out.println(userInfoList);
    }
}

运行结果如下:

4.3 增删改查操作

4.3.1增(lnsert)

UserInfoMapper接口:

java 复制代码
 Integer insertUser(UserInfo userInfo);

UserInfoMapper.xml实现:

java 复制代码
 <insert id="insertUser">
   insert into userinfo (username, `password`, age, gender, phone) values (#{username}, #{password}, #{age},#{gender},#{phone})
 </insert>

如果使用@Param设置参数名称的话,使用方法和注解类似:

UserInfoMapper接口:

java 复制代码
Integer insertUser(@Param("userInfo") UserInfo userInfo);

UserInfoMapper.xml实现:

java 复制代码
<insert id="insertUser">
    insert into user_info (username, `password`, age, gender, phone) values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})
</insert>

返回自增id

接口定义不变,Mapper.xml实现设置usegeneratedKeys 和 keyProperty属性:

java 复制代码
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into user_info (username, `password`, age, gender, phone) values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})
</insert>

4.3.2删(Delete)

UserInfoMapper接口:

java 复制代码
Integer deleteUser(Integer id);

UserInfoMapper.xml实现:

java 复制代码
<delete id="deleteUser">
    delete from user_info where id = #{id}
</delete>

4.3.3改(Update)

UserInfoMapper接口:

java 复制代码
Integer updateUser(UserInfo userInfo);

UserInfoMapper.xml实现:

java 复制代码
<update id="updateUser">
    update user_info set username=#{username} where id=#{id}
</update>

4.3.4查(Select)

同样的,使用XML的方式进行查询,也存在数据封装的问题我们把SQL语句进行简单修改,查询更多的字段内容。

java 复制代码
<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
    select id, username,`password`, age, gender, phone, delete_flag, create_time, update_time from user_info
</select>

运行结果:

结果显示:deleteFlag,createTime,updateTime也没有进⾏赋值。

解决办法和注解类似:

  1. 起别名
  2. 结果映射
  3. 开启驼峰命名

xml使用结果映射:

Mapper.xml

java 复制代码
<resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
    <id column="id" property="id"></id>
    <result column="delete_flag" property="deleteFlag"></result>
    <result column="create_time" property="createTime"></result>
    <result column="update_time" property="updateTime"></result>
</resultMap>

<select id="queryAllUser" resultMap="BaseMap">
    select id, username,`password`, age, gender, phone, delete_flag, create_time, update_time from user_info
</select>

开发中使用注解还是XML的方式?

关于开发中使用哪种模式这个问题,没有明确答案.仁者见仁智者见智,并没有统一的标准,更多是取决于你的团队或者项目经理,项目负责人.

5.其他查询操作

5.1多表查询

5.1.1 数据查询

需求:根据uid查询作者的名称等相关信息.

SQL:

java 复制代码
SELECT
      ta.id,
      ta.title,
      ta.content,
      ta.uid,
      tb.username,
      tb.age,
      tb.gender 
FROM
      articleinfo ta
      LEFT JOIN user_info tb ON ta.uid = tb.id 
WHERE
      ta.id =1

接口定义:

java 复制代码
import com.example.demo.model.ArticleInfo;
import org.apache.ibatis.annotations.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 user_info tb ON ta.uid = tb.id WHERE ta.id = #{id}")
    ArticleInfo queryUserByUid(Integer id);
}

如果名称不一致的,采用ResultMap,或者契苾民的方式解决,和单表查询一样。

MyBatis不分单表还是多表,主要就是三部分:SQL、映射关系和实体类。

通过映射关系,把SQL运行结果和实体类关联起来。

5.2 #{}和${}

MyBaits参数赋值有两种方式:#{} 和 ${}。

5.2.1 #{}和${}使用

5.2.1.1 Interger类型的参数
java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where id= #{id} ")
UserInfo queryById(Integer id);

观察打印的日志:

控制台上输出的sql语句是

java 复制代码
 select username, `password`, age, gender, phone from user_info where id= ?

我们输入的参数并没有在后面拼接,id的值是使用进行占位,这种SQL我们称之为"预编译SQL"。

把#{} 换成 ${}:

java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where id= ${id} ")
UserInfo queryById(Integer id);

使用${},参数是直接拼接在SQL语句中了。

5.2.1.1 String类型的参数
java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where username= #{name} ")
UserInfo queryByName(String name);

把#{} 换成${} :

java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where username= ${name} ")
UserInfo queryByName(String name);

可以看到,这次的参数依然是直接拼接在SQL语句中了,但是字符串作为参数时,需要添加引号'',使用${} 不会拼接引号'',导致程序报错。

修改代码:

java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where username= '${name}' ")
UserInfo queryByName(String name);

【总结】:

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

参数为数字类型时,也可以加上,查询结果不变,但是可能会导致索引失效,性能下降。

5.2.2 #{}和${}区别

#{} 和 ${} 的区别就是预编译SQL和即时SQL的区别
1. 性能更高

绝大多数情况下,某一条SQL语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如select的where子句值不同,update的set子句值不同,insert的values值不同).如果每次都需要经过上面的语法解析,SQL优化、SQL编译等,则效率就明显不行了.

预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输入的参数不同),省去了解析优化等过程,以此来提高效率。

2. 更安全(防止SQL注入)

SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。

由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。

sql注入代码:' or 1='1

java 复制代码
@Select("select username, `password`, age, gender, phone from user_info where username= '${name}' ")
List<UserInfo> queryByName(String name);

测试代码:
正常情况:

java 复制代码
@Test
void queryByName() {
    List<UserInfo> userInfos = userInfoMapper.queryByName("admin");
    System.out.println(userInfos);
}

运行结果:

2. SQL注入场景

java 复制代码
@Test
void queryByName() {
    List<UserInfo> userInfos = userInfoMapper.queryByName("' or 1='1");
    System.out.println(userInfos);
}

运行结果:

结果依然被查询出来了,其中参数or被当做了SQL语句的一部分。

但是,查询的数据并不是自己想要的数据,所以用于查询的字段,尽量使用#{}预查询的方式。

SQL注入是一种非常常见的数据库攻击手段,SQL注入漏洞也是网络世界中最普遍的漏洞之一。

如果发生在用户登录的场景中,密码输入为' or1='1,就可能完成登录(不是一定会发生的场景,需要看登录代码如何写)

5.3 排序功能

${}会有SQL注入的风险,所以我们尽量使用#{}完成查询。

既然如此,是不是$0就没有存在的必要性了呢?

当然不是!!!

java 复制代码
 @Select("select id, username, age, gender, phone, delete_flag, create_time, update_time from user_info order by id ${sort} ")
 List<UserInfo> queryAllUserBySort(String sort);

使用${sort} 可以实现排序查询,而使用#{sort} 就不能实现排序查询了.

注意:此处sort参数为String类型,但是SQL语句中,排序规则是不需要加引号''的,所以此时的${sort}也不加引号。

${} 改成 #{}

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time from user_info order by id #{sort} ")
List<UserInfo> queryAllUserBySort(String sort);

运行结果:

可以发现,当使用#{sort} 查询时,asc前后⾃动给加了引号,导致sql错误。

#{} 会根据参数类型判断是否拼接引号''

如果参数类型为String,就会加上引号。

除此之外,还有表名作为参数时,就会加上引号。

5.4 like查询

like 使⽤#{}报错

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time from user_info where username like '%#{key}%' ")
List<UserInfo> queryAllUserByLike(String key);

把#{}改成 {}可以正确查出来,但是 {}存在SQL注⼊的问题,所以不能直接使⽤${}.

解决办法:使⽤mysql的内置函数concat()来处理,实现代码如下:

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time from user_info where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);

6. 数据库连接池

上面的MyBatis使用了数据库连接池技术,避免频繁的创建连接和销毁连接。

6.1 介绍

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。

  1. 没有使用数据库连接池的情况: 每次执行SQL语句,要先创建一个新的连接对象,然后执行SQL语句,SQL语句执行完,再关闭连接对象释放资源,,这种重复的创建连接,销毁连接比较消耗资源。
  2. **使用数据库连接池的情况:**程序启动时,会在数据库连接池中创建一定数量的Connection对象,当客户请求数据库连接池,会从数据库中获取Connection对象,然后执行SQL,SQL语句执行完,再把Connection归还给连接池。

优点:

  1. 减少了网络开销
  2. 资源重用
  3. 提升了系统的性能。

6.2 使用

常见的数据库连接池:

1.C3P0

2.DBCP

3.Druid

4.Hikari

目前比较流行的是:Hikari、Druid

  1. Hikari:SpringBoot默认使用的数据库连接池
  2. Druid
    如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引入相关依赖即可.
java 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-3-starter</artifactId>
    <version>1.2.21</version>
</dependency>

如果SpringBoot版本为2.X,使用druid-spring-boot-starter依赖

java 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>

运行结果:

7.总结

7.1 MySQL开发企业规范

  1. 表名,字段名使用小写字母或数字,单词之间以下划线分割.尽量避免出现数字开头或者两个下划线中间只出现数字.数据库字段名的修改代价很大,所以字段名称需要慎重考虑。

MySQL在Windows下不区分大小写,但在Linux下默认是区分大小写.因此,数据库名,表名,字段名都不允许出现任何大写字母,避免节外生枝.

正例:aliyun_admin,rdc_config,level3_name

反例:AliyunAdmin,rdcConfig,level_3_name

2.表必备三字段:id,create_time,update_time

id必为主键,类型为bigintunsigned,单表时自增,步长为1

reate_time,update_time的类型均为datetime类型,create_time表示创建时间,update_time表示更新时间

有同等含义的字段即可,字段名不做强制要求

3.在表查询中,避免使用*作为查询的字段列表,标明需要哪些字段.

  1. 增加查询分析器解析成本
  2. 增减字段容易与resultMap配置不一致
  3. 无用字段增加网络消耗,尤其是text类型的字段

7.2 #{} 和${} 区别

  1. #{}:预编译处理, ${}:字符直接替换
  2. #{} 可以防止SQL注⼊,${}存在SQL注入的风险,查询语句中,可以使⽤#{},推荐使用#{}
  3. 但是⼀些场景,#{}不能完成,比如排序功能,表名,字段名作为参数时,这些情况需要使⽤${}
  4. 模糊查询虽然${}可以完成,但因为存在SQL注入的问题,所以通常使用mysql内置函数concat来完成
相关推荐
广龙宇5 分钟前
【一起学Rust】使用Thunk工具链实现Rust应用对Windows XP/7的兼容性适配实战
开发语言·windows·rust
jerry20110817 分钟前
R语言之rjava版本不匹配解决方法
开发语言·r语言
拓端研究室TRL20 分钟前
PYTHON用几何布朗运动模型和蒙特卡罗MONTE CARLO随机过程模拟股票价格可视化分析耐克NKE股价时间序列数据
开发语言·python
独立开阀者_FwtCoder21 分钟前
狂收 33k+ star!全网精选的 MCP 一网打尽!!
java·前端·javascript
再路上121623 分钟前
direct_visual_lidar_calibration iridescence库问题
java·服务器·数据库
无畏烧风38 分钟前
[Qt]双击事件导致的问题
开发语言·qt
CheungChunChiu44 分钟前
Qt 容器类使用指南
linux·开发语言·c++·qt·容器
小王努力学编程1 小时前
美团2024年春招第一场笔试 C++
开发语言·数据结构·c++·学习·算法
攻城狮7号1 小时前
Python爬虫第18节-动态渲染页面抓取之Splash使用上篇
开发语言·人工智能·爬虫·python·python爬虫
卡皮巴拉爱吃小蛋糕1 小时前
MySQL的事务(Transaction)【学习笔记】
数据库·笔记·学习·mysql