代码不停头像
MyBatis操作数据库封面图

MyBatis操作数据库


前言
web一弄程序一般分为三层,Controller,Service,Dao
浏览器发送请求Controller,接收请求之后调用Service业务逻辑层,再调用Dao数据层,会数据需要从数据库中进行读取
使用JDBC操作数据库比较繁琐,因此这里就有了MyBatis可以更简单和方便操作数据库

MyBatis快速入门

什么是MyBatis

是一个数据持久层框架,简化JDBC开发,起源是iBatis

在这里插入图片描述
MyBatis中文网:https://mybatis.net.cn/

使用Spring Boot导入东西

Developer Tools中 Lombok,要使用@Data注解
Web 中 Spring Web ,web项目
SQL 中 MySQL Driver 、MyBatis Framework数据库相关框架和驱动

在这里插入图片描述
pom.xml文件中有对应依赖
Spring Boot3.x版本,其MyBatis也是3.X版本
在这里插入图片描述
https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

上面选取这些pom文件中有这些依赖

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
        <!--MyBatis框架-->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>3.0.5</version>
		</dependency>
		<!--MySQL驱动-->
		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!--lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
        <!--测试相关-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter-test</artifactId>
			<version>3.0.5</version>
			<scope>test</scope>
</dependency>

数据库数据准备

MySQL数据准备

-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;

CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
USE mybatis_test;

-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
        `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
        `username` VARCHAR ( 127 ) NOT NULL,
        `password` VARCHAR ( 127 ) NOT NULL,
        `age` TINYINT ( 4 ) NOT NULL,
        `gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认',
        `phone` VARCHAR ( 15 ) DEFAULT NULL,
        `delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
        `create_time` DATETIME DEFAULT now(),
        `update_time` DATETIME DEFAULT now() ON UPDATE now(),
        PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; 

-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 17, 0, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );

这里需要一个实体类来作为接收数据库中每行的信息
这里名称要一一对应

UserInfo

@Data
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;

    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

这里需要创建一个持久层接口编写数据库对应操作UserInfoMapper

@Mapper
public interface UserInfoMapper {

    //搜索所有
    @Select("select * from user_info")
    List<UserInfo> selectAll();

    @Select("select * from user_info where id = #{id}")
    UserInfo selectById(Integer id);

    @Select("select * from user_info where gender = #{gender} and age = #{age}")
    UserInfo selectByAgeAndGender(Integer age,Integer gender);
}

@Mapper注解:表示MyBatis中的Mapper接口
程序运行时,框架会自动生成接口实现类对象,交给Spring 的IOC容器进行管理
使用@Select注解即可,里面放数据库相关语句,但这里一些参数需要传递这里需要 #{id}放要传入的参数
这里名称要对应上,顺序可以不一致
在这里插入图片描述

在这里插入图片描述

@Select("select * from user_info where gender = #{gender} and age = #{a}")
UserInfo selectByAgeAndGender(@Param("a") Integer age, Integer gender);

可以使用@Param注解取bi

这里通过测试代码进行测试

这里可以直接右键 Generate -> test -> 勾选对应这个类要进行测试的方法
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
就会生成相关测试代码
在这里插入图片描述
测试相关代码
@SpringBootTest,该测试类在运行时,自动加载Spring 运行,这里通过@Autowired注解进行初始化

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @BeforeEach
    void setUp() {
    }

    @AfterEach
    void tearDown() {
    }

    @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoMapper.selectAll();
        for (UserInfo userInfo : userInfos){
            System.out.println(userInfo);
        }
        //userInfos.forEach(System.out::println);
    }

    @Test
    void selectById() {
        UserInfo userInfo = userInfoMapper.selectById(4);
        System.out.println(userInfo);
    }

    @Test
    void selectByAgeAndGender() {
        UserInfo userInfo = userInfoMapper.selectByAgeAndGender(17,0);
        System.out.println(userInfo);
    }
}

如果没有使用@Autowired注入对象在这里插入图片描述

这里测试代码是可以直接执行的,但是这里执行就会出现下面这样的错误

Error creating bean with name ‘dataSource’ defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method ‘dataSource’ threw exception with message: Failed to determine a suitable driver class
这是因为 Spring Boot 无法自动检测到合适的 JDBC 驱动程序,上面我们并没有配置数据库相关信息,无法连接到数据库

数据库相关配置

这里使用的是MySQL8.0

在这里插入图片描述
这里使用yml文件为例进行配置
数据库相关连接配置

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

这里username和password就是对应数据库名称和密码,这里是本机的数据库
如果这里密码是纯数字的话,需要加上双引号,否则会和上面一样,爆出未连接数据库的问题
这样就可以执行测试代码了
在这里插入图片描述

在这里插入图片描述
测试的日志也是需要打印
配置yaml文件

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

数据库相关日志
1.查询语句
2.传入参数及类型
3.SQL执行结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

MyBatis的基础操作

主要是增、删、查、改等操作

Insert(增)

还是在上面这个UserInfoMapper接口下
SQL语句

insert into user_info (username,`password`,age,gender) VALUES("java1","java1",1,0);

UserInfoMapper

直接使⽤UserInfo对象的属性名来获取参数

以UserInfo对象作为参数,可以直接使用变量名进行赋值

@Insert("insert into user_info (username,`password`,age,gender) " +
         "values(#{username},#{password},#{age},#{gender})")
Integer insertUser(UserInfo userInfo);

测试代码

@Slf4j
@SpringBootTest
class UserInfoMapperTest {
    @Test
    void insertUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java1");
        userInfo.setPassword("java1");
        userInfo.setAge(1);
        userInfo.setGender(1);
        Integer result = userInfoMapper.insertUser(userInfo);
        //日志打印影响行数
        log.info("影响行数:{}",result);
    }
}

结果
在这里插入图片描述
执行SQL通常会返回这次操作影响的行数,这里也进行了接收
在这里插入图片描述

如果使用@Param注解取别名后,这里需要#{…}需要使⽤参数.属性来获取

@Insert("insert into user_info (username,`password`,age,gender) " +
         "values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})")
Integer insertUser2(@Param("userInfo") UserInfo userInfo);
  @Test
    void insertUser2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java2");
        userInfo.setPassword("java2");
        userInfo.setAge(1);
        userInfo.setGender(1);
        Integer result = userInfoMapper.insertUser2(userInfo);
        log.info("影响行数:{}",result);
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Insert语句默认返回的是受影响的⾏数
这里当然也可以获取到其新插入数据行的id

获取新插入数据行的id

@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into user_info (username,`password`,age,gender) " +
            "values(#{username},#{password},#{age},#{gender})")
Integer insertUser(UserInfo userInfo);

useGeneratedKeys:使MyBatis用JDBC的getGeneratedKeys方法取出数据库内的主键,像MySQL中这样关系型数据库的自增主键,默认值:false
keyProperty :指定唯一识别属性,通常使用getGeneratedKeys返回值或者insert语句的selectKey⼦元素设置它的值

这里测试代码将其自增主键也打印出来

    @Test
    void insertUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java1");
        userInfo.setPassword("java1");
        userInfo.setAge(1);
        userInfo.setGender(1);
        Integer result = userInfoMapper.insertUser(userInfo);
        log.info("影响行数:{},自增主键:{}",result,userInfo.getId());
    }

在这里插入图片描述

Delete(删)

执行前
在这里插入图片描述

delete from `user_info` where id  = 9;
//通过id进行删除
@Delete("delete from `user_info` where id = #{id}")
Integer deleteUserById(Integer id);
 @Test
    void deleteUserById() {
        Integer result = userInfoMapper.deleteUserById(9);
        log.info("影响行数:{}",result);
    }

在这里插入图片描述
在这里插入图片描述
删除后
在这里插入图片描述

Update(改)

将id为8的gender修改为0

update user_info set gender = 0 where id = 8;
@Update("update user_info set gender = #{gender} where id = #{id}")
Integer updateUserById(Integer gender,Integer id);
   @Test
    void updateUserById() {
        Integer result = userInfoMapper.updateUserById(0,8);
        log.info("影响行数:{}",result);
    }

在这里插入图片描述

在这里插入图片描述
成功修改
在这里插入图片描述

Select(查)

 //搜索所有
@Select("select * from user_info")
List<UserInfo> selectAll();
 @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoMapper.selectAll();
        for (UserInfo userInfo : userInfos){
            System.out.println(userInfo);
        }
        //userInfos.forEach(System.out::println);
    }

在这里插入图片描述
数据库中,delete_flag、create_time、 update_time都不是空,但是这里接收后这里三行都是null
在这里插入图片描述
这三个属性没有正确赋值
因为当自动映射查询结果时,MyBatis会获取结果中返回列名与Java中相同名称进行赋值,忽略大小写,例如:ID / id 都可以被MyBatis数据库中id进行赋值
但是这里最后三个属性名称和数据库中因为Java和MySQL命名规则不同,导致这里定义的时候名称不同,没能正确赋值
在这里插入图片描述
这里有三种解决方法

1.起别名

select id, username, password, age, gender, phone, 
       delete_flag as deleteFlag, 
	   create_time as createTime, 
	   update_time as updateTime from user_info;
//搜索所有
@Select("select id, username, password, age, gender, phone," +
        "delete_flag as deleteFlag, " +
        "create_time as createTime, " +
        "update_time as updateTime from user_info;")
List<UserInfo> selectAll();

在这里插入图片描述

2.结果映射

@Results({
    @Result(column = "delete_flag",property = "deleteFlag"),
    @Result(column = "create_time",property = "createTime"),
    @Result(column = "update_time",property = "updateTime"),
})
@Select("select id, username, password, age, gender, phone, delete_flag," +
        " create_time, update_time from user_info")
List<UserInfo> selectAll();

在这里插入图片描述
但是这样后面如果还需要写这个结果映射,因此可以使⽤ id 属性给该 Results 定义别名,@ResultMap 注解来复⽤其他定义的 ResultMap
这样就起到了复用的作用
在这里插入图片描述

在这里插入图片描述

3.配置驼峰自动转换

mybatis: 
  configuration:
    map-underscore-to-camel-case: true #配置驼峰⾃动转换 

所有的yaml文件

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


mybatis:
  configuration: # 配置打印 MyBatis⽇志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true #配置驼峰⾃动转换 

例如:abc_dd 变成为 abcDd
这里无需做任何处理

@Select("select id, username, password, age, gender, phone, delete_flag," +
        " create_time, update_time from user_info")
List<UserInfo> selectAll();

在这里插入图片描述

MyBatis XML配置⽂件

使用MyBatis其实有两种方式,注解和XML文件形式,上面是通过注解的方式,这里使用XML方式操作数据库
在这里插入图片描述

这里需要多一个这个xml映射位置配置

mybatis:
  mapper-locations: classpath:mybatis/**Mapper.xml
# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: "060610"
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:mybatis/**Mapper.xml
  configuration: # 配置打印 MyBatis⽇志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true #配置驼峰⾃动转换

在这里插入图片描述

mapper接口

UserInfoXMLMapper

@Mapper
public interface UserInfoXMLMapper {
    List<UserInfo> selectAll();
}

UserInfoMapper.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="com.mybatis.demo.mapper.UserInfoXMLMapper">

</mapper>

这里的classpath也就是此时resources下的mybatis目录下,以Mapper.XML结尾的文件

实现查询所有的功能,也就是接口UserInfoXMLMapper 下的selectAll

<?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.mybatis.demo.mapper.UserInfoXMLMapper">

    <select id="selectAll" resultType="com.mybatis.demo.entity.UserInfo">
        select * from user_info
    </select>
</mapper>

mapper标签下:namespace属性,是命名空间,全限定接口,全包名.类名,也就是此时UserInfoXMLMapper所在路径
select查询标签:用户查询操作
id:是Interface接口下的方法名称,对应接口方法的实现
resultType:返回的数据类型
在这里插入图片描述
测试代码

@SpringBootTest
class UserInfoXMLMapperTest {
    @Autowired
    private UserInfoXMLMapper userInfoXMLMapper;

    @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoXMLMapper.selectAll();
        userInfos.forEach(System.out::println);
    }
}

在这里插入图片描述

Insert(增)

UserInfoXMLMapper接口

Integer insertUser(UserInfo userInfo);

UserInfoMapper.xml实现

<insert id="insertUser">
        insert into user_info (username,`password`,age,gender)
        values(#{username},#{password},#{age},#{gender})
</insert>
    @Test
    void insertUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java12");
        userInfo.setPassword("java12");
        userInfo.setAge(1);
        userInfo.setGender(1);
        Integer result = userInfoXMLMapper.insertUser(userInfo);
        log.info("影响行数:{},自增主键:{}",result,userInfo.getId());
    }

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

@Param设置参数名称的话,使⽤⽅法和注解类似

就是和上面查询语句不一样
参数.属性名
UserInfoXMLMapper接口

Integer insertUser2(@Param("userInfo") UserInfo userInfo);

UserInfoXMLMapper.xml

<insert id="insertUser2">
        insert into user_info (username,`password`,age,gender)
        values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})
</insert>
@Test
    void insertUser2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java22");
        userInfo.setPassword("java22");
        userInfo.setAge(2);
        userInfo.setGender(0);
        Integer result = userInfoXMLMapper.insertUser2(userInfo);
        log.info("影响行数:{},自增主键:{}",result,userInfo.getId());
    }

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

在这里插入图片描述
但是发现这里获取自增主键,但是没有获取到,这里需要设置useGeneratedKeys和keyProperty属性
UserInfoXMLMapper.xml

 <insert id="insertUser2" useGeneratedKeys="true" keyProperty="id">
        insert into user_info (username,`password`,age,gender)
        values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})
 </insert>

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

Delete(删)

UserInfoMapper接⼝:

Integer deleteUserById(Integer id);

UserInfoMapper.xml实现:

<delete id="deleteUserById">
    delete from user_info where id = #{id};
</delete>

根据id进行删除,删除id = 12

    @Test
    void deleteUserById() {
        userInfoXMLMapper.deleteUserById(12);
    }

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

Update(改)

UserInfoMapper接⼝:

Integer updateUserById(Integer gender,Integer age);

UserInfoMapper.xml实现:

 <update id="updateUserById">
        update user_info set age = 20 where age = #{age} and gender = #{gender}
 </update>

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

Select(查)

UserInfoMapper接⼝:

List<UserInfo> selectAll();

UserInfoMapper.xml实现:

<select id="selectAll" resultType="com.mybatis.demo.entity.UserInfo">
        select id, username, password, age, gender, phone,
        delete_flag, create_time, update_time from user_info
</select>
    @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoXMLMapper.selectAll();
        userInfos.forEach(System.out::println);
    }

这里delete_flag, create_time, update_time为空,因为表中名称和这里名称不一样,没有赋值
在这里插入图片描述

在这里插入图片描述
这里和上面一样,依旧有三种方法解决
1.起别名
2.结果映射
3.开启驼峰命名

1.起别名

<select id="selectAll" resultType="com.mybatis.demo.entity.UserInfo">
        select id, username, password, age, gender, phone,
        delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from user_info
</select>

在这里插入图片描述

2.结果映射

  <resultMap id="BaseMap" type="com.mybatis.demo.entity.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="selectAll" resultMap="BaseMap">
        select id, username, password, age, gender, phone,
        delete_flag, create_time, update_time from user_info
    </select>

在这里插入图片描述

3.开启驼峰命名

mybatis:
  mapper-locations: classpath:mybatis/**Mapper.xml
  configuration: # 配置打印 MyBatis⽇志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true #配置驼峰⾃动转换

UserInfoMapper.xml实现:

    <select id="selectAll" resultType="com.mybatis.demo.entity.UserInfo">
        select id, username, password, age, gender, phone,
        delete_flag, create_time, update_time from user_info
    </select>

这里有了配置驼峰⾃动转换,这里可以正常赋值

其他查询操作

多表查询

结合上面的user_info表和这个article_info 表

-- 创建文章表
DROP TABLE IF EXISTS article_info;
        
CREATE TABLE article_info (
        id INT PRIMARY KEY auto_increment,
        title VARCHAR ( 100 ) NOT NULL,
        content TEXT NOT NULL,
        uid INT NOT NULL,
        delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
        create_time DATETIME DEFAULT now(),
        update_time DATETIME DEFAULT now() 
) DEFAULT charset 'utf8mb4';

-- 插入测试数据
INSERT INTO article_info ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1 );

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

这里article_info 的 uid 和 user_info中id列进行匹配,结合这两个表查找

select 
 ta.*,tb.username,tb.password
from 
  article_info ta LEFT JOIN user_info tb ON ta.uid = tb.id 
WHERE ta.id = 1;  

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

public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private Integer uid;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;

    //userInfo
    private String username;
    private String password;
}

ArticleInfoMapper映射类

@Mapper
public interface ArticleInfoMapper {

    @Select("select ta.*,tb.username,tb.password " +
            "from article_info ta LEFT JOIN user_info tb ON ta.uid = tb.id " +
            "WHERE ta.id = #{id}")
    List<ArticleInfo> selectAll(Integer id);
}

test

@SpringBootTest
class ArticleInfoMapperTest {

    @Autowired
    private ArticleInfoMapper articleInfoMapper;
    @Test
    void selectAll() {
        articleInfoMapper.selectAll(1).forEach(System.out::println);
    }
}

在这里插入图片描述

#{} 和 ${}

根据id进行查找,#{ }

@Select("select * from user_info where id = #{id}")
UserInfo selectById(Integer id);

在这里插入图片描述
使用${ }

@Select("select * from user_info where id = ${id}")
UserInfo selectById(Integer id);

在这里插入图片描述
这里使用名字进行查找
#{ }
UserInfoMapper

@Select("select * from user_info where username = #{userName}")
UserInfo selectByName(String userName);
 @Test
    void selectByName() {
        System.out.println(userInfoMapper.selectByName("java22"));
    }

在这里插入图片描述
${ }

@Select("select * from user_info where username = ${userName}")
UserInfo selectByName(String userName);

这里换成这个就会出错
其本质上执行的是

select * from user_info where username = java22

在这里插入图片描述
这肯定会出错,因为这里的username是字符串类型,这里java22没有加上引号
在这里插入图片描述
这里给username加上引号即可

@Select("select * from user_info where username = '${userName}'")
UserInfo selectByName(String userName);

在这里插入图片描述
在这里插入图片描述
通过对比可以发现,此时这里#{}使用的是预编译的SQL,?占位符,这里把参数填充到SQL语句中,也就是替换这里的?,这里会根据类型自动拼接上’ '(字符串类型会)
${}直接进行字符替换,一起对SQL进行编译,并不会自动加上‘ ’,需要手动添加

#{}和${}区别

本质上是预编译SQL和即时SQL的区别
大致流程
1.解析语法和语义,检测SQL语法是否正确
2.优化SQL语句,制定执行计划
3.执行SQL并返回结果

1.性能方面

因为这里绝大多数情况下某一条SQL可能会被反复执行或者只是对应参数不同,如果每次都进行解析,优化SQL这样执行效率太低了
因此这里的预编译SQL,会将其SQL缓存起来,后续执行只是传入参数不同,省去了解析和优化SQL过程,提高了效率
在这里插入图片描述
因此这里的#{}相对而言比${}效率高

安全性
SQL注入问题

SQL注入:需要传参,但是用户非法输入,输入有修改、删除或更新等对数据危险的操作来攻击数据库

正常

@Select("select * from user_info where username = '${userName}'")
List<UserInfo> selectByName(String userName);
@Test
void selectByName() {
    userInfoMapper.selectByName("admin").forEach(System.out::println);
}

这样是正常可以进行查找
在这里插入图片描述
但是这里如果是SQL注入
’ or 1='1

@Test
void selectByName() {
    userInfoMapper.selectByName("' or 1='1").forEach(System.out::println);
}

这里执行的SQL就会是这样,查询所有,明显不是我们要的结果,并且SQL还执行成功了

select * from user_info where username = '' or 1='1'

在这里插入图片描述
因此这里使用#{}

@Select("select * from user_info where username = #{userName}")
List<UserInfo> selectByName(String userName);

这里因为#{}如果是String类型,会自动添加上引号,这里找username = ’ or 1=‘1
然而${}需要手动添加’ ',也就导致这里username 变成 ‘ ’ or 1=‘1’

如果这里是登录场景,密码是’ or 1='1

@Select("select * from user_info where username = '${userName}' and password = '${password}'")
List<UserInfo> selectByNameAndPassword(String userName,String password);

UserInfoController

@RestController
@RequestMapping("/user")
public class UserInfoController {
    @Autowired
    public UserService userService;

    @RequestMapping("/login")
    public Boolean login(String userName,String password){
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            return false;
        }
        List<UserInfo> userInfos = userService.LoginByNameAndPassword(userName,password);
        if(userInfos.size() > 0){
            return true;
        }
        return false;
    }
}

UserService

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    public List<UserInfo> LoginByNameAndPassword(String userName,String password){
        return userInfoMapper.selectByNameAndPassword(userName,password);

    }
}

正常登录
在这里插入图片描述
这里password = ’ or 1='1
在这里插入图片描述
这里使用#{},输入这个没有SQL注入问题
在这里插入图片描述

排序功能

因为${}存在SQL注入的风险,因此这里能使用#{}尽量使用这个,
但是有些场景需要使用不了#{},就像这里的排序功能

//升序
select * from user_info order by id asc;
//降序
select * from user_info order by id desc;
@Select("select id, username, password, age, gender, phone, delete_flag," +
        "create_time, update_time from user_info order by id #{sort}")
List<UserInfo> SelectBySort(String sort);
@Test
  void selectBySort() {
    userInfoMapper.SelectBySort("asc");
}

这里使用#{}出现了SQL语法错误
在这里插入图片描述
在这里插入图片描述
因为这里asc是字符串,因此这里会自动加上引号,导致这里SQL出错
因此这里需要使用${},这里不加引号即可

@Select("select id, username, password, age, gender, phone, delete_flag," +
        "create_time, update_time from user_info order by id ${sort}")
List<UserInfo> SelectBySort(String sort);

在这里插入图片描述
但是使用${}又会引发SQL注入问题,但是此时可以了避免,用户进行参数传递的时候,因为这里排序只可能哟 asc和desc这两个参数,因此用户传入参数时候,可以进行参数校验,如果是这两个参数就进行后续操作,如果不是就停止

like模糊查询

例如这里查询姓名中有 java的
在这里插入图片描述

select * from user_info where username like '%java%';

在这里插入图片描述

@Select("select id, username, password, age, gender, phone, delete_flag," +
            "create_time, update_time from user_info username like %#{likeUserName}%")
List<UserInfo> selectUserByLikeUserName(String likeUserName);
@Test
 void selectUserByLikeUserName() {
   userInfoMapper.selectUserByLikeUserName("java").forEach(System.out::println);
}

使用#{}肯定会出错
因为这里查询的时候 是 %‘java’%多了一个引号,但是使用${}又会有SQL注入问题
这里可以使用concat字符串拼接

select * from user_info where username like concat('%',"java",'%');
@Select("select id, username, password, age, gender, phone, delete_flag," +
            "create_time, update_time from user_info where username like CONCAT('%',#{likeUserName},'%')")
List<UserInfo> selectUserByLikeUserName(String likeUserName);

在这里插入图片描述

区别
1.#{}预编译处理,KaTeX parse error: Expected 'EOF', got '#' at position 13: {}是字符直接替换 2.#̲{}可以防止SQL注入,{}存在SQL注入的风险,能使用#{},推荐使用这个
3.一些场景,#{}不能完成,像上面排序、表名、字段名作为参数时候,需要使用${},但要采取一定措施方式SQL注入

数据库连接池

上面的Mybatis其实使用了数据库连接池技术,避免频繁的创建连接,销毁连接
在这里插入图片描述
没有数据库连接池:这里每次执行SQL语句,都要创建一个新的对象,然后执行SQL语句,SQL语句执行完之后,关闭连接对象,释放资源,这里重复创建和销毁连接比较消耗资源
有连接池:程序启动之后,会先创建一定的数量Connection对象,当客户端发送请求后,会从连接池中获取Connection对象建立连接,执行SQL,执行之后,会把Connection归还给连接池
有了连接池,这里就可以减少网络开销、Connection对象等资源重用、提高系统的性能

常见的数据库连接池有C3P0、DBCP、Druid、 Hikari
Druid、 Hikari这两个较为常用
Hikari是Spring Boot默认使用数据库连接池
在这里插入图片描述
Hikari是⽇语"光"的意思(ひかり),Hikari也是以追求性能极致为⽬标

这里将连接池换成Druid,只需要导入对应依赖即可
Spring Boot 3X版本使用这个

<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid-spring-boot-3-starter</artifactId>
 <version>1.2.21</version>
</dependency>

如果Spring Boot是2X版本,使用这个

<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid-spring-boot-starter</artifactId>
 <version>1.1.17</version>
</dependency>

在这里插入图片描述
Druid连接池是阿⾥巴巴开源的数据库连接池项⽬,也是一个比较优秀的连接池之一
https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2401_89019969/article/details/156315632

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--