一.#{} 和 ${}
#{} 和 ${} 在MyBatis框架中都是用于SQL语句中参数替换的标记,但它们在使用方式和处理参数值上存在一些显著的区别。
#{}的作用:
- #{} 是MyBatis中用于预编译SQL语句的参数占位符。
- 它会将参数值放入一个预编译的PreparedStatement中,确保参数值被正确地转义和引用,从而防止SQL注入攻击。
特点:
- 预编译:数据库驱动在发送SQL语句和参数给DBMS之前对SQL语句进行编译,DBMS执行SQL时不需要重新编译,提高了执行效率。
- 安全性:由于使用了预编译机制,可以有效防止SQL注入攻击。
- 类型转换:自动进行Java类型和JDBC类型转换。
- 适用性:适用于大多数情况,特别是当参数值是从用户输入中获得时,因为它提供了更好的安全性和可靠性。
${}的作用:
- ${} 是MyBatis中用于字符串拼接的参数标记。
- 它会将参数值直接嵌入到SQL语句中,不进行预编译或转义。
特点:
- 字符串替换:纯粹的字符串替换,不进行预编译或转义,直接替换成变量的值。
- 安全性风险:由于不进行预编译或转义,如果不正确地处理参数值,可能会导致SQL注入攻击。
- 适用性:适用于一些特殊情况,如动态表名、列名或函数调用等,但需要谨慎使用,确保参数值的安全性。
1.1Interger类型的参数
@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"。
@Select("select username, `password`, age, gender, phone from userinfo where
id= ${id} ")
UserInfo queryById(Integer id);
5.再次查看输出日志信息
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.查看日志
@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 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要 经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了.
1.3.2SQL注⼊(面试点)
由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击。
sql 注⼊代码: ' or 1='1
@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.依然正确运行
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;
}
}
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;
}
}
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排序功能
比如我进行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.单元测试
#测试(失败)
#{} 会根据参数类型判断是否拼接引号 '' 如果参数类型为String, 就会加上 引号
$测试(成功)
使用场景:
注意: 此处 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);
总结 :
- #{}:预编译处理, ${}:字符直接替换
- #{} 可以防⽌SQL注⼊, ${}存在SQL注⼊的⻛险, 查询语句中, 可以使⽤ #{} ,推荐使⽤ #{}
- 但是⼀些场景, #{} 不能完成, ⽐如 排序功能, 表名, 字段名作为参数时, 这些情况需要使⽤${}
- 模糊查询虽然${}可以完成, 但因为存在SQL注⼊的问题,所以通常使⽤mysql内置函数concat来完成
二.数据库连接池
- 没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQL 语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源
-
使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把Connection归还给连接池.
- C3P0
- DBCP
- Druid
- Hikari
Hikari 是⽇语"光"的意思(ひかり), Hikari也是以追求性能极致为⽬标
2. Druid
如果我们想把默认的数据库连接池切换为Druid数据库连接池, 只需要引⼊相关依赖即可
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
运行结果: