Spring Boot單元測試與Mybatis單表增刪改查

目錄

1. Spring Boot單元測試

1.1 什么是單元測試?

1.2 單元測試有哪些好處?

1.3 Spring Boot 單元測試使用

單元測試的實現步驟

1. 生成單元測試類

2. 添加單元測試代碼

簡單的斷言說明

2. Mybatis 單表增刪改查

2.1 單表查詢

2.2 參數占位符 ${} 和 #{}

${} 和 #{}的區別

1. 作用不同

2. 安全性: ${} 的SQL注入問題

${} 應用場景

2.3 單表修改操作

2.4 單表刪除操作

2.5 單表添加操作

添加返回影響行數

添加返回影響行數和id


1. Spring Boot單元測試

1.1 什么是單元測試?

單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證的過程就叫單元測試。

單元測試是開發者編寫的一小段代碼,用于檢驗被測代碼的一個很小的、很明確的(代碼)功能是否正確。執行單元測試就是為了證明某段代碼的執行結果是否符合我們的預期。如果測試結果符合我們的預期,稱之為測試通過,否則就是測試未通過 (或者叫測試失敗)

1.2 單元測試有哪些好處?

  1. 可以非常簡單、直觀、快速的測試某一個功能是否正確。
  2. 使用單元測試可以幫我們在打包的時候,發現一些問題,因為在打包之前,所有的單元測試必須通過, 否則不能打包成功。

  1. 使用單元測試,在測試功能的時候,可以不污染連接的數據庫,也就是可以不對數據庫進行任何改變的情況下,測試功能。

1.3 Spring Boot 單元測試使用

Spring Boot 項目創建時會默認單元測試框架 spring-boot-starter-test,而這個單元測試框架主要是依靠另個著名的測試框架 JUnit 實現的,打開 pom.xml 就可以看到,以下信息是 Spring Boot 項目創建是自動添加的:

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>

單元測試的實現步驟

1. 生成單元測試類

最終生成的代碼:

package com.example.demo.mapper;import org.junit.jupiter.api.Test;class UserMapperTest {@Testvoid getAll() {}
}

這個時候,此方法是不能調用到任何單元測試的方法的,此類只生成了單元測試的框架類,具體的業務代碼要自己填充。

2. 添加單元測試代碼

  1. 在測試類上添加Spring Boot 框架測試注解: @SpringBootTest
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest // 表示當前單元測試的類是運行在 Spring Boot 環境中的(一定不能省略)
class UserMapperTest {// ..
}
  1. 添加單元測試業務邏輯
    @Autowiredprivate UserMapper userMapper;@Testvoid getAll() {List<UserEntity> list = userMapper.getAll();System.out.println(list.size());}

簡單的斷言說明

方法

說明

assertEquals

判斷兩個對象或兩個原始類型是否相等

assertNotEquals

判斷兩個對象或兩個原始類型是否不相等

assertSame

判斷兩個對象引用是否指向同一個對象

assertNotSame

判斷兩個對象引用是否指向不同的對象

assertTrue

判斷給定的布爾值是否為 true

assertFalse

判斷給定的布爾值是否為 false

assertNull

判斷給定的對象引用是否為 null

assertNotNull

判斷給定的對象引用是否不為 null

斷言: 如果斷言失敗,則后面的代碼都不會執行.

2. Mybatis 單表增刪改查

2.1 單表查詢

下面我們來實現一下根據用戶id查詢用戶信息的功能.

在UserMapper類中添加接口:

// 根據 id 查詢用戶對象
UserEntity getUserById(@Param("uid") Integer id);    // @Param是給形參起名
    <select id="getUserById" resultType="com.example.demo.entity.UserEntity">select * from userinfo where id=${uid}</select>

注: 上面 ${uid} 中的uid對應@Param的uid

使用單元測試的方式去調用它.

    @Testvoid getUserById() {UserEntity user = userMapper.getUserById(2);System.out.println(user);}

那么我們的預期結果是能夠打印出數據庫中"zhangsan"的數據:

執行結果:

可以看到, 預期結果成功執行了.


2.2 參數占位符 ${} 和 #{}

Mybatis獲取動態參數有兩種實現:

  1. ${paramName} -> 直接替換
  2. #{paramName} -> 占位符模式

驗證直接替換:

在Spring配置文件中有一個配置, 只需要把這個配置給它配置之后, 那么Mybatis的執行SQL(Mybatis底層是基于JDBC), 最終會生成JDBC的執行SQL和它的執行模式, 那么我們就可以把這個執行的SQL語句打印出來.

需要配置兩個配置項, 一個是日志打印的實現, 另一個是設置日志打印的級別 (SQL的打印默認輸出的級別的debug級別, 但日志默認級別的info, 默認info要大于debug, 所以并不會顯示, 所以要去進行日志級別的設置).

# 打印 Mybatis 執行 SQL
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example.demo=debug

配置完成之后再次運行剛才的測試代碼, 可以看到SQL的相關信息都被打印了出來, 所以可以知道$是直接替換的模式.


將上文的 $ 換成 # , 會看到的是, SQL語句的id變成了?, 也就是變成了占位符的模式.

而占位符的模式是預執行的, 而預執行是比較安全的, 具體來說預執行可以有效的排除SQL注入的問題.


${} 和 #{}的區別

1. 作用不同

${} 所見即所得, 直接替換, #{} 是預處理的.

在進行使用的時候, 如果傳的是int這種簡單數據類型的時候, 兩者是沒有區別的, 但是如果更復雜點的使用varchar, 就會有安全的問題出現.

在UserMapper類中添加接口:

// 根據名稱查詢用戶對象
UserEntity getUserByUserName(@Param("username") String username);
    <select id="getUserByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username=#{username}</select>

測試:

    @Testvoid getUserByUserName() {UserEntity user = userMapper.getUserByUserName("zhangsan");System.out.println(user);}

測試結果沒有問題, 那么再將#換成$.

    <select id="getUserByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username=${username}</select>

這時程序報錯沒有找到'zhangsan', 并且我們看到SQL語句變成了.

在數據庫客戶端中執行圖中SQL語句也是會報出和上圖一樣的錯.

那么這里的原因就在于剛才我們的代碼中, ${}是直接替換的模式, 當加上單引號后再次運行就正常運行了.

    <select id="getUserByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username='${username}'</select>

但是加單引號只能保證不報錯, 但是不能保證安全性問題.

所以當我們遇到是int類型的時候, ${} 和 #{} 在執行上沒有什么區別, 當出現字符型的時候${} 就有可能會出現問題.

2. 安全性: ${} 的SQL注入問題

${} 的安全性問題出現在登錄, 接下來我們以登錄為例看一下什么是SQL注入.

首先SQL注入是 用戶用了并不是一個真實的用戶名和密碼, 但是卻查詢到了數據. 我們通過代碼說明.

// 登錄方法
UserEntity login(UserEntity user);
    <select id="login" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username='${username}' and password='${password}'</select>
注: 當Interface傳的是對象時, xml中獲取屬性時, 也就是{}里面直接寫對象的屬性名即可, 無需"對象.屬性", 這是Mybatis的約定

為了演示效果, 我們在數據庫中刪掉id=2的zhangsan.

先來看正常的用戶行為.

    @Testvoid login() {String username = "admin";String password = "admin";UserEntity inputUser = new UserEntity();inputUser.setUsername(username);inputUser.setPassword(password);UserEntity user = userMapper.login(inputUser);System.out.println(user);}

可以看到, 找到了相關信息.

當輸入錯誤密碼時, 即:

String password = "admin2";

可以看到, 結果是null, 以上都是正常的行為.

接下來我們來看一個特殊的, 不正常的行為, 輸入如下密碼:

String password = "' or 1='1";

此時我們可以發現, 輸入了一個不正常的密碼, 卻把admin查出來了, 這就是SQL注入, 對于程序來說是非常危險的.

那么我們可以看到這里的SQL語句是

select * from userinfo where username='admin' and password='' or 1='1'

所以這便是這里出錯的原因, 它把字符串誤解析成SQL指令去執行了, 使邏輯運行結果與預期不同, 但卻正常執行.

當把 ${} 改為 #{} 后, 再次測試, 可以看到結果是null.

由上可見, 使用 ${} 是會安全性問題的, 而使用 #{} 就不會出現安全性問題, 原因在于 #{} 使用了JDBC的占位符的模式, 那么這種模式是預執行的, 是直接當成字符串來執行的.


${} 應用場景

${} 雖然在查詢的時候會有安全性問題, 但是它也有具體的應用場景, 比如以下場景:

在淘寶中有時候需要按照某種屬性進行排序, 比如價格低到高或者高到低, 這時SQL傳遞的就是order by后的規則asc或desc.

使用 ${sort} 可以實現排序查詢,而使用 #{sort} 就不能實現排序查詢了,因為當使用 #{sort} 查詢時如果傳遞的值為 String 則會加單引號,就會導致 sql 錯誤。


那么對于我們之前的程序, 我們也可以進行類似的應用.

    List<UserEntity> getAllByIdOrder(@Param("ord") String order);
   <select id="getAllByIdOrder" resultType="com.example.demo.entity.UserEntity">select * from userinfo order by id ${ord}</select>
    @Testvoid getAllByIdOrder() {List<UserEntity> list = userMapper.getAllByIdOrder("desc");System.out.println(list.size());}

這時使用 #{} 就會報錯了.

   <select id="getAllByIdOrder" resultType="com.example.demo.entity.UserEntity">select * from userinfo order by id #{ord}</select>

既然 ${} 有用, 但是它也極其的危險, 在使用的時候要注意, 要保證它的值必須得被枚舉. 所以盡量少用.


2.3 單表修改操作

比如需要修改用戶密碼.

首先, 在Interface聲明方法,

    // 修改密碼int updatePassword(@Param("id") Integer id,@Param("password") String password,@Param("newPassword") String newPassword);

然后在xml中實現方法, 注意修改操作是使用<update>標簽.

    <update id="updatePassword">update userinfo set password=#{newPassword}where id=#{id} and password=#{password}</update>
    @Testvoid updatePassword() {int result = userMapper.updatePassword(1, "admin", "123456");System.out.println("修改: " + result);}

運行前后查詢數據庫,

可以看到, password已經成功修改了.

當再次修改newPassword參數的代碼時, 即:

int result = userMapper.updatePassword(1, "admin", "666666");

這里說明, 注入參數有問題, 代碼沒問題.

不過, 這里的測試是把原本數據庫污染了, 違背了單元測試的初衷, 那么要想不污染數據庫, 需要在測試類前加上@Transactional事務注解.

    @Transactional  // 事務@Testvoid updatePassword() {int result = userMapper.updatePassword(1, "123456", "666666");System.out.println("修改: " + result);}

當加上注解之后, 測試的代碼可以正常執行, 但是就不會污染數據庫了.

看到打印了"修改: 1", 就說明成功修改了.

在代碼執行的時候不會進行干擾的, 只不過在執行之初, 會開啟一個事務, 等全部代碼執行完了, 比如這里的"修改: x"已經正常打印了, 然后在它執行完會進行rollback回滾操作, 所以就不會污染數據庫了.

驗證數據庫是否污染:

2.4 單表刪除操作

// 刪除用戶
int delById(@Param("id") Integer id);
    <delete id="delById">delete from userinfo where id=#{id}</delete>
    @Transactional@Testvoid delById() {int id = 1;int result = userMapper.delById(id);System.out.println("刪除結果: " + result);}

2.5 單表添加操作

添加返回影響行數

// 添加用戶
int addUser(UserEntity user);
    <insert id="addUser">insert into userinfo(username,password) values(#{username},#{password})</insert>
    @Testvoid addUser() {UserEntity user = new UserEntity();user.setUsername("lisi");user.setPassword("123456");int result = userMapper.addUser(user);System.out.println("添加: " + result);}


添加返回影響行數和id

int addUserGetId(UserEntity user);
    <insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id">insert into userinfo(username,password) values(#{username},#{password})</insert>
    @Testvoid addUserGetId() {UserEntity user = new UserEntity();user.setUsername("lili");user.setPassword("123456");int result = userMapper.addUserGetId(user);System.out.println("添加結果: " + result);System.out.println("ID: " + user.getId());}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/34830.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/34830.shtml
英文地址,請注明出處:http://en.pswp.cn/news/34830.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

學點Selenium玩點新鮮~,讓分布式測試有更多玩法

前 言 我們都知道 Selenium 是一款在 Web 應用測試領域使用的自動化測試工具&#xff0c;而 Selenium Grid 是 Selenium 中的一大組件&#xff0c;通過它能夠實現分布式測試&#xff0c;能夠幫助團隊簡單快速在不同的環境中測試他們的 Web 應用。 分布式執行測試其實并不是一…

opencv,opengl,osg,vulkan,webgL,opencL,cuda

OpenCV OpenCV是一個基于BSD許可&#xff08;開源&#xff09;發行的跨平臺計算機視覺和機器學習軟件庫&#xff0c;可以運行在Linux、Windows、Android和Mac OS操作系統上。 它輕量級而且高效——由一系列 C 函數和少量 C 類構成&#xff0c;同時提供了Python、Ruby、MATLAB等…

安卓java A應用切換到B應用,來回切換不執行OnCreate

需求&#xff1a;安卓java如何做到A應用切換到B應用&#xff0c;如果B應用沒啟動就啟動&#xff0c;如果B應用已經啟動就僅僅切換到B應用。B應用再切換回A應用&#xff0c;不要重復執行OnCreate! 在 A 應用中的&#xff1a; 在 A 應用中&#xff0c;如果你希望在切換回 B 應用…

小米平板6Max14即將發布:自研G1 電池管理芯片,支持33W反向快充

明天晚上7點&#xff08;8 月 14 日&#xff09;&#xff0c;雷軍將進行年度演講&#xff0c;重點探討“成長”主題。與此同時&#xff0c;小米將推出一系列全新產品&#xff0c;其中包括備受矚目的小米MIX Fold 3折疊屏手機和小米平板6 Max 14。近期&#xff0c;小米官方一直在…

分布式搜索ElasticSearch-ES(一)

一、ElasticSearch介紹 ES是一款非常強大的開源搜索引擎&#xff0c;可以幫我們從海量的數據中快速找到我們需要的內容。 ElasticSearch結合kibana、Logstash、Beats&#xff0c;也就是elastic stack(ELK)&#xff0c;被廣泛運用在日志數據分析&#xff0c;實時監控等領域。 …

accumulate函數的簡單應用

accumulate函數是C numeric庫中的一個函數&#xff0c;主要用來對指定范圍內元素求和&#xff0c;但也自行指定一些其他操作&#xff0c;如范圍內所有元素相乘、相除等。 使用前需要引用頭文件&#xff1a; #include <numeric>函數共有四個參數&#xff0c;其中前三個為…

Ajax 筆記(二)—— Ajax 案例

筆記目錄 2. Ajax 綜合案例2.1 案例一-圖書管理2.1.1 渲染列表2.1.2 新增圖書2.1.3 刪除圖書2.1.4 編輯圖書 2.2 案例二-背景圖的上傳和更換2.2.1 上傳2.2.2 更換 2.3 案例三-個人信息設置2.3.1 信息渲染2.3.2 頭像修改2.2.3 信息修改2.3.4 提示框 Ajax 筆記&#xff1a; Ajax…

React Native 列表組件基礎知識

ScrollView 組件 ScrollView組件是一個容器滾動組件&#xff0c;當容器超出指定寬高時就可以進行滾動交互。 ScrollView組件是一次性渲染所有的 React 子組件&#xff0c;這在性能上是比較差的&#xff0c;所以不建議當列表特別長的時候使用此組件。 接下來列舉幾個常用的一…

HTML(JavaEE初級系列12)

目錄 前言&#xff1a; 1.HTML結構 1.1認識HTML標簽 1.2HTML文件基本結構 1.3標簽層次結構 1.4快速生成代碼框架 2.HTML常見標簽 2.1注釋標簽 2.2標題標簽&#xff1a;h1-h6 2.3段落標簽&#xff1a;p 2.4換行標簽&#xff1a; br 2.5格式化標簽 2.6圖片標簽&#…

【數據結構?堆】經典問題:k路歸并

題目描述 k路歸并問題&#xff1a;   把k個有序表合并成一個有序表。&#xff08; k < 10^4 &#xff09; 輸入輸出格式 輸入格式&#xff1a; 輸入數據共有 2*k1 行。   第一行&#xff0c;一個整數k&#xff08; k < 10^4 &#xff09;&#xff0c;表示有k個有序…

【詳細教程】學會使用Python隧道代理

作為一名專業爬蟲程序猿&#xff0c;我深知在進行網絡數據采集時&#xff0c;可能會面臨網絡封鎖、隱私泄露等問題。今天&#xff0c;我將與大家分享如何學會使用Python隧道代理&#xff0c;幫助我們自由訪問受限網站&#xff0c;同時保護了解探索Python隧道代理&#xff01; …

3.1 Spring MVC概述

1. MVC概念 MVC是一種編程思想&#xff0c;它將應用分為模型&#xff08;Model&#xff09;、視圖&#xff08;View&#xff09;、控制器&#xff08;Controller&#xff09;三個層次&#xff0c;這三部分以最低的耦合進行協同工作&#xff0c;從而提高應用的可擴展性及可維護…

C++ opencv:視頻讀取、變換顏色風格、保存

1. 相關知識點 VideoCapture&#xff1b; applyColorMap&#xff1b; VideoWriter&#xff1b; 2. 代碼 編寫代碼main.cpp: #include<iostream> #include "opencv2/opencv.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/highgu…

解開謎團:為什么紅黑樹勝過AVL樹?

為什么紅黑樹勝過AVL樹 博主簡介一、引言1.1、紅黑樹和AVL樹簡介1.2、紅黑樹在某些方面優于AVL樹 二、紅黑樹和AVL樹的基本原理2.1、紅黑樹的定義和性質2.2、AVL樹的定義和性質2.3、對比兩種樹結構的特點 三、插入和刪除操作的復雜性比較3.1、紅黑樹的插入操作和平衡性維護3.2、…

冪次方(c++題解)

題目描述 任何一個正整數都可以用 22 的冪次方表示。例如 1372^72^32^0。 同時約定方次用括號來表示&#xff0c;即 a^b 可表示為 a(b)。 由此可知&#xff0c;137137 可表示為 2(7)2(3)2(0) 進一步&#xff1a; 7 2^222^0 ( 2^121 用 2 表示)&#xff0c;并且 322^0。 所…

Spring Boot 重啟命令

Spring Boot 重啟命令 本文描述了一個重啟Spring Boot命令執行過程和示例 本文利用kill -9 關閉進程&#xff0c;不優雅&#xff0c;會突然中斷程序&#xff0c;可能導致數據和邏輯異常 搜索微信小程序【亞特技術】在資源中搜索【優雅】可得到Spring Boot如何優化重啟 1. 過…

【Bert101】變壓器模型背后的復雜數學【02/4】

一、說明 眾所周知&#xff0c;變壓器架構是自然語言處理&#xff08;NLP&#xff09;領域的突破。它克服了 seq-to-seq 模型&#xff08;如 RNN 等&#xff09;無法捕獲文本中的長期依賴性的局限性。變壓器架構被證明是革命性架構&#xff08;如 BERT、GPT 和 T5 及其變體&…

【陣列信號處理】空間匹配濾波器、錐形/非錐形最佳波束成形器、樣本矩陣反演 (SMI) 研究(Matlab代碼實現)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;歡迎來到本博客????&#x1f4a5;&#x1f4a5; &#x1f3c6;博主優勢&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客內容盡量做到思維縝密&#xff0c;邏輯清晰&#xff0c;為了方便讀者。 ??座右銘&a…

九耶丨閣瑞鈦倫特-產品經理面試題

在產品上線后&#xff0c;會著重觀察6類指標&#xff1a; 1、活躍用戶指標 衡量APP用戶規模的指標&#xff0c;一個產品是否成功&#xff0c;如果只看一個指標&#xff0c;那么這個指標一定是活躍用戶數。 日活(DAU)&#xff1a;一天內日均活躍設備數(去重&#xff0c;每個公…

關于使用pycharm遇到只能使用unittest方式運行,無法直接選擇Run

相信大家可能都遇到過這個問題&#xff0c;使用pycharm直接運行腳本的時候&#xff0c;只能選擇unittest的方式&#xff0c;能愁死個人 經過幾次各種嘗試無果之后&#xff0c;博主就放棄死磕了&#xff0c;原諒博主是個菜鳥 后來遇到這樣的問題&#xff0c;往往也就直接使用cm…