开发者

深入解析SpringBoot中#{}和${}的使用

目录
  • 1.#{} 和 ${}的使用
    • 1.1数据准备
      • 1.1.1.mysql数据准备
      • 1.1.2.创建对应的实体类
    • 1.2 获取Integer类型
      • 1.2.1 #{}
      • 1.2.2 ${}
    • 1.3 获取String类型
      • 1.3.1 #{}
      • 1.3.2 ${}
  • 2.#{} 和 ${}的区别
    • 2.1 预编译SQL和即时SQL的执行过程
      • 2.1.1 预编译SQL执行过程
      • 2.1.2 即时QL执行过程
    • 2.2性能比较
      • 2.3 排序举例
        • 2.3.1 #{}
        • 2.3.2 #{}
      • 2.4 like 查询
        • 2.4.1 #{}
        • 2.4.2 ${}
    • 3.什么是SQL注入?
      • 4.数据库连接池
        • 4.1介绍
          • 4.2使⽤

          1.#{} 和 ${}的使用

          1.1数据准备

          1.1.1.MySQL数据准备

          (1)创建数据库:

          CREATE DATABASE myBATis_study DEFAULT CHARACTER SET utf8mb4;
          

          (2)使用数据库

          -- 使⽤数据数据
          USE mybatis_study;
          

          (3)创建用户表

          -- 创建表[⽤⼾表]
          
          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; 

          (4)添加用户信息

          -- 添加⽤⼾信息
          INSERT INTO mybatis_study.user_info( username, `password`, age, gender, phone )
          VALUhttp://www.devze.comES ( 'admin', 'admin', 18, 1, '18612340001' );
          INSERT INTO mybatis_study.user_info( username, `password`, age, gender, phone )
          VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
          INSERT INTO mybatis_study.user_info( username, `password`, age, gender, phone )
          VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
          INSERT INTO mybatis_study.user_info( username, `password`, age, gender, phone )
          VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
          

          1.1.2.创建对应的实体类

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

          @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;
          
          }
          

          深入解析SpringBoot中#{}和${}的使用

          注意:在实际开发中不管什么实体类都要设置删除标志、创建时间、修改时间

          1.2 获取Integer类型

          1.2.1 #{}

          Mapper接口:

          @Mapper
          public interface UserInfoMapper {
              // 获取参数中的 UserId
              @Select("select * from user_info where id = #{userId} ")
              UserInfo queryById(@Param("userId") Integer id);
          
          

          测试代码:

          @Slf4j
          @SpringBootTest //启动Sring 容器
          class UserInfoMapperTest {
          
              @Test
              void queryById() {
                  UserInfo result = userInfoMapper.queryById(8);
                  log.info(result.toString());
              }
          }
          

          运行结果:

          深入解析SpringBoot中#{}和${}的使用

          通过日志可以发现,进行占位,传的参数进行绑定到占位符。

          1.2.2 ${}

          Mapper接口:

          @Mapper
          public interface UserInfoMapper {
              // 获取参数中的 UserId
              @Select("select * from user_info where id = ${userId} ")
              UserInfo queryById(@Param("userId")javascript Integer id);
          
          

          测试代码:

          @Slf4j
          @SpringBootTest //启动Sring 容器
          class UserInfoMapperTest {
          
              @Test
              void queryById() {
                  UserInfo result = userInfoMapper.queryById(8);
                  log.info(result.toString());
              }
          }
          

          运行结果:

          深入解析SpringBoot中#{}和${}的使用

          通过日志可以发现,SQL命令是完整的,因为,该方法是把字符串拼接在一起执行的。

          1.3 获取String类型

          1.3.1 #{}

          Mapper接口:

          @Mapper
          public interface UserInfoMapper {
              // 获取参数中的 username
              @Select("select * from user_info where username = #{username} ")
              List<UserInfo> queryByUsername( String username);
          
          

          测试代码:

          @Slf4j
          @SpringBootTest //启动Sring 容器
          class UserInfoMapperTest {
              @Autowired
              private UserInfoMapper userMapper;
          
              @Test
              void queryByUsername() {
                  userMapper.queryByUsername("lisi");
              }
          }
          

          运行结果:

          深入解析SpringBoot中#{}和${}的使用

          通过日志可以发现,进行占位,传的参数进行绑定到占位符。

          1.3.2 ${}

          Mapper接口:

          @Mapper
          public interface UserInfoMapper {
              // 获取参数中的 username
              @Select("select * from user_info where username = ${username} ")
              List<UserInfo> queryByUsername( String username);
          
          

          测试代码:

          @Slf4j
          @SpringBootTest //启动Sring 容器
          class UserInfoMapperTest {
              @Autowired
              private UserInfoMapper userMapper;
          
              @Test
              void queryByUsername() {
                  userMapper.queryByUsername("lisi");
              }
          }
          

          运行结果:报错

          深入解析SpringBoot中#{}和${}的使用

          深入解析SpringBoot中#{}和${}的使用

          从SQL语句中明显的看到WHERE username 后面的字符串没有引号,导致报错。

          因为,${}直接把字符内容直接放进SQL语句中而没有加单引号。

          修改后的mapper接口:

          @Mapper
          public interface UserInfoMapper {
              // 获取参数中的 username
              @Select("select * from user_info where username = '${username}' ")
              UserInfo queryByUsername( String username);
          
          

          深入解析SpringBoot中#{}和${}的使用

          2.#{} 和 ${}的区别

          2.1 预编译SQL和即时SQL的执行过程

          2.1.1 预编译SQL执行过程

          #{}是预编译SQL。

          第一步:数据库客户端(如 JDBC 驱动)将 SQL 模板发送到数据库服务器。

          // SQL模版
          PreparedStatement pstmt = connection.prepareStatement("SELECT * FROM user WHERE id = ? AND name = ?");
          

          第二步:SQL 预编译

          (1)数据库解析 SQL 模板,生成执行计划(包括语法检查、语义分析、优化等),并缓存该计划。

          (2)此时,占位符 ? 的具体值尚未填充,数据库只处理 SQL 的结构。

          第三步:客户端通过 PreparedStatement编程客栈 的方法设置参数值

          //参数值以二进制形式单独发送到数据库,不会直接拼接到 SQL 中,避免了 SQL 注入
          pstmt.setInt(1, 123);    // 绑定 id
          pstmt.setString(2, "Alice"); // 绑定 name
          

          第四步:SQL 执行

          (1)数据库使用缓存的执行计划,将绑定参数代入执行计划,直接运行查询或更新操作。

          (2)如果相同的 SQL 模板再次执行(仅参数不同),数据库可复用缓存的执行计划,减少编译开销。

          2.1.2 即时QL执行过程

          ${}是即时SQL。

          第一步:SQL 语句拼接

          SELECT * FROM user ORDER BY ${columnName}
          
          //如果 columnName = "age"
          
          //生成
          SELECT * FROM user ORDER BY age; DROP TABLE user;
          

          第二步:SQL 发送到数据库

          客户端将拼接好的完整 SQL 字符串通过 Statement 或类似接口发送到数据库

          Statement stmt = connection.createStatement();
          ResultSet rs = stmt.executeQuery(sql);

          第三步:SQL解析与编译

          语法解析:检查 SQL 语句的语法是否正确。

          语义分析:验证表名、列名等是否存在,权限是否足够。

          优化:生成执行计划,选择最优的查询路径。

          第四步:SQL执行

          数据库根据生成的执行计划执行 SQL,完成查询或更新操作。

          2.2性能比较

          预编译SQL(#{})性能更高:

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

          深入解析SpringBoot中#{}和${}的使用

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

          预编译SQL(#{})更安全(防⽌SQL注⼊):

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

          2.3 排序举例

          排序需要用到SQL的关键字asc 和desc,把该两个关键字设置为参数时需要用到${},因为#{}会把asc 和desc认为是字符串

          2.3.1 #{}

          Mapper接口:

          @Mapper
          public interface UserInfoMapper {
          
              @Select("select * from userInfo order by username #{flag}")
              List<UserInfo> findAll(String flag);
          }
          

          测试代码

          @Slf4j
          @SpringBootTest //启动Sring 容器
          class UserInfoMapperTest {
          
              @Autowired
              private UserInfoMapper userMapper;
          
              @Test
              void findAll() {
                  userMapper.findAll("asc");
              }
          }
          

          运行结果:

          深入解析SpringBoot中#{}和${}的使用

          2.3.2 #{}

          Mapper接口:

          @Mapper
          public interface UserInfoMapper {
          
              @Select("select * from userInfo order by username ${flag}")
              List<UserInfo> findAll(String flag);
          }
          

          测试代码

          @Slf4j
          @SpringBootTest //启动Sring 容器
          class UserInfoMapperTest {
          
              @Autowired
              private UserInfoMapper userMapper;
          
              @Test
              void findAll() {
                  userMapper.findAll("asc");
              }
          }
          

          运行结果:

          深入解析SpringBoot中#{}和${}的使用

          2.4 like 查询

          2.4.1 #{}

          Mapper接口:

          @Mapper
          public interface UserInfoMapper {
          
              @Select("select * from user_info where username like '%#{s}%'")
              List<UserInfo> queryLike(String s);
          }
          

          测试代码:

          @Slf4j
          @SpringBootTest //启动Sring 容器
          class UserInfoMapperTest {
          
              @Autowired
              private UserInfoMapper userMapper;
          
              @Test
              void queryLike() {
                  String s = "6";
                  userMapper.queryLike(s);
              }
          }
          

          运行结果:

          深入解析SpringBoot中#{}和${}的使用

          把 #{} 改成 可以正确查出来 , 但是 {} 可以正确查出来, 但是可以正确查出来,但是{}存在SQL注⼊的问题, 所以不能直接使⽤ ${}.解决办法: 使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:

          修改后的Mapper接口:

          @Mapper
          public interface UserInfoMapper {
          
              @Select("select * from user_info where username like concat('%',#{s},'%') ")
              List<UserInfo> queryLike(String s);
          

          运行结果:

          深入解析SpringBoot中#{}和${}的使用

          2.4.2 ${}

          Mapper接口:

          @Mapper
          public interface UserInfoMapper {
          
              @Select("select * from user_info where username like '%${s}%' ")
              List<UserInfo> queryLike(String s);
          }
          

          测试代码:

          @Slf4j
          @SpringBootTest //启动Sring 容器
          class UserInfoMapperTest {
          
              @Autowired
              private UserInfoMapper userMapper;
          
              @Test
              void queryLike() {
                  String s = "6";
                  userMapper.queryLike(s);
              }
          }
          

          运行结果:

          深入解析SpringBoot中#{}和${}的使用

          3.什么是SQL注入?

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

          举例:

          下面定义的接口是由username得到该username的信息

          Mapper接口:

          @Mapper
          public interface UserInfoMapper {
              // 获取参数中的 username
              @Select("select * from user_info where username = ${username} ")
              List<UserInfo> queryByUsername( String username);
          
          

          可以通过输入' or username='来获取该表中所有人的信息

          测试代码:

          @Slf4j
          @SpringBootTest //启动Sring 容器
          class UserInfoMapperTest {
              @Autowired
              private UserInfoMapper userMapper;
          
              @Test
              void queryByUsername() {
                  userMapper.queryByUsername("lisi");
              }
          }
          

          运行结果:

          深入解析SpringBoot中#{}和${}的使用

          可以看出来, 查询的数据越界了接口的定义。所以⽤于查询的字段,尽量使⽤#{}预查询的⽅式

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

          4.数据库连接池

          4.1介绍

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

          深入解析SpringBoot中#{}和${}的使用

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

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

          优点:

          1.减少了⽹络开销

          2.资源重⽤

          3.提升了系统的性能

          4.2使⽤

          常⻅的数据库连接池:

          • C3P0
          • DBCP
          • Druid
          • Hikari

          ⽬前⽐较流⾏的是 Hikari, Druid

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

          深入解析SpringBoot中#{}和${}的使用

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

          Druid

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

          <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 依赖

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

          Druid连接池是阿⾥巴巴开www.devze.com源的数据库连接池项⽬

          功能强⼤,性能优秀,是Java语⾔最好的数据库连接池之⼀

          到此这篇关于深入解析SpringBoot中#{} 和 ${}的使用的文章就介绍到这了,更多相关SpringBoot中#{} 和 ${}内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

          0

          上一篇:

          下一篇:

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          最新开发

          开发排行榜