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 )
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.創建對應的實體類
實體類的屬性名與表中的字段名??對應
@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;}
注意:在實際開發中不管什么實體類都要設置刪除標志、創建時間、修改時間
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 {@Testvoid queryById() {UserInfo result = userInfoMapper.queryById(8);log.info(result.toString());}
}
運行結果:
通過日志可以發現,?
進行占位,傳的參數進行綁定到占位符。
1.2.2 ${}
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 {@Testvoid queryById() {UserInfo result = userInfoMapper.queryById(8);log.info(result.toString());}
}
運行結果:
通過日志可以發現,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 {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryByUsername() {userMapper.queryByUsername("lisi");}
}
運行結果:
通過日志可以發現,?
進行占位,傳的參數進行綁定到占位符。
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 {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryByUsername() {userMapper.queryByUsername("lisi");}
}
運行結果:報錯
從SQL語句中明顯的看到WHERE username 后面的字符串沒有引號,導致報錯。
因為,${}直接把字符內容直接放進SQL語句中而沒有加單引號。
修改后的mapper接口:
@Mapper
public interface UserInfoMapper {// 獲取參數中的 username@Select("select * from user_info where username = '${username}' ")UserInfo queryByUsername( String username);
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編譯等,則效率就明顯不?了
預編譯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 {@Autowiredprivate UserInfoMapper userMapper;@Testvoid findAll() {userMapper.findAll("asc");}
}
運行結果:
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 {@Autowiredprivate UserInfoMapper userMapper;@Testvoid findAll() {userMapper.findAll("asc");}
}
運行結果:
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 {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryLike() {String s = "6";userMapper.queryLike(s);}
}
運行結果:
把 #{} 改成 可以正確查出來 , 但是 {} 可以正確查出來, 但是 可以正確查出來,但是{}存在SQL注?的問題, 所以不能直接使? ${}.解決辦法: 使? mysql 的內置函數 concat() 來處理,實現代碼如下:
修改后的Mapper接口:
@Mapper
public interface UserInfoMapper {@Select("select * from user_info where username like concat('%',#{s},'%') ")List<UserInfo> queryLike(String s);
運行結果:
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 {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryLike() {String s = "6";userMapper.queryLike(s);}
}
運行結果:
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 {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryByUsername() {userMapper.queryByUsername("lisi");}
}
運行結果:
可以看出來, 查詢的數據越界了接口的定義。所以?于查詢的字段,盡量使?#{}
預查詢的?式
SQL注?是?種?常常?的數據庫攻擊?段, SQL注?漏洞也是?絡世界中最普遍的漏洞之?。
4.數據庫連接池
4.1介紹
數據庫連接池負責分配、管理和釋放數據庫連接,它允許應?程序重復使??個現有的數據庫連接,?不是再重新建??個.
沒有使?數據庫連接池的情況: 每次執?SQL語句, 要先創建?個新的連接對象, 然后執?SQL語句, SQL語句執?完, 再關閉連接對象釋放資源. 這種重復的創建連接, 銷毀連接?較消耗資源
使?數據庫連接池的情況: 程序啟動時, 會在數據庫連接池中創建?定數量的Connection對象, 當客?請求數據庫連接池, 會從數據庫連接池中獲取Connection對象, 然后執?SQL, SQL語句執?完, 再把 Connection歸還給連接池.
優點:
1.減少了?絡開銷
2.資源重?
3.提升了系統的性能
4.26.2使?
常?的數據庫連接池:
?C3P0
?DBCP
?Druid
?Hikari
?前?較流?的是 Hikari, Druid
Hikari : SpringBoot默認使?的數據庫連接池
Hikari 是?語"光"的意思(ひかり), Hikari也是以追求性能極致為?標
2.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連接池是阿?巴巴開源的數據庫連接池項?
功能強?,性能優秀,是Java語?最好的數據庫連接池之?