【JavaEE】MyBatis 实战指南:从 JDBC 到高效数据库操作的进阶教程

news2024/11/16 0:04:59

目录

    • MyBatis 操作数据库
      • JDBC 操作⽰例回顾
      • 什么是MyBatis?
      • MyBatis⼊⻔
        • 1. 准备⼯作
        • 2. 配置数据库连接字符串
        • 3. 写持久层代码
        • 4. 单元测试
        • 使用MyBatis可能遇到的问题
      • MyBatis的基础操作
        • 打印⽇志
        • 参数传递
        • 增(Insert)
          • 返回主键
        • 删(Delete)
        • 改(Update)
        • 查(Select)
          • 起别名
          • 结果映射
          • 开启驼峰命名(推荐)
      • MyBatis XML配置⽂件
        • 配置连接字符串和MyBatis
        • 写持久层代码
          • 添加 mapper 接⼝
          • 添加 UserInfoXMLMapper.xml
        • 增删改查操作
          • 增(Insert)
            • 返回主键
          • 删(Delete)
          • 改(Update)
          • 查(Select)
        • 其他查询操作
          • 多表查询
            • 准备工作
            • 数据查询
          • \#{} 和 ${}
            • \#{} 和 ${} 使⽤
            • \#{} 和 ${} 区别
          • 排序功能
          • like 查询
        • 数据库连接池
          • 介绍
          • 使⽤
      • 总结
        • MySQL 开发企业规范
        • \#{} 和${} 区别


MyBatis 操作数据库

目标:

  1. 使⽤MyBatis完成简单的增删改查操作, 参数传递.
  2. 掌握MyBatis的两种写法: 注解 和 XML⽅式
  3. 掌握MyBatis 相关的⽇志配置

在应⽤分层学习时, 我们了解到web应⽤程序⼀般分为三层,即:Controller、Service、Dao .

之前的案例中,请求流程如下: 浏览器发起请求, 先请求Controller, Controller接收到请求之后, 调⽤Service进⾏业务逻辑处理, Service再调⽤Dao, 但是Dao层的数据是Mock的, 真实的数据应该从数据库中读取.

我们学习MySQL数据库时,已经学习了JDBC来操作数据库, 但是JDBC操作太复杂了.

JDBC 操作⽰例回顾

我们先来回顾⼀下 JDBC 的操作流程:

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

下⾯的⼀个完整案例,展⽰了通过 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
);

以下是 JDBC 操作的具体实现代码:

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_isb
            );
            //参数绑定
            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
            );
            //参数绑定
            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) {
                //
            }
        }
    }

    public static class Book {
        private String name;
        private String author;
        private String isbn;
        //省略 setter getter ⽅法
    }
}

从上述代码和操作流程可以看出,对于 JDBC 来说,整个操作⾮常的繁琐,我们不但要拼接每⼀个参数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接等,⽽所有的这些操作步骤都需要在每个⽅法中重复书写. 那有没有⼀种⽅法,可以更简单、更⽅便的操作数据库呢?

答案是肯定的,这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更⽅便、更快速的操作数据库.

什么是MyBatis?

  • MyBatis 是⼀款优秀的 持久层 框架,⽤于简化JDBC的开发。而且本身和 Spring 没有任何关系。本质就是操作数据库的框架

  • MyBatis 本是 Apache的⼀个开源项⽬iBatis,2010年这个项⽬由 apache 迁移到了 google code,并且改名为 MyBatis 。2013年11⽉迁移到Github。

  • 官⽹:https://mybatis.org/mybatis-3/zh/index.html

在上⾯我们提到⼀个词:持久层

  • 持久层:指的就是持久化操作的层, 通常指数据访问层(dao), 是⽤来操作数据库的.

在这里插入图片描述

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

MyBatis可以看做是Java程序和数据库之间的桥梁,具体实现还是MySQL实现的(JDBC实现的)。简化JDBC的开发

接下来,我们就通过⼀个⼊⻔程序,让⼤家感受⼀下通过Mybatis如何来操作数据库

MyBatis⼊⻔

Mybatis操作数据库的步骤:

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

MyBatis写法

  1. 注解
  2. xml
  1. 测试
1. 准备⼯作

创建工程

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

在这里插入图片描述

Mybatis 是⼀个持久层框架, 具体的数据存储和数据操作还是在MySQL中操作的, 所以需要添加MySQL驱动

项⽬⼯程创建完成后,⾃动在pom.xml⽂件中,导⼊Mybatis依赖和MySQL驱动依赖

在这里插入图片描述

或者如果以前做的比如图书管理系统现在也想用到Mybatis,下面这样也可以
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

版本会随着SpringBoot 版本发⽣变化, ⽆需关注

		<!--Mybatis 依赖包-->
		<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>
		<!--mysql驱动包-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

数据准备

创建⽤⼾表, 并创建对应的实体类User

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

CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;

-- 使⽤数据库
USE mybatis_test;

-- 创建表[⽤⼾表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (
    `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(),
    PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;

-- 添加⽤⼾信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );

企业建表规范:需要具备三个字段

  1. id
  2. 创建时间
  3. 更新时间

哪怕你只要维护一个username也要加上这三个字段

命名规范:

  1. 字段名/表名:全部小写

创建对应的实体类 UserInfo

实体类的属性名与表中的字段名⼀ 对应

import lombok.Data;
import java.util.Date;

@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;
}
2. 配置数据库连接字符串

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

  • MySQL驱动类
  • 登录名
  • 密码
  • 数据库连接字符串

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

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

注意事项:

如果使⽤ MySQL 是 5.x 之前的使⽤的是 “com.mysql.jdbc.Driver”,如果是⼤于 5.x 使⽤的是 “com.mysql.cj.jdbc.Driver”.

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

 #驱动类名称
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=123456
3. 写持久层代码

在项⽬中, 创建持久层接⼝UserInfoMapper

在这里插入图片描述

import com.Hsu.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper
public interface UserInfoMapper {
    @Select("select * from userinfo")
    List<UserInfo> selectAll();
}

在这里插入图片描述

Mybatis的持久层接⼝规范⼀般都叫 XxxMapper

@Mapper注解:表⽰是MyBatis中的Mapper接⼝

  • 程序运⾏时, 框架会⾃动⽣成接⼝的实现类对象(代理对象),并给交Spring的IoC容器管理
  • @Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容.

import org.apache.ibatis.annotations.Mapper;

ibatis是Mybatis的前身

加了Mapper之后,mybatis就会告诉Spring,把对象交给Spring管理

这里为什么定义为接口interface而不能抽象类呢?

因为抽象类要有子类继承,这里不需要用到子类

4. 单元测试

可以这样测试

在这里插入图片描述

@RestController
public class UserInfoController {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @RequestMapping("/selectAll")
    public List<UserInfo> selectAll(){
        return userInfoMapper.selectAll();
    }
}

访问http://127.0.0.1:8080/selectAll

可能图看不太清

在这里插入图片描述

但上面这样比较麻烦

在创建出来的SpringBoot⼯程中,在src下的test⽬录下,已经⾃动帮我们创建好了测试类 ,我们可以直接使⽤这个测试类来进⾏测试.

@SpringBootTest
class DemoApplicationTests {
   @Autowired
   private UserInfoMapper userInfoMapper;

   @Test
   void contextLoads() {
      List<UserInfo> userInfoList = userInfoMapper.selectAll();
      System.out.println(userInfoList);
   }

}

测试类上添加了注解 @SpringBootTest,该测试类在运⾏时,就会⾃动加载Spring的运⾏环境.我们通过@Autowired这个注解, 注⼊我们要测试的类, 就可以开始进⾏测试了

运⾏结果如下:

在这里插入图片描述

返回结果中, 可以看到, 只有SQL语句中查询的列对应的属性才有赋值

使⽤Idea ⾃动⽣成测试类

除此之外, 也可以使⽤Idea⾃动⽣成测试类

  1. 在需要测试的Mapper接⼝中, 右键 -> Generate -> Test

在这里插入图片描述

  1. 选择要测试的⽅法, 点击 OK

在这里插入图片描述

  1. 书写测试代码
@Slf4j
@SpringBootTest
class UserInfoMapperTest {
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void selectAll() {
        List<UserInfo> list=userInfoMapper.selectAll();
        log.info(list.toString());
    }
}

记得加 @SpringBootTest 注解, 启动Spring容器,加载Spring运⾏环境

运⾏结果:

在这里插入图片描述

使用MyBatis可能遇到的问题
  1. 没有配置数据库相关信息

启动Spring就错误了

在这里插入图片描述

  1. 账号密码错误

启动还没报错,运行对应的方法就错误了

在这里插入图片描述

  1. 数据库错误

在这里插入图片描述

  1. 表不存在

在这里插入图片描述

  1. 字段错误

在这里插入图片描述

MyBatis的基础操作

我们学习了Mybatis的查询操作, 接下来我们学习MyBatis的增, 删, 改操作

在学习这些操作之前, 我们先来学习MyBatis⽇志打印

打印⽇志

在Mybatis当中我们可以借助⽇志, 查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果

建议只出现在开发环境中,不要出现在线上环境

开发环境(本地开发,一些公司也会提供单独的服务器进行开发调试)开发环境的数据库

测试环境(给测试人员使用)测试的数据库/配置等等

预发布环境(和线上环境一样,但不对外提供服务)

发布环境(线上环境)

预发布环境和发布环境通常用的是同一个数据库

灰度发布:是指发布(上线)环境,比如发布环境有200台机器,发布的时候是一批一批的发布。通常开始就发布一台,也就是1/200的流量进入了,观察一段时间后如果没问题就继续发布下一批(可能是3、5、10台),继续观察,如果没有问题就继续发布…

在配置⽂件中进⾏配置即可

这个只在开发环境配置

日志的打印也是影响性能的

打印的日志越多,受影响越多

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

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

#指定mybatis输出⽇志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

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

在这里插入图片描述

①: 查询语句

②: 传递参数及类型

③: SQL执⾏结果

参数传递

需求: 查找 id=4 的⽤⼾,对应的 SQL 就是: select * from userinfo where id=4

@Select("select * from userinfo where id=4")
UserInfo selectOne();

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

解决⽅案:在selectOne⽅法中添加⼀个参数(id),将⽅法中的参数,传给SQL语句

使⽤ #{} 的⽅式获取⽅法中的参数

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

如果mapper接⼝⽅法形参只有⼀个普通类型的参数,#{…} ⾥⾯的属性名可以随便写,如:#{id}、#{value}。建议和参数名保持⼀致

添加测试⽤例

	@Test
    void selectOne() {
        log.info(userInfoMapper.selectOne(1).toString());
    }

在这里插入图片描述

也可以通过 @Param , 设置参数的别名, 如果使⽤ @Param 设置别名, #{...} ⾥⾯的属性名必须和 @Param 设置的⼀样

@Select("select * from userinfo where id=#{userId}")
UserInfo selectOne2(@Param("userId") Integer id);

传递参数

使用 #{} 来接收参数

  1. 传递单个参数,#{}可以为任意
  2. 传递多个参数,默认的参数名就是Java接口方法声明的形参。如果需要修改参数名,使用@Param
  3. 传递对象,默认会给这个对象的每个属性,都有一个参数,以对象的属性名为参数名。如果对 对象参数 使用@Param进行重命名,使用时,需要加上对象名#{对象名.属性名}。这也是JavaScript获取对象的属性的方式
增(Insert)

SQL 语句:

insert into userinfo (username,password,age,gender,phone) values ("aaa","aaa",5,1,"15975253308);

把SQL中的常量替换为动态的参数

Mapper接⼝

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

注意这些括号不要用错,也不要用多了

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

测试代码:

@Test
void insert() {
    UserInfo userInfo=new UserInfo();
    userInfo.setUsername("zhaoliu");
    userInfo.setPassword("zhaoliu");
    userInfo.setAge(6);
    userInfo.setGender(0);
    userInfo.setPhone("15975253308");
    Integer result= userInfoMapper.insert(userInfo);
    log.info("insert方法,执行结果:{}",result);
}

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

在这里插入图片描述

如果设置了 @Param 属性, #{...} 需要使⽤ 参数.属性 来获取

返回主键

自增id

Insert 语句默认返回的是 受影响的⾏数

但有些情况下, 数据插⼊之后, 还需要有后续的关联操作, 需要获取到新插⼊数据的id

⽐如订单系统

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

如果想要拿到⾃增id, 需要在Mapper接⼝的⽅法上添加⼀个Options的注解

//设置返回自增id,把这个自增id赋值给 id字段
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username,password,age,gender,phone) " +
        "values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);
  • useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false.

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

测试数据:

@Test
void insert() {
    UserInfo userInfo=new UserInfo();
    userInfo.setUsername("zhaoliu");
    userInfo.setPassword("zhaoliu");
    userInfo.setAge(6);
    userInfo.setGender(0);
    userInfo.setPhone("15975253308");
    Integer result= userInfoMapper.insert(userInfo);
    //后面是获取自增id,用的是userInfo获取的id
    //注意这里是用 {} 表示占位符,接受后面的结果的
    log.info("insert方法,执行结果:{},自增id:{}",result,userInfo.getId());
}

运⾏结果:

在这里插入图片描述

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

参数为对象时,对参数进行重命名

//对insert的参数进行重命名
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username,password,age,gender,phone) " +
        "values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert2(@Param("userInfo") UserInfo userInfo);

测试数据:

@Test
void insert2() {
    UserInfo userInfo=new UserInfo();
    userInfo.setUsername("zhaoliu");
    userInfo.setPassword("zhaoliu");
    userInfo.setAge(6);
    userInfo.setGender(0);
    userInfo.setPhone("15975253308");
    Integer result= userInfoMapper.insert2(userInfo);
    //后面是获取自增id,用的是userInfo获取的id
    //注意这里是用 {} 表示占位符,接受后面的结果的
    log.info("insert方法,执行结果:{},自增id:{}",result,userInfo.getId());
}

结果:报异常,

在这里插入图片描述

在这里插入图片描述

改为这样,通过对象名.属性获取

@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username,password,age,gender,phone) " +
        "values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})")
Integer insert2(@Param("userInfo") UserInfo userInfo);

结果:

在这里插入图片描述

删(Delete)

SQL 语句:

delete from userinfo where id=8

把SQL中的常量替换为动态的参数

Mapper接⼝

@Delete("delete from userinfo where id = #{id}")
void delete(Integer id);

测试代码:

@Test
void delete() {
    userInfoMapper.delete(8);
}

在这里插入图片描述

改(Update)

SQL 语句:

update userinfo set age=8 where id=7

把SQL中的常量替换为动态的参数

Mapper接⼝

@Update("update userinfo set age=#{age} where id=#{id}")
    Integer update(UserInfo userInfo);

测试代码:

@Test
void update() {
    UserInfo userInfo=new UserInfo();
    userInfo.setAge(8);
    userInfo.setId(7);
    Integer result=userInfoMapper.update(userInfo);
    if(result>0){
        log.info("数据修改成功");
    }
}

在这里插入图片描述

查(Select)

我们在上⾯查询时发现, 有⼏个字段是没有赋值的, 只有Java对象属性和数据库字段⼀模⼀样时, 才会进⾏赋值

MyBatis会自动把MySQL返回的数据和Java对象进行映射(映射规则:名称一致)

如果MySQL字段名和Java对象的属性不一致,对于注解的实现方式:

  1. 起别名
  2. 通过@Results注解来实现
  3. 通过配置,自动转驼峰

对于XML的实现方式只有2不同:

  1. 通过<ResultMap>标签来实现

接下来我们多查询⼀些数据

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

查询结果:

在这里插入图片描述

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

MyBatis 会根据⽅法的返回结果进⾏赋值.

⽅法⽤对象 UserInfo 接收返回结果, MySQL 查询出来数据为⼀条, 就会⾃动赋值给对象.

⽅法⽤List<UserInfo>接收返回结果, MySQL 查询出来数据为⼀条或多条时, 也会⾃动赋值给List.

但如果MySQL 查询返回多条, 但是⽅法使⽤UserInfo接收, MyBatis执⾏就会报错.

原因分析:

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

在这里插入图片描述

解决办法:

  1. 起别名
  2. 结果映射
  3. 开启驼峰命名
起别名
    /**
     * 查找
     * 方法1:对字段进行重命名
     * @return
     */
    @Select("select id, username, password, age, gender, phone," +
            " delete_flag as deleteFlag, create_time as createTime, update_time as updateTime" +
            " from userinfo")//注意这里换行后前面的from要加个空格,因为字符串拼接这里连起来的话这句sql语句就是错的
    List<UserInfo> selectAll();

SQL语句太⻓时, 使⽤加号 + 进⾏字符串拼接

测试结果

在这里插入图片描述

结果映射
/**
 * 查找
 * 方法2:使用注解
 * @return
 */
@Select("select * from userinfo")
@Results({
        // column="字段名",property="Java对象属性"
        @Result(column = "delete_flag",property = "deleteFlag"),
        @Result(column = "create_time",property = "createTime"),
        @Result(column = "update_time",property = "updateTime")
})
List<UserInfo> selectAll2();
@Test
void selectAll2() {
    List<UserInfo> list=userInfoMapper.selectAll2();
    log.info(list.toString());
}

在这里插入图片描述

如果其他SQL, 也希望可以复⽤这个映射关系, 可以给这个Results定义⼀个名称

/**
 * 查找
 * 方法2:使用注解
 * @return
 */
@Select("select * from userinfo")
@Results(id="BaseMap",value = {
        // column="字段名",property="Java对象属性"
        @Result(column = "delete_flag",property = "deleteFlag"),
        @Result(column = "create_time",property = "createTime"),
        @Result(column = "update_time",property = "updateTime")
})
List<UserInfo> selectAll2();

@ResultMap(value = "BaseMap")
@Select("select * from userinfo where id=#{id}")
UserInfo selectOne(Integer id);

id="BaseMap",value = 这部分就是需要复用时才需要加的

@Test
void selectOne() {
    log.info(userInfoMapper.selectOne(1).toString());
}

在这里插入图片描述

使⽤ id 属性给该 Results 定义别名, 使⽤ @ResultMap 注解来复⽤其他定义的 ResultMap

在这里插入图片描述

如果字段和Java属性名称一样,可以省略

开启驼峰命名(推荐)

通常数据库列使⽤蛇形命名法进⾏命名(下划线分割各个单词), ⽽ Java 属性⼀般遵循驼峰命名法约定.

为了在这两种命名⽅式之间启⽤⾃动映射,需要将 mapUnderscoreToCamelCase 设置为 true。

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

驼峰命名规则: abc_xyz => abcXyz

  • 表中字段名:abc_xyz
  • 类中属性名:abcXyz
/**
 * 查找
 * 方法3:使用配置,自动转为驼峰
 * @return
 */
@Select("select * from userinfo")
List<UserInfo> selectAll3();
@Test
void selectAll3() {
    List<UserInfo> list=userInfoMapper.selectAll3();
    log.info(list.toString());
}

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

在这里插入图片描述

字段全部进⾏正确赋值.

MyBatis XML配置⽂件

Mybatis的开发有两种⽅式:

  1. 注解
  2. XML

上⾯学习了注解的⽅式, 接下来我们学习XML的⽅式

使⽤Mybatis的注解⽅式,主要是来完成⼀些简单的增删改查功能. 如果需要实现复杂的SQL功能,建议使⽤XML来配置映射语句,也就是将SQL语句写在XML配置⽂件中.

注解和XML是可以共存的

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

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

XML的方式:

  1. 需要配置数据库
  2. 指明xml的路径
  3. 写xml的实现
配置连接字符串和MyBatis

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

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

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #配置打印 MyBatis日志
    map-underscore-to-camel-case: true #配置驼峰⾃动转换
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
  mapper-locations: classpath:mapper/**Mapper.xml

指定MyBatis的mapper文件位置。这里设置的路径意味着Spring Boot会扫描resources/mapper目录及其子目录中所有名称以Mapper.xml结尾的XML文件。这些XML文件通常包含了SQL语句和MyBatis的映射配置

在这里插入图片描述

名字可以随机写但是要对应

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

#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding
#连接数据库的⽤⼾名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=root
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
写持久层代码

持久层代码分两部分

  1. ⽅法定义 Interface
  2. ⽅法实现 XXX.xml

在这里插入图片描述

添加 mapper 接⼝

数据持久层的接⼝定义:

@Mapper
public interface UserInfoXMLMapper {
    List<UserInfo> selectAll();
}
添加 UserInfoXMLMapper.xml

在这里插入图片描述

数据持久成的实现,MyBatis 的固定 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.Hsu.demo.mapper.UserInfoXMLMapper">

</mapper>

com.Hsu.demo.mapper.UserInfoXMLMapper这个是要实现的那个接口,要写接口的全限定类名

不能写错,验证:按住ctrl然后点击能找到就行

namespace="com.Hsu.demo.mapper.UserInfoXMLMapper"定义了这个映射文件的命名空间,通常对应于一个接口路径,这有助于MyBatis找到和这个文件关联的方法

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

在这里插入图片描述

在这里插入图片描述

查询所有⽤⼾的具体实现 :

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

以下是对以上标签的说明:

  • <mapper> 标签:需要指定 namespace 属性,表⽰命名空间,值为 mapper 接⼝的全限定名,包括全包名.类名

  • <select>查询标签:是⽤来执⾏数据库的查询操作的:

    • id :是和 Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法
    • resultType :是返回的数据类型,也就是开头我们定义的实体类

在这里插入图片描述

select id="selectAll"这里要写对应的方法名

resultType="com.Hsu.demo.model.UserInfo"这个是返回的类型

这里是返回一个List的UserInfo,返回的就是UserInfo,全限定类名

如果返回的是一个UserInfo,那也写UserInfo

不管返回的是对象还是List,定义的都是返回的数据的类型

测试代码

@Slf4j
@SpringBootTest
class UserInfoXMLMapperTest {
    @Autowired
    private UserInfoXMLMapper userInfoXMLMapper;

    @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoXMLMapper.selectAll();
        log.info(userInfos.toString());
    }
}

在这里插入图片描述

常见错误:在这里插入图片描述

这是没有找到对应的实现

  1. xml文件处和接口定义的方法名称不一致
  2. mapper的路径配置和xml文件的路径不一致
  3. xml namespace写错了
增删改查操作
增(Insert)

UserInfoMapper接⼝:

Integer insertUser(UserInfo userInfo);

返回值通常表示插入操作影响的行数。如果返回1,则通常意味着成功插入了一行数据;如果返回0,则没有行被插入。

UserInfoMapper.xml实现:

<insert id="insert">
    insert into userinfo (username, password, age, gender, phone)
    values (#{username}, #{password}, #{age}, #{gender}, #{phone})
</insert>
@Test
void insert() {
    UserInfo userInfo=new UserInfo();
    userInfo.setUsername("liu666");
    userInfo.setPassword("liu666");
    userInfo.setAge(1);
    userInfo.setGender(2);
    userInfo.setPhone("15975253308");
    Integer result = userInfoXMLMapper.insert(userInfo);
    log.info("影响行数:{}",result);
}

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

UserInfoMapper接⼝:

Integer insertUser(@Param("userinfo") UserInfo userInfo);

UserInfoMapper.xml实现:

<insert id="insertUser">
	insert into userinfo (username, `password`, age, gender, phone) values
	(#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gende
</insert>
返回主键

接⼝定义不变, Mapper.xml 实现 设置 useGeneratedKeys 和 keyProperty 属性

<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into userinfo (username, password, age, gender, phone)
    values (#{username}, #{password}, #{age}, #{gender}, #{phone})
</insert>
删(Delete)

UserInfoMapper接⼝:

Integer delete(Integer id);

返回值通常表示被删除的行数。

UserInfoMapper.xml实现:

<delete id="deleteUser">
	delete from userinfo where id = #{id}
</delete>
@Test
void delete() {
    userInfoXMLMapper.delete(11);
}
改(Update)

UserInfoMapper接⼝:

Integer update(UserInfo userInfo);

返回值通常表示更新的行数。

UserInfoMapper.xml实现:

<update id="update">
    update userinfo set gender=#{gender} where id =#{id}
</update>
@Test
void update() {
    UserInfo userInfo=new UserInfo();
    userInfo.setGender(1);
    userInfo.setId(12);
    userInfoXMLMapper.update(userInfo);
}
查(Select)

同样的, 使⽤XML 的⽅式进⾏查询, 也存在数据封装的问题

我们把SQL语句进⾏简单修改, 查询更多的字段内容

<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
	select id, username,`password`, age, gender, phone, delete_flag, create_time
</select>

在这里插入图片描述

结果显⽰: deleteFlag, createTime, updateTime 也没有进⾏赋值.

解决办法和注解类似:

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

其中1,3的解决办法和注解⼀样,不再多说, 接下来看下xml如果来写结果映射

UserInfoXMLMapper.xml

<resultMap id="XMLBaseMap" type="com.Hsu.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="selectAll2" resultMap="XMLBaseMap">
    select * from userinfo
</select>

resultType是结果类型,resultMap是结果映射

结果映射resultMap要对应resultMap id="XMLBaseMap"这里的

对于有结果映射就不需要结果类型了

id="XMLBaseMap":结果映射的唯一标识符

type="com.Hsu.demo.model.UserInfo":指定映射到的Java类型

内部的<id><result>元素定义了数据库表中的字段和Java对象属性之间的映射关系

这部分呢,建议是要写就全都写了,而不只是要改的才写,别的在数据库中的字段名和Java对象属性名一样的也要写上去,统一操作一下,因为有些场景没写全可能会导致bug

List<UserInfo> selectAll2();
@Test
void selectAll2() {
    List<UserInfo> userInfos = userInfoXMLMapper.selectAll();
    log.info(userInfos.toString());
}

在这里插入图片描述

这个主键是Mysql主键

开发中使⽤注解还是XML的⽅式?

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

其他查询操作
多表查询

其实公司工作中是尽量避免使用多表查询的,尤其是对性能要求比较高的项目

多表查询和单表查询类似, 只是SQL不同⽽已

准备工作

上⾯建了⼀张⽤⼾表, 我们再来建⼀张⽂章表, 进⾏多表关联查询.

⽂章表的uid, 对应⽤⼾表的id.

数据准备

 -- 创建⽂章表
DROP TABLE IF EXISTS articleinfo;

CREATE TABLE articleinfo (
    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 articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正⽂', 1 );

对应Model:

@Data
public class ArticleInfo {
    //文章相关信息
    private Integer id;
    private String titie;
    private String content;
    private Integer uid;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

接口定义:

/**
 * 多表查询
 * @param articleId
 * @return
 */
@Select("select ta.* , tb.username, tb.age from articleinfo ta" +
        " LEFT JOIN userinfo tb on ta.uid = tb.id" +
        " where ta.id = 1")
ArticleInfo selectArticleAndUserById(Integer articleId);

测试代码:

@Test
void selectArticleAndUserById() {
    ArticleInfo articleInfo=articleInfoMapper.selectArticleAndUserById(1);
    log.info(articleInfo.toString());
}

如果只是这样直接运行测试

在这里插入图片描述

这是因为ArticleInfo中没有对应的字段属性。因此看下面

数据查询

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

SQL:

SELECT
	ta.id,
	ta.title,
	ta.content,
	ta.uid,
	tb.username,
	tb.age,
	tb.gender 
FROM
	articleinfo ta
	LEFT JOIN userinfo tb ON ta.uid = tb.id 
WHERE
	ta.id =1

补充实体类:

@Data
public class ArticleInfo {
    //文章相关信息
    private Integer id;
    private String titie;
    private String content;
    private Integer uid;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
    //⽤⼾相关信息
    private String username;
    private Integer age;
}

接⼝定义:

/**
 * 多表查询
 * @param articleId
 * @return
 */
@Select("select ta.* , tb.username, tb.age from articleinfo ta" +
        " LEFT JOIN userinfo tb on ta.uid = tb.id" +
        " where ta.id = 1")
ArticleInfo selectArticleAndUserById(Integer articleId);

测试代码:

@Test
void selectArticleAndUserById() {
    ArticleInfo articleInfo=articleInfoMapper.selectArticleAndUserById(1);
    log.info(articleInfo.toString());
}

在这里插入图片描述

在这里插入图片描述

SQL中直接查询多个表,把查询的结果放在一个对象即可,也就是上面的ArticleInfo

如果名称不⼀致的, 采⽤ResultMap, 或者别名的⽅式解决, 和单表查询⼀样

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

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

#{} 和 ${}

MyBatis 参数赋值有两种⽅式, 咱们前⾯使⽤了 #{} 进⾏赋值, 接下来我们看下⼆者的区别

#{} 和 ${} 使⽤
  1. 先看Interger类型的参数
@ResultMap(value = "BaseMap")
@Select("select * from userinfo where id=#{id}")
UserInfo selectOne(Integer id);

观察我们打印的⽇志

在这里插入图片描述

发现我们输出的SQL语句:

select * from userinfo where id=?

我们输⼊的参数并没有在后⾯拼接,id的值是使⽤ ? 进⾏占位. 这种SQL 我们称之为"预编译SQL"

MySQL 中 JDBC编程使⽤的就是预编译SQL

我们把 #{} 改成 ${} 再观察打印的⽇志:

@ResultMap(value = "BaseMap")
@Select("select * from userinfo where id=${id}")
UserInfo selectOne(Integer id);

在这里插入图片描述

可以看到, 这次的参数是直接拼接在SQL语句中了.这种称之为“即时SQL

  1. 接下来我们再看String类型的参数
@ResultMap(value = "BaseMap")
@Select("select * from userinfo where username=#{username}")
UserInfo selectByName(String username);

观察我们打印的⽇志, 结果正常返回

在这里插入图片描述

我们把 #{} 改成 ${} 再观察打印的⽇志:

@ResultMap(value = "BaseMap")
@Select("select * from userinfo where username=${username}")
UserInfo selectByName(String username);

在这里插入图片描述

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

select * from userinfo where username='admin' 这是正确的语句,拼接的是这样的:select * from userinfo where username=admin 少了两个引号

修改代码如下:

@ResultMap(value = "BaseMap")
@Select("select * from userinfo where username='${username}'")
UserInfo selectByName(String username);

再次运⾏, 结果正常返回

在这里插入图片描述

从上⾯两个例⼦可以看出:

#{} 使⽤的是预编译SQL, 通过 ? 占位的⽅式, 提前对SQL进⾏编译, 然后把参数填充到SQL语句中。**#{} 会根据参数类型, ⾃动拼接引号 '' **。而${}是直接拼接

${} 会直接进⾏字符替换, ⼀起对SQL进⾏编译. 如果参数为字符串, 需要加上引号 '' .

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

#{} 和 ${} 区别

#{}${} 的区别就是预编译SQL和即时SQL的区别.

简单回顾:

当客⼾发送⼀条SQL语句给服务器后, ⼤致流程如下:

  1. 解析语法和语义, 校验SQL语句是否正确
  2. 优化SQL语句, 制定执⾏计划
  3. 执⾏并返回结果

⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL)

  1. 性能更⾼

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

在这里插入图片描述

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

  1. 更安全(防⽌SQL注⼊)

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

由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击。

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

select * from userinfo where username=' ' or1=1

那么在SQL语句中where相当于判断语句,并且是由 or 连接的,所以 username=’ ‘ 和 1=1 中有一个为真就为真。1=1一定为真,所以语句又等价于:select * from userinfo

先来看看SQL注⼊的例⼦

@ResultMap(value = "BaseMap")
@Select("select * from userinfo where username='${username}'")
List<UserInfo> selectByName(String username);

测试代码:

正常访问情况:

@Test
void selectByName() {
	List<UserInfo> userInfos = userInfoMapper.selectByName("admin");
	System.out.println(userInfos);
}

结果运⾏正常

在这里插入图片描述

SQL注⼊场景:

@Test
void selectByName() {
    log.info(userInfoMapper.selectByName("'or 1='1").toString());
}

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

在这里插入图片描述

可以看出来, 查询的数据并不是⾃⼰想要的数据. 所以⽤于查询的字段,尽量使⽤ #{} 预查询的⽅式

@ResultMap(value = "BaseMap")
@Select("select * from userinfo where username=#{username}")
List<UserInfo> selectByName(String username);
@Test
void selectByName() {
    log.info(userInfoMapper.selectByName("'or 1='1").toString());
}

在这里插入图片描述

SQL注⼊是⼀种⾮常常⻅的数据库攻击⼿段, SQL注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀.

如果发⽣在⽤⼾登录的场景中, 密码输⼊为 ’ or 1='1 , 就可能完成登录(不是⼀定会发⽣的场景,需要看登录代码如何写)

控制层: UserController


业务层: UserService


数据层: UserInfoMapper


启动服务, 访问: http://127.0.0.1:8080/login?name=admin&password=admin

程序正常运⾏

在这里插入图片描述

接下来访问SQL注⼊的代码:

password 设置为 ' or 1='1

http://127.0.0.1:8080/login?name=admin&password=%27%20or%201=%271

在这里插入图片描述

排序功能

从上⾯的例⼦中, 可以得出结论: ${} 会有SQL注⼊的⻛险, 所以我们尽量使⽤#{}完成查询

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

当然不是.

接下来我们看下${}的使⽤场景

Mapper实现

@Select("select * from userinfo order by id ${sort}")
List<UserInfo> selectUserBySort(String sort);

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

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

我们把 ${} 改成 #{}

@Select("select * from userinfo order by id #{sort}")
List<UserInfo> selectUserBySort(String sort);

运⾏结果:

在这里插入图片描述

在这里插入图片描述

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

select * from userinfo order by id asc这是正确的SQL语句,用了#{sort}变成了select * from userinfo order by id 'asc'了,多了两个引号

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

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

除此之外, 还有表名作为参数时, 也只能使⽤ ${}

like 查询

模糊查询

like 使⽤ #{} 报错

/**
     * 模糊查询
     * @param username
     * @return
     */
    @Select("select * from userinfo where username like'%#{username}%'")
    List<UserInfo> selectUserByLike(String username);

在这里插入图片描述

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

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

/**
 * 模糊查询
 * @param username
 * @return
 */
@Select("select * from userinfo where username like concat('%',#{username},'%')")
List<UserInfo> selectUserByLike(String username);
@Test
void selectUserByLike() {
    log.info(userInfoMapper.selectUserByLike("liu").toString());
}

在这里插入图片描述

数据库连接池

在上⾯Mybatis的讲解中, 我们使⽤了数据库连接池技术, 避免频繁的创建连接, 销毁连接

下⾯我们来了解下数据库连接池

介绍

数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,⽽不是再重新建⽴⼀个.

在这里插入图片描述

没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQL语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源

使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把Connection归还给连接池.

优点:

  1. 减少了⽹络开销
  2. 资源重⽤
  3. 提升了系统的性能
使⽤

常⻅的数据库连接池:

  • C3P0
  • DBCP
  • Druid
  • Hikari

⽬前⽐较流⾏的是 Hikari, Druid

  1. Hikari : SpringBoot默认使⽤的数据库连接池

在这里插入图片描述

Hikari 是⽇语"光"的意思(ひかり), Hikari也是以追求性能极致为⽬标

  1. Druid

如果我们想把默认的数据库连接池切换为Druid数据库连接池, 只需要引⼊相关依赖即可

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

运⾏结果:

在这里插入图片描述

参考官⽅地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

  • Druid连接池是阿⾥巴巴开源的数据库连接池项⽬
  • 功能强⼤,性能优秀,是Java语⾔最好的数据库连接池之⼀
  • 学习⽂档: https://github.com/alibaba/druid/wiki/%E9%A6%96%E9%A1%B5

⼆者对⽐,参考: Hikaricp和Druid对⽐_数据库_晚⻛暖-华为云开发者联盟

总结

MySQL 开发企业规范
  1. 表名, 字段名使⽤⼩写字⺟或数字, 单词之间以下划线分割. 尽量避免出现数字开头或者两个下划线中间只出现数字. 数据库字段名的修改代价很⼤, 所以字段名称需要慎重考虑。

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

正例: aliyun_admin, rdc_config, level3_name

反例: AliyunAdmin, rdcConfig, level_3_name

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

id 必为主键, 类型为 bigint unsigned, 单表时⾃增, 步⻓为 1

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

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

  1. 在表查询中, 避免使⽤ * 作为查询的字段列表, 标明需要哪些字段(课堂上给⼤家演⽰除外).
  1. 增加查询分析器解析成本
  2. 增减字段容易与 resultMap 配置不⼀致
  3. ⽆⽤字段增加⽹络消耗, 尤其是 text 类型的字段
#{} 和${} 区别
  1. #{}:预编译处理, ${}:字符直接替换
  2. #{} 可以防⽌SQL注⼊, ${}存在SQL注⼊的⻛险, 查询语句中, 可以使⽤ #{} ,推荐使⽤ #{}
  3. 但是⼀些场景, #{} 不能完成, ⽐如 排序功能, 表名, 字段名作为参数时, 这些情况需要使⽤${}
  4. 模糊查询虽然${}可以完成, 但因为存在SQL注⼊的问题,所以通常使⽤mysql内置函数concat搭配#{}来完成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2047890.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue使用高德获取当前地区天气

1、收件箱 | 高德控制台 (amap.com) 首先打开高德开放平台注册一下 2、创建一个应用获取到key后面获取天气的时候 请求接口的时候会用到key 2.1.1 创建应用的时候注意类型选成天气 2.1.2 创建完成之后就点添加key 然后选择web服务就行 3、可以调取天气接口 天气查询-基础 API…

https握手过程详解

https握手过程详解 上一篇《HTTPS通讯全过程》中https握手过程实际上还有更多的细节&#xff0c;为什么会这样设计呢&#xff1f;是因为一开始将握手过程时&#xff0c;吧步骤说的太详细会导致更难理解惹。所以我就先在上一篇把部分细节忽略&#xff0c;把原来几步的过程先简化…

洛杉物理服务器怎么样?

洛杉矶作为美国科技和互联网的重要中心&#xff0c;物理服务器的质量通常非常高&#xff0c;可以提供卓越的性能、强大的安全性、多样的配置选项和专业的服务支持。以下是对洛杉物理服务器的详细介绍。 1. 优质的性能 稳定的网络连接&#xff1a;洛杉矶物理服务器位于先进的数据…

CASS11时空版 全新升级支持多版本CAD软件下载License使用

南方数码地形地籍成图软件CASS&#xff0c;经过二十余年的发展&#xff0c;市场和技术积累丰厚&#xff0c;用户遍及国内外测绘地理信息相关行业。软件销量和市场占有率持续领先&#xff0c;是业内应用广&#xff0c;服务优的软件品牌。 南方数码深刻理解信息化测绘的内…

合宙LuatOS生成毫秒级时间戳

合宙Luatos - os操作 os.time()生成时间戳的精度只能达到秒级&#xff0c;在很多联网应用中需要毫秒级的时间戳。 经查看LuatOS-SOC接口文档&#xff0c;发现了解决办法。 socket - 网络接口文档 通过文档&#xff0c;我们只要获取当前数&#xff0c;然后把毫秒数与os.time(…

【C语言】:字符和字符串中的字符比较

1.入门 当我们想要一个字符和字符串中的某个字符进行比较时&#xff0c;可以直接用“”进行比较。 为什么可以用“”&#xff1f; 因为字符是存放在常量区&#xff0c;字符变量的值是固定的&#xff0c;字符之间的比较&#xff0c;本质上是对字符的ASCII比较。ASCII_百度百科…

FinalData-绿色便携免安装数据恢复软件 下载

下载地址(资源制作整理不易&#xff0c;使用需付费&#xff0c;不能接受请勿浪费时间下载)&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/17CH5tkSc2qAj-6FuGvfb9Q?pwdvyze 提取码&#xff1a;vyze

基于Java语言的能源管理系统-水电气热油数据采集系统

基于Java语言的能源管理系统-水电气热油数据采集系统 介绍 适用于高能耗企业、建筑、工厂、园区的水、电、气、热、油、空压机等能源数据采集、分析、报表&#xff1b; 基于SpringCloud的能源管理系统-能源管理平台源码-能源在线监测平台-双碳平台源码-SpringCloud全家桶-能管…

string模拟

本章准备对string模拟进行讲解&#xff0c;以下是string的学习网址&#xff1a; string - C Reference (cplusplus.com) string本质可以理解为储存char类型的顺序表&#xff0c;其中string的迭代器用一个char*就可以解决。所以string类成员变量如下&#xff1a; 这里用了一个命…

PumpkinRaising靶机

端口扫描 目录扫描 访问80端口&#xff0c; 在页面上面发现提到了一个Jack&#xff0c;可能是一个用户名 f12查看源码 找到一个页面 拼接访问 查看源码 发现一个注释 解密 是一个目录 /scripts/spy.pcap 访问&#xff0c;自动下载了一个文件 wireshark打开流量包 找到第一个s…

Element plus部分组件样式覆盖记录

文章目录 一、el-button 样式二、Popconfirm 气泡确认框三、Popover 气泡卡片四、Checkbox 多选框五、Pagination 分页六、Form 表单七、Table 表格 一、el-button 样式 html&#xff1a; <el-button class"com_btn_style">button</el-button>样式覆盖…

端口隔离 Port isolation 华为交换机配置端口隔离

Port isolation 什么是端口隔离 如果用户想进行二层隔离&#xff0c;用户可以将不同的端口加入不同的VLAN&#xff0c;但这样会浪费有限的VLAN资源。采用端口隔离功能&#xff0c;可以实现同一VLAN内端口之间的隔离。用户只需要将端口加入到隔离组中&#xff0c;就可以实现隔离…

hyper-v连接显卡,hyper-v使用显卡能力、Hyper-V显卡虚拟化VMGpu设置

hyper-v连接显卡&#xff0c;hyper-v使用显卡能力、Hyper-V显卡虚拟化VMGpu设置 现在越来越多的软件在使用时&#xff0c;都会调用GPU获得更好的使用效果。如&#xff1a;浏览器的硬件加速模式。由于Nvidia和AMD都屏蔽了家用显卡虚拟化技术&#xff0c;常用的虚拟机也无法对显卡…

交互式散点图,快速提升你的PPT观赏性|每日科研绘图·24-08-17

一、散点图基础概念 散点图是一种非常直观且功能强大的图表&#xff0c;用于探索和展示两个数值变量之间的相关性。这种图表通过在二维平面上绘制数据点&#xff0c;使得观察者能够一眼看出变量间的潜在联系。 1-1&#xff1a;散点图的构成 X轴&#xff08;横轴&#xff09;&…

电话语音机器人优势很多

智能语音机器人近年来备受关注&#xff0c;受到很多个人或是企业的青睐&#xff0c;其广泛受到欢迎归因于智能语音机器人对电话销售提供了极大的帮助&#xff0c;其可以完美替代人工进行电销外呼服务&#xff0c;不间断的工作&#xff0c;不带有任何情绪色彩&#xff0c;且能实…

Hive:大数据时代的SQL魔法师

时间&#xff1a;2024年08月17日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 音频地址&#xff1a;https://xima.tv/1_ZRh54d?_sonic0 希望大家帮个忙&#xff01;如果大家有工作机会&#xff0c;希望帮小蒋内推一下&#x…

半岛体存储器常见类型简介

前言 个人邮箱&#xff1a;zhangyixu02gmail.com在学习 ESP32 的存储器结构时&#xff0c;发现 DRAM 是 Data RAM 而非 Dynamic RAM&#xff0c;IRAM 是 Instruction RAM 而非 Internal RAM 。突然发现自己对于这一块的知识还比较混乱&#xff0c;因此查阅相关资料进行学习整理…

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(一)---UnrealCV获取深度+分割图像

前言 本系列教程旨在使用UE5配置一个具备激光雷达深度摄像机的仿真小车&#xff0c;并使用通过跨平台的方式进行ROS2和UE5仿真的通讯&#xff0c;达到小车自主导航的目的。本教程使用的环境&#xff1a; ubuntu 22.04 ros2 humblewindows11 UE5.4.3python8 本系列教程将涉及以…

04-正弦波,衰减正弦波,正弦波脉冲冲串的产生

1.设置波形线宽 点击Waveforms a 2.添加Comment 3.添加正弦波 3.1先添加一个电压源 3.2增加波形窗口 3.3右键选择Advanced 3.31原始正弦波 名称含义①DC offset直流偏置②Amplitude幅值③Freq频率④Tdelay延迟⑤Theta衰减⑥Phi相位⑦Ncycles产生正弦波的个数 设置完成后&am…

数据结构与算法——BFS(广度优先搜索)

算法介绍&#xff1a; 广度优先搜索&#xff08;Breadth-First Search&#xff0c;简称BFS&#xff09;是一种遍历或搜索树和图的算法&#xff0c;也称为宽度优先搜索&#xff0c;BFS算法从图的某个节点开始&#xff0c;依次对其所有相邻节点进行探索和遍历&#xff0c;然后再…