Mybatis实战:#{} 和 ${}的使用区别和数据库连接池

一.#{} 和 ${}

#{} 和 ${} 在MyBatis框架中都是用于SQL语句中参数替换的标记,但它们在使用方式和处理参数值上存在一些显著的区别。

#{}的作用

  1. #{} 是MyBatis中用于预编译SQL语句的参数占位符。
  2. 它会将参数值放入一个预编译的PreparedStatement中,确保参数值被正确地转义和引用,从而防止SQL注入攻击。

特点: 

  • 预编译:数据库驱动在发送SQL语句和参数给DBMS之前对SQL语句进行编译,DBMS执行SQL时不需要重新编译,提高了执行效率。
  • 安全性:由于使用了预编译机制,可以有效防止SQL注入攻击。
  • 类型转换:自动进行Java类型和JDBC类型转换。
  • 适用性:适用于大多数情况,特别是当参数值是从用户输入中获得时,因为它提供了更好的安全性和可靠性。

${}的作用

  • ${} 是MyBatis中用于字符串拼接的参数标记。
  • 它会将参数值直接嵌入到SQL语句中,不进行预编译或转义。

特点

  • 字符串替换:纯粹的字符串替换,不进行预编译或转义,直接替换成变量的值。
  • 安全性风险:由于不进行预编译或转义,如果不正确地处理参数值,可能会导致SQL注入攻击。
  • 适用性:适用于一些特殊情况,如动态表名、列名或函数调用等,但需要谨慎使用,确保参数值的安全性。

 

1.1Interger类型的参数

1.先看Interger类型的参数
@Select("select username, `password`, age, gender, phone from userinfo where 
id= #{id} ")

UserInfo queryById(Integer id);

2.观察日志

 3.查看日志中的输出语句

select username, `password`, age, gender, phone from userinfo where id= ?

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

4.我们把 #{} 改成 ${} 再观察打印的⽇志:
@Select("select username, `password`, age, gender, phone from userinfo where 
id= ${id} ")

UserInfo queryById(Integer id);

5.再次查看输出日志信息

可以看到, 这次的参数是直接拼接在SQL语句中了。

1.2 String类型的参数

1.传入String类型的参数

@Select("select username, `password`, age, gender, phone from userinfo where 
username= #{name} ")

UserInfo queryByName(String name);

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

3.我们把 #{} 改成 ${} 再观察打印

@Select("select username, `password`, age, gender, phone from userinfo where 
username= ${name} ")

UserInfo queryByName(String name);

4.查看日志 

 

可以看到, 这次的参数依然是直接拼接在SQL语句中了, 但是字符串作为参数时, 需要添加引号 '' , 使⽤ ${} 不会拼接引号 '' , 导致程序报错.
5.修改代码(加上' ')
@Select("select username, `password`, age, gender, phone from userinfo where 
username= '${name}' ")
UserInfo queryByName(String name);

6.查看日志


 我们在IDEA进行代码示范

 1.声明方法中区别

@Select("select * from userinfo where username = #{userName}")
    UserInfo getUserByName(String userName);

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

2.分别进行单元测试

#运行成功

 $运行失败

为什么呢?

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

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

 

运行成功



1.3#{} 和 ${}区别 

1.3.1.性能不同

#{} 和 ${} 的区别就是预编译SQL和即时SQL 的区别.。
当客⼾发送⼀条SQL语句给服务器后, ⼤致流程如下:
  1. 解析语法和语义, 校验SQL语句是否正确
  2. 优化SQL语句, 制定执⾏计划
  3. 执⾏并返回结果
⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL)。
绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要 经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了.
总之:#预编译SQl的性能更高,是提前占座的行为,$即时SQL是直接拼接的行为。 
预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译。
(只是输⼊的参数不同), 省去了解析优化等过程, 以此来提⾼效率.

1.3.2SQL注⼊(面试点)

SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的⽅法。
由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击。

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

1.先来看看SQL注⼊的例⼦:
@Select("select username, `password`, age, gender, phone from userinfo where 
username= '${name}' ")
List<UserInfo> queryByName(String name);
测试代码:
@Test
void queryByName() {
 List<UserInfo> userInfos = userInfoMapper.queryByName("admin");
 System.out.println(userInfos);
}

2.正常访问情况

3.SQL注⼊场景:

@Test
void queryByName() {
List<UserInfo> userInfos = userInfoMapper.queryByNam
 System.out.println(userInfos);
}

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

4.依然正确运行

控制层: UserController
import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public boolean login(String name, String password) {
        UserInfo userInfo = userService.queryUserByPassword(name, password);
        if (userInfo != null) {
            return true;
        }
        return false;
    }
}
业务层: UserService

import com.example.demo.mapper.UserInfoMapper;
import com.example.demo.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    public UserInfo queryUserByPassword(String name, String password) {
        List<UserInfo> userInfos = userInfoMapper.queryUserByPassword(name,
                password);
        if (userInfos != null && userInfos.size() > 0) {
            return userInfos.get(0);
        }
        return null;
    }
}
数据层: UserInfoMapper
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserInfoMapper {
    @Select("select username, `password`, age, gender, phone from userinfo 
            where username= '${name}' and password='${password}' ")
            List<UserInfo> queryUserByPassword(String name, String password);
}
程序正常运⾏

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

password 设置为 ' or 1='1


在IDEA运行示范

1.方法声明

 @Select("select * from userinfo where username = #{userName}")
    UserInfo selectUserByName(String userName);

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

2.单元测试

 

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

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

#运行:

$运行:

1.3.3排序功能

从上⾯的例⼦中, 可以得出结论: ${} 会有SQL注⼊的⻛险, 所以我们尽量使⽤#{}完成查询
既然如此, 是不是 ${} 就没有存在的必要性了呢?
当然不是.

 比如我进行id升序/降序时。

1.方法声明

使用#

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

使用$

 

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

 2.单元测试

#测试(失败)

可以发现, 当使⽤ #{sort} 查询时, asc 前后⾃动给加了引号, 导致 sql 错误
#{} 会根据参数类型判断是否拼接引号 '' 如果参数类型为String, 就会加上 引号

 $测试(成功)

使用场景:

使⽤ ${sort} 可以实现排序查询, ⽽使⽤ #{sort} 就不能实现排序查询了.
注意: 此处 sort 参数为String类型, 但是SQL语句中, 排序规则是不需要加引号 '' 的, 所以此时的${sort} 也不加引号

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

1.3.4like 查询

LIKE进行模糊查询时,$能直接查询.

like 直接使⽤ #{} 报错

@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from userinfo where username like '%#{key}%' ")
List<UserInfo> queryAllUserByLike(String key);

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

实现代码如下: 

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

总结

  1. #{}:预编译处理, ${}:字符直接替换
  2. #{} 可以防⽌SQL注⼊, ${}存在SQL注⼊的⻛险, 查询语句中, 可以使⽤ #{} ,推荐使⽤ #{}
  3. 但是⼀些场景, #{} 不能完成, ⽐如 排序功能, 表名, 字段名作为参数时, 这些情况需要使⽤${}
  4. 模糊查询虽然${}可以完成, 但因为存在SQL注⼊的问题,所以通常使⽤mysql内置函数concat来完成

二.数据库连接池

在上⾯Mybatis的讲解中, 我们使⽤了数据库连接池技术, 避免频繁的创建连接, 销毁连接
下⾯我们来了解下数据库连接池。
数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,⽽不是再重新建⽴⼀个

 

  • 没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQL 语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源
  • 使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把Connection归还给连接池.
优点 :
1. 减少了⽹络开销
2. 资源重⽤
3. 提升了系统的性能
常⻅的数据库连接池:
  • C3P0
  • DBCP
  • Druid
  • Hikari
⽬前⽐较流⾏的是 Hikari, Druid
1.Hikari : SpringBoot默认使⽤的数据库连接池

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

2. Druid

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

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

 运行结果:

Druid连接池是阿⾥巴巴开源的数据库连接池项⽬
功能强⼤,性能优秀,是Java语⾔最好的数据库连接池之⼀。
  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值