深入解析SpringBoot中#{}和${}的使用
1.#{} 和 ${}的使用
1.1數(shù)據(jù)準(zhǔn)備
1.1.1.MySQL數(shù)據(jù)準(zhǔn)備
(1)創(chuàng)建數(shù)據(jù)庫:
CREATE DATABASE mybatis_study DEFAULT CHARACTER SET utf8mb4;
(2)使用數(shù)據(jù)庫
-- 使?數(shù)據(jù)數(shù)據(jù) USE mybatis_study;
(3)創(chuàng)建用戶表
-- 創(chuàng)建表[??表] 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-默認(rèn)', `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 ) VALUES ( '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.創(chuàng)建對應(yīng)的實體類
實體類的屬性名與表中的字段名??對應(yīng)
@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;
}

注意:在實際開發(fā)中不管什么實體類都要設(shè)置刪除標(biāo)志、創(chuàng)建時間、修改時間
1.2 獲取Integer類型
1.2.1 #{}
Mapper接口:
@Mapper
public interface UserInfoMapper {
// 獲取參數(shù)中的 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());
}
}
運行結(jié)果:

通過日志可以發(fā)現(xiàn),?進(jìn)行占位,傳的參數(shù)進(jìn)行綁定到占位符。
1.2.2 ${}
Mapper接口:
@Mapper
public interface UserInfoMapper {
// 獲取參數(shù)中的 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());
}
}
運行結(jié)果:

通過日志可以發(fā)現(xiàn),SQL命令是完整的,因為,該方法是把字符串拼接在一起執(zhí)行的。
1.3 獲取String類型
1.3.1 #{}
Mapper接口:
@Mapper
public interface UserInfoMapper {
// 獲取參數(shù)中的 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");
}
}
運行結(jié)果:

通過日志可以發(fā)現(xiàn),?進(jìn)行占位,傳的參數(shù)進(jìn)行綁定到占位符。
1.3.2 ${}
Mapper接口:
@Mapper
public interface UserInfoMapper {
// 獲取參數(shù)中的 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");
}
}
運行結(jié)果:報錯


從SQL語句中明顯的看到WHERE username 后面的字符串沒有引號,導(dǎo)致報錯。
因為,${}直接把字符內(nèi)容直接放進(jìn)SQL語句中而沒有加單引號。
修改后的mapper接口:
@Mapper
public interface UserInfoMapper {
// 獲取參數(shù)中的 username
@Select("select * from user_info where username = '${username}' ")
UserInfo queryByUsername( String username);

2.#{} 和 ${}的區(qū)別
2.1 預(yù)編譯SQL和即時SQL的執(zhí)行過程
2.1.1 預(yù)編譯SQL執(zhí)行過程
#{}是預(yù)編譯SQL。
第一步:數(shù)據(jù)庫客戶端(如 JDBC 驅(qū)動)將 SQL 模板發(fā)送到數(shù)據(jù)庫服務(wù)器。
// SQL模版
PreparedStatement pstmt = connection.prepareStatement("SELECT * FROM user WHERE id = ? AND name = ?");
第二步:SQL 預(yù)編譯
(1)數(shù)據(jù)庫解析 SQL 模板,生成執(zhí)行計劃(包括語法檢查、語義分析、優(yōu)化等),并緩存該計劃。
(2)此時,占位符 ? 的具體值尚未填充,數(shù)據(jù)庫只處理 SQL 的結(jié)構(gòu)。
第三步:客戶端通過 PreparedStatement 的方法設(shè)置參數(shù)值
//參數(shù)值以二進(jìn)制形式單獨發(fā)送到數(shù)據(jù)庫,不會直接拼接到 SQL 中,避免了 SQL 注入 pstmt.setInt(1, 123); // 綁定 id pstmt.setString(2, "Alice"); // 綁定 name
第四步:SQL 執(zhí)行
(1)數(shù)據(jù)庫使用緩存的執(zhí)行計劃,將綁定參數(shù)代入執(zhí)行計劃,直接運行查詢或更新操作。
(2)如果相同的 SQL 模板再次執(zhí)行(僅參數(shù)不同),數(shù)據(jù)庫可復(fù)用緩存的執(zhí)行計劃,減少編譯開銷。
2.1.2 即時QL執(zhí)行過程
${}是即時SQL。
第一步:SQL 語句拼接
SELECT * FROM user ORDER BY ${columnName}
//如果 columnName = "age"
//生成
SELECT * FROM user ORDER BY age; DROP TABLE user;
第二步:SQL 發(fā)送到數(shù)據(jù)庫
客戶端將拼接好的完整 SQL 字符串通過 Statement 或類似接口發(fā)送到數(shù)據(jù)庫
Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql);
第三步:SQL解析與編譯
語法解析:檢查 SQL 語句的語法是否正確。
語義分析:驗證表名、列名等是否存在,權(quán)限是否足夠。
優(yōu)化:生成執(zhí)行計劃,選擇最優(yōu)的查詢路徑。
第四步:SQL執(zhí)行
數(shù)據(jù)庫根據(jù)生成的執(zhí)行計劃執(zhí)行 SQL,完成查詢或更新操作。
2.2性能比較
預(yù)編譯SQL(#{})性能更高:
絕?多數(shù)情況下, 某?條 SQL 語句可能會被反復(fù)調(diào)?執(zhí)?, 或者每次執(zhí)?的時候只有個別的值不同(?如 select 的 where ?句值不同, update 的 set ?句值不同, insert 的 values 值不同). 如果每次都需要經(jīng)過上?的語法解析, SQL優(yōu)化、SQL編譯等,則效率就明顯不?了

預(yù)編譯SQL,編譯?次之后會將編譯后的SQL語句緩存起來,后?再次執(zhí)?這條語句時,不會再次編譯 (只是輸?的參數(shù)不同), 省去了解析優(yōu)化等過程, 以此來提?效率
預(yù)編譯SQL(#{})更安全(防?SQL注?):
由于沒有對??輸?進(jìn)?充分檢查,?SQL?是拼接?成,在??輸?參數(shù)時,在參數(shù)中添加?些 SQL關(guān)鍵字,達(dá)到改變SQL運?結(jié)果的?的,也可以完成惡意攻擊。
2.3 排序舉例
排序需要用到SQL的關(guān)鍵字asc 和desc,把該兩個關(guān)鍵字設(shè)置為參數(shù)時需要用到${},因為#{}會把asc 和desc認(rèn)為是字符串
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");
}
}
運行結(jié)果:

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");
}
}
運行結(jié)果:

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);
}
}
運行結(jié)果:

把 #{} 改成 可以正確查出來 , 但是 {} 可以正確查出來, 但是可以正確查出來,但是{}存在SQL注?的問題, 所以不能直接使? ${}.解決辦法: 使? mysql 的內(nèi)置函數(shù) concat() 來處理,實現(xiàn)代碼如下:
修改后的Mapper接口:
@Mapper
public interface UserInfoMapper {
@Select("select * from user_info where username like concat('%',#{s},'%') ")
List<UserInfo> queryLike(String s);
運行結(jié)果:

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);
}
}
運行結(jié)果:

3.什么是SQL注入?
SQL注?:是通過操作輸?的數(shù)據(jù)來修改事先定義好的SQL語句,以達(dá)到執(zhí)?代碼對服務(wù)器進(jìn)?攻擊的?法。
舉例:
下面定義的接口是由username得到該username的信息
Mapper接口:
@Mapper
public interface UserInfoMapper {
// 獲取參數(shù)中的 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");
}
}
運行結(jié)果:

可以看出來, 查詢的數(shù)據(jù)越界了接口的定義。所以?于查詢的字段,盡量使?#{}預(yù)查詢的?式
SQL注?是?種?常常?的數(shù)據(jù)庫攻擊?段, SQL注?漏洞也是?絡(luò)世界中最普遍的漏洞之?。
4.數(shù)據(jù)庫連接池
4.1介紹
數(shù)據(jù)庫連接池負(fù)責(zé)分配、管理和釋放數(shù)據(jù)庫連接,它允許應(yīng)?程序重復(fù)使??個現(xiàn)有的數(shù)據(jù)庫連接,?不是再重新建??個.

沒有使?數(shù)據(jù)庫連接池的情況: 每次執(zhí)?SQL語句, 要先創(chuàng)建?個新的連接對象, 然后執(zhí)?SQL語句, SQL語句執(zhí)?完, 再關(guān)閉連接對象釋放資源. 這種重復(fù)的創(chuàng)建連接, 銷毀連接?較消耗資源
使?數(shù)據(jù)庫連接池的情況: 程序啟動時, 會在數(shù)據(jù)庫連接池中創(chuàng)建?定數(shù)量的Connection對象, 當(dāng)客?請求數(shù)據(jù)庫連接池, 會從數(shù)據(jù)庫連接池中獲取Connection對象, 然后執(zhí)?SQL, SQL語句執(zhí)?完, 再把 Connection歸還給連接池.
優(yōu)點:
1.減少了?絡(luò)開銷
2.資源重?
3.提升了系統(tǒng)的性能
4.2使?
常?的數(shù)據(jù)庫連接池:
- C3P0
- DBCP
- Druid
- Hikari
?前?較流?的是 Hikari, Druid
Hikari : SpringBoot默認(rèn)使?的數(shù)據(jù)庫連接池

Hikari 是?語"光"的意思(ひかり), Hikari也是以追求性能極致為?標(biāo)
Druid
如果我們想把默認(rèn)的數(shù)據(jù)庫連接池切換為Druid數(shù)據(jù)庫連接池, 只需要引?相關(guān)依賴即可
<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連接池是阿?巴巴開源的數(shù)據(jù)庫連接池項?
功能強?,性能優(yōu)秀,是Java語?最好的數(shù)據(jù)庫連接池之?
到此這篇關(guān)于深入解析SpringBoot中#{} 和 ${}的使用的文章就介紹到這了,更多相關(guān)SpringBoot中#{} 和 ${}內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中int[]與ArrayList<>數(shù)組轉(zhuǎn)換方法詳解
這篇文章主要介紹了在Java中將int[]和ArrayList進(jìn)行雙向轉(zhuǎn)換的方法,包括手動遍歷和使用Java8?Stream?API兩種方式,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02
Java Object類詳解_動力節(jié)點Java學(xué)院整理
Java作為一個龐大的知識體系,涉及到的知識點繁多,本文將從Java中最基本的類java.lang.Object開始談起,對java object類相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2017-04-04
IntelliJ IDEA Java項目手動添加依賴 jar 包的方法(圖解)
這篇文章主要介紹了IntelliJ IDEA Java項目手動添加依賴 jar 包,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04
Java如何基于EasyExcel實現(xiàn)導(dǎo)入數(shù)據(jù)校驗并生成錯誤信息Excel
這篇文章主要介紹了Java如何基于EasyExcel實現(xiàn)導(dǎo)入數(shù)據(jù)校驗并生成錯誤信息Excel,為了優(yōu)化項目中的文件導(dǎo)入功能,考慮構(gòu)建一個基于EasyExcel的通用Excel導(dǎo)入框架,主要解決導(dǎo)入數(shù)據(jù)的校驗問題,避免業(yè)務(wù)代碼中堆積大量校驗邏輯,需要的朋友可以參考下2024-09-09

