MyBatis查詢(xún)數(shù)據(jù)庫(kù)語(yǔ)句總結(jié)
1.MyBatis是什么?
MyBatis是一款開(kāi)源的基于Java的持久層框架,它可以幫助Java開(kāi)發(fā)人員簡(jiǎn)化數(shù)據(jù)庫(kù)操作,通過(guò)XML配置文件或注解,把Java對(duì)象映射到對(duì)應(yīng)的數(shù)據(jù)庫(kù)表中,實(shí)現(xiàn)對(duì)象關(guān)系和數(shù)據(jù)關(guān)系的互相轉(zhuǎn)換,從而使得Java應(yīng)用程序能夠更簡(jiǎn)單的操作和讀取數(shù)據(jù)庫(kù)。
MyBatis相對(duì)于其他ORM框架最大的特點(diǎn)就是它提供了優(yōu)秀的SQL編寫(xiě)和調(diào)試機(jī)制。通過(guò)MyBatis,我們可以直接編寫(xiě)出原生SQL語(yǔ)句,并通過(guò)MyBatis的一些工具,如參數(shù)映射、結(jié)果集映射等機(jī)制,把查詢(xún)結(jié)果自動(dòng)映射成Java對(duì)象或者List/Map等數(shù)據(jù)格式。同時(shí),MyBatis還提供了懶加載、緩存和事務(wù)等高級(jí)特性,可以支持各種復(fù)雜的業(yè)務(wù)場(chǎng)景。
核心思想
將JavaBean中的數(shù)據(jù)和SQL語(yǔ)句中的數(shù)據(jù)進(jìn)行映射,因此,在使用MyBatis時(shí),我們需要定義一個(gè)Mapper接口,然后在XML中描述這個(gè)接口中每個(gè)方法對(duì)應(yīng)的SQL語(yǔ)句,并指定參數(shù)和返回值類(lèi)型,最后,讓MyBatis自動(dòng)生成這個(gè)Mapper接口的實(shí)現(xiàn)類(lèi),這樣就可以方便地對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作。
持久層
在 Java 程序開(kāi)發(fā)中,數(shù)據(jù)通常被存儲(chǔ)在關(guān)系型數(shù)據(jù)庫(kù)或者非關(guān)系型數(shù)據(jù)庫(kù)中,而持久層就是用來(lái)處理和管理這些數(shù)據(jù)存取操作的技術(shù)層次。持久層的作用是將應(yīng)用程序中的對(duì)象轉(zhuǎn)換成可存儲(chǔ)的形式,然后將其存儲(chǔ)到數(shù)據(jù)存儲(chǔ)介質(zhì)中,同時(shí)還要負(fù)責(zé)從數(shù)據(jù)存儲(chǔ)介質(zhì)中獲取數(shù)據(jù)并將其轉(zhuǎn)換成應(yīng)用程序所需要的對(duì)象形式,這樣就把數(shù)據(jù)存儲(chǔ)和數(shù)據(jù)訪問(wèn)分開(kāi)了,提高了程序的可維護(hù)性和擴(kuò)展性。
持久層技術(shù)通常包括以下方面的內(nèi)容:
- 對(duì)象-關(guān)系映射(ORM):ORM將Java對(duì)象與關(guān)系型數(shù)據(jù)庫(kù)之間進(jìn)行了映射,使得Java對(duì)象可以像操作內(nèi)存一樣操作數(shù)據(jù)庫(kù),簡(jiǎn)化了開(kāi)發(fā)。
- 數(shù)據(jù)訪問(wèn)層(DAO):定義了對(duì)數(shù)據(jù)訪問(wèn)的接口,封裝了數(shù)據(jù)存取的細(xì)節(jié),使得上層業(yè)務(wù)邏輯不需要關(guān)注數(shù)據(jù)存取的具體實(shí)現(xiàn)方式。
- 持久化框架:用于簡(jiǎn)化數(shù)據(jù)庫(kù)訪問(wèn)、提高性能、降低開(kāi)發(fā)難度、提供高級(jí)功能,如緩存、批量處理、分布式共享等等。
JDBC編程的缺點(diǎn)
- 繁瑣的代碼編寫(xiě):JDBC需要開(kāi)發(fā)人員自己手動(dòng)編寫(xiě)大量的結(jié)果集映射、異常處理等代碼,很容易出現(xiàn)重復(fù)代碼和錯(cuò)誤。
- 容易造成資源泄漏:使用JDBC需要顯式地打開(kāi)和關(guān)閉連接、Statement和ResultSet等對(duì)象,如果不仔細(xì)處理,可能會(huì)導(dǎo)致資源泄漏,從而影響應(yīng)用程序的性能和穩(wěn)定性。
- SQL語(yǔ)句的硬編碼:JDBC需要直接在Java代碼中編寫(xiě)SQL語(yǔ)句,這樣就容易出現(xiàn)硬編碼,對(duì)于SQL語(yǔ)句的修改和維護(hù)也不太方便。
- 難以應(yīng)對(duì)復(fù)雜業(yè)務(wù)場(chǎng)景:如果某個(gè)應(yīng)用有比較復(fù)雜的業(yè)務(wù)邏輯需求,例如多表關(guān)聯(lián)查詢(xún)、分頁(yè)查詢(xún)、事務(wù)控制等,使用JDBC來(lái)實(shí)現(xiàn)可能比較困難,需要編寫(xiě)大量的復(fù)雜代碼。
MyBatis可以解決這些問(wèn)題.ORM框架可以將Java對(duì)象和數(shù)據(jù)庫(kù)表自動(dòng)映射,并提供了更加優(yōu)雅和高效的API,讓開(kāi)發(fā)人員專(zhuān)注于業(yè)務(wù)邏輯,而不是繁瑣的數(shù)據(jù)訪問(wèn)代碼。同時(shí),ORM框架還可以提供緩存、懶加載和批處理等高級(jí)特性,從而進(jìn)一步提高程序的性能和可維護(hù)性。
MyBatis與JDBC
MyBatis是基于JDBC實(shí)現(xiàn)的。MyBatis雖然與JDBC有很大的相似之處,但是它封裝了JDBC的細(xì)節(jié),使得開(kāi)發(fā)人員可以更加便捷和高效地進(jìn)行數(shù)據(jù)庫(kù)操作。
在使用MyBatis進(jìn)行數(shù)據(jù)訪問(wèn)時(shí),程序仍然需要使用JDBC的API來(lái)獲取連接對(duì)象、創(chuàng)建Statement對(duì)象、執(zhí)行SQL語(yǔ)句以及處理查詢(xún)結(jié)果等操作。不過(guò),MyBatis對(duì)JDBC進(jìn)行了簡(jiǎn)化和封裝,提供了面向?qū)ο蟮姆绞皆L問(wèn)數(shù)據(jù)庫(kù),通過(guò) XML 或注解方式配置 SQL 語(yǔ)句,并且提供了一些高級(jí)特性,如緩存、動(dòng)態(tài) SQL 和對(duì)象關(guān)系映射(ORM)等。
MyBatis相對(duì)于JDBC的優(yōu)勢(shì)在于,MyBatis更加簡(jiǎn)單易用,提升了開(kāi)發(fā)效率;同時(shí),MyBatis還提供了更多的功能,如自動(dòng)映射、動(dòng)態(tài) SQL 和二級(jí)緩存等,可以提升應(yīng)用程序的性能和可維護(hù)性。
因此,MyBatis是一種基于JDBC實(shí)現(xiàn)的高級(jí)數(shù)據(jù)訪問(wèn)框架,它封裝了JDBC的底層細(xì)節(jié),提供了一種高級(jí)的、面向?qū)ο蟮姆绞絹?lái)訪問(wèn)數(shù)據(jù)庫(kù),并且提供了多種高級(jí)特性,以方便開(kāi)發(fā)人員進(jìn)行數(shù)據(jù)訪問(wèn)和業(yè)務(wù)邏輯的實(shí)現(xiàn)。
ORM框架
ORM是對(duì)象關(guān)系映射(Object-Relational Mapping)的縮寫(xiě),是一種將對(duì)象模型和關(guān)系數(shù)據(jù)庫(kù)模型進(jìn)行轉(zhuǎn)換、映射的技術(shù)。通過(guò)ORM框架,可以將Java對(duì)象映射到關(guān)系數(shù)據(jù)庫(kù)表中的行,從而通過(guò)面向?qū)ο蟮姆绞絹?lái)操作數(shù)據(jù)庫(kù),而無(wú)需編寫(xiě)大量的SQL語(yǔ)句。
Java的類(lèi),對(duì)象,對(duì)象屬性映射到ORM框架中是什么
類(lèi)映射:在MyBatis中,Java的類(lèi)映射到數(shù)據(jù)庫(kù)中的某個(gè)表
對(duì)象映射:在MyBatis中,Java的對(duì)象映射到數(shù)據(jù)庫(kù)中的數(shù)據(jù)行
屬性映射:Java對(duì)象的屬性和數(shù)據(jù)庫(kù)表的列是一一對(duì)應(yīng)的
2.配置MyBatis環(huán)境
1.添加MyBatis框架支持
運(yùn)行后會(huì)報(bào)錯(cuò)
2.設(shè)置MyBatis配置信息
1.設(shè)置數(shù)據(jù)庫(kù)連接相關(guān)信息
2.配置xml保存路徑,xml命名規(guī)則
classpath
表示類(lèi)路徑,即應(yīng)用程序的CLASSPATH環(huán)境變量所指的路徑,用于指示 MyBatis 框架在類(lèi)路徑下查找配置文件。
/mybatis/
是一個(gè)目錄路徑,表示 MyBatis 框架將在類(lèi)路徑下的
mybatis
目錄中查找 *Mapper.xml 配置文件。
*Mapper.xml
*
是通配符,表示所有以Mapper.xml
結(jié)尾的文件都應(yīng)該被 MyBatis 框架解析和加載。綜合起來(lái),
classpath:/mybatis/*Mapper.xml
用于指示 MyBatis 框架在類(lèi)路徑下的mybatis
目錄中查找所有以Mapper.xml
結(jié)尾的配置文件,并將其解析為 MyBatis 的 Mapper 映射文件。舉個(gè)例子,假設(shè)我們的應(yīng)用程序類(lèi)路徑中有一個(gè)名為
mybatis
的目錄,該目錄下包含了兩個(gè) MyBatis 的映射文件:UserMapper.xml
和OrderMapper.xml
。那么,使用classpath:/mybatis/*Mapper.xml
這個(gè)路徑表達(dá)式,MyBatis 框架將會(huì)自動(dòng)解析這兩個(gè)映射文件,并將其配置信息綁定到對(duì)應(yīng)的 Java 接口或類(lèi)上,從而實(shí)現(xiàn)與數(shù)據(jù)庫(kù)的數(shù)據(jù)訪問(wèn)操作。
至此,配置環(huán)境就完成了
3. MyBatis模式開(kāi)發(fā)
先向數(shù)據(jù)庫(kù)中插入一些數(shù)據(jù)
首先,創(chuàng)建一個(gè)實(shí)體類(lèi)UserEntity,包含上述幾個(gè)屬性,必須對(duì)應(yīng)。
接著,在Mapper接口中聲明對(duì)UserEntity表進(jìn)行操作的方法:
Mapper是MyBatis框架中的一個(gè)概念,用于描述一組將Java對(duì)象映射到SQL語(yǔ)句的規(guī)則。通常情況下,Mapper會(huì)包含一組接口和配置文件,用于定義Java對(duì)象和數(shù)據(jù)庫(kù)表之間的映射關(guān)系。
然后,在Mapper.xml文件中,實(shí)現(xiàn)這些接口方法
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mapper.UserMapper"> <select id="getAll" resultType="com.example.demo.entity.UserEntity"> select * from userinfo </select> </mapper>
id對(duì)應(yīng)接口中的方法名
namesapce:實(shí)現(xiàn)接口(包名加類(lèi)名)
按照標(biāo)準(zhǔn)分層,然后,創(chuàng)建Service層訪問(wèn)Mapper
Service層處理業(yè)務(wù)邏輯,并對(duì)外提供服務(wù)接口
創(chuàng)建Controller層
Controller層:負(fù)責(zé)接收用戶(hù)請(qǐng)求,調(diào)用 Service 層處理請(qǐng)求,并通過(guò)視圖呈現(xiàn)處理結(jié)果
啟動(dòng)項(xiàng)目,成功訪問(wèn)
向表中插入一條數(shù)據(jù)
刷新后,更新數(shù)據(jù)庫(kù)中插入的新數(shù)據(jù)
上述是最簡(jiǎn)單的查詢(xún)操作
4.單元測(cè)試
此處介紹一下單元測(cè)試的概念
SpringBoot已經(jīng)內(nèi)置了單元測(cè)試的依賴(lài),不用添加支持
spring-boot-starter-test
是 Spring Boot 提供的一種測(cè)試 Starter,旨在提供各種常見(jiàn)的測(cè)試工具和框架,使開(kāi)發(fā)人員能夠更加輕松地編寫(xiě)、運(yùn)行、管理 Spring Boot 應(yīng)用程序的單元測(cè)試和集成測(cè)試
包含了以下幾個(gè)主要組件:
- JUnit:一個(gè)廣泛使用的 Java 單元測(cè)試框架,能夠幫助開(kāi)發(fā)人員快速構(gòu)建單元測(cè)試用例,并自動(dòng)執(zhí)行這些測(cè)試用例;()我們此處使用的JUit5)
- Spring Test:一個(gè)專(zhuān)門(mén)為 Spring 應(yīng)用程序設(shè)計(jì)的測(cè)試框架,能夠幫助開(kāi)發(fā)人員快速搭建 Spring 容器環(huán)境,在容器環(huán)境中執(zhí)行測(cè)試用例,并驗(yàn)證應(yīng)用程序的正確性;
- AssertJ:一個(gè)優(yōu)秀的斷言庫(kù),能夠幫助開(kāi)發(fā)人員編寫(xiě)簡(jiǎn)潔、易于理解、易于擴(kuò)展的斷言語(yǔ)句;
- Hamcrest:另一個(gè)流行的斷言庫(kù),也提供了類(lèi)似于 AssertJ 的各種斷言方法;
- Mockito:一個(gè)強(qiáng)大的 Java 模擬框架,能夠幫助開(kāi)發(fā)人員快速創(chuàng)建模擬對(duì)象,并進(jìn)行各種測(cè)試;
- JsonPath:一個(gè)用于處理 JSON 路徑的庫(kù),能夠幫助開(kāi)發(fā)人員快速過(guò)濾、提取 JSON 數(shù)據(jù)。
優(yōu)點(diǎn)主要有以下幾個(gè):
- 提升測(cè)試效率:借助 spring-boot-starter-test Starter 提供的各種測(cè)試工具和框架,開(kāi)發(fā)人員能夠更加輕松地編寫(xiě)、運(yùn)行、管理單元測(cè)試和集成測(cè)試,從而大大提升測(cè)試效率;
- 方便集成 Spring Boot:spring-boot-starter-test Starter 與 Spring Boot 完美集成,可直接使用 Spring Boot 的自動(dòng)配置機(jī)制和依賴(lài)注入機(jī)制,使得單元測(cè)試與實(shí)際應(yīng)用程序開(kāi)發(fā)更加無(wú)縫銜接;
- 簡(jiǎn)化代碼編寫(xiě):借助 spring-boot-starter-test Starter 提供的各種測(cè)試工具和框架,開(kāi)發(fā)人員不需要編寫(xiě)大量的測(cè)試框架代碼,只需要關(guān)注業(yè)務(wù)邏輯和測(cè)試用例的編寫(xiě)即可;
- 多樣化的測(cè)試支持:spring-boot-starter-test Starter 支持多種類(lèi)型的測(cè)試:?jiǎn)卧獪y(cè)試、集成測(cè)試、端到端測(cè)試等等;
- 良好的社區(qū)支持與更新:spring-boot-starter-test Starter 是 Spring Boot 社區(qū)的核心組件之一,擁有龐大的用戶(hù)群和活躍的維護(hù)者團(tuán)隊(duì),能夠及時(shí)更新和修復(fù)各種測(cè)試工具和框架的問(wèn)題。
單元測(cè)試實(shí)現(xiàn)步驟
1.在要測(cè)試的類(lèi)上右鍵Generate
會(huì)生成測(cè)試類(lèi)
寫(xiě)測(cè)試信息.不要忘記添加注解!!
啟動(dòng)測(cè)試類(lèi)
這是測(cè)試類(lèi)的創(chuàng)建,向測(cè)試類(lèi)中添加測(cè)試時(shí),會(huì)報(bào)一個(gè)錯(cuò)誤
意思是已經(jīng)存在了這個(gè)測(cè)試類(lèi),是否向其中追加測(cè)試,直接點(diǎn)擊Ok
單元測(cè)試就簡(jiǎn)單了解到這里
5.增刪查改
下來(lái)看傳參問(wèn)題
當(dāng)這兩處,參數(shù)名稱(chēng)不一致,xml中應(yīng)該寫(xiě)成什么
啟動(dòng)測(cè)試類(lèi)會(huì)報(bào)錯(cuò),參數(shù)id沒(méi)找到
修改xml文件
此時(shí)單元測(cè)試運(yùn)行成功了 .
所以xml文件的參數(shù)命名要和@Param("uid")一致..如果沒(méi)有加這個(gè)注解,就要和原參數(shù)名相同
加了注解,生效的就是注解中的參數(shù)命名
Mybatis獲取動(dòng)態(tài)參數(shù)的方式有兩種:
1.${key}
${key} 是一種占位符語(yǔ)法,被稱(chēng)為字符串替換,用于將 SQL 語(yǔ)句中的參數(shù)占位符替換為實(shí)際的值。使用 ${} 語(yǔ)法會(huì)直接將參數(shù)替換為 SQL 字符串,而不是通過(guò)預(yù)編譯語(yǔ)句來(lái)傳遞參數(shù)。在使用 ${key} 時(shí),如果不正確地處理輸入的參數(shù),可能會(huì)導(dǎo)致 SQL 注入攻擊。
可以打印出執(zhí)行的生成的sql語(yǔ)句,就能看到效果了
需要先配置一下
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8&useSSL=false spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #配置mybatis xml 的文件路徑,在resources/mapper 創(chuàng)建的所有表的 xml 文件 mybatis.mapper-locations=classpath:/mybatis/*Mapper.xml #日志打印sql mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #日志打印級(jí)別(默認(rèn)是info,需要設(shè)置位debug,才能顯示) logging.level.com.example.demo=debug
然后執(zhí)行測(cè)試
可以看到這是直接將參數(shù)替換為 SQL 字符串
2.#{key}
在 MyBatis 中,#{key} 是一種占位符語(yǔ)法,被稱(chēng)為參數(shù)占位符,用于將 SQL 語(yǔ)句中的參數(shù)占位符替換為實(shí)際的值。它可以幫助我們?cè)?SQL 語(yǔ)句中使用參數(shù),使得 SQL 語(yǔ)句更加靈活和通用
具體來(lái)說(shuō),當(dāng)應(yīng)用程序調(diào)用 DAO 層的方法時(shí),會(huì)傳遞一些參數(shù)給對(duì)應(yīng)的 SQL 語(yǔ)句,這些參數(shù)可以通過(guò) #{} 占位符來(lái)引用。MyBatis 會(huì)自動(dòng)將 #{} 中的參數(shù)值進(jìn)行預(yù)編譯處理,保證輸入數(shù)據(jù)的安全性,并將它們以安全的方式插入到 SQL 語(yǔ)句中,然后發(fā)送給數(shù)據(jù)庫(kù)執(zhí)行,從而防止了 SQL 注入攻擊等安全問(wèn)題
啟動(dòng)單元測(cè)試
這里的 #{uid} 是一個(gè)占位符,代表該位置需要?jiǎng)討B(tài)替換為具體的參數(shù)值。在執(zhí)行 SQL 時(shí),MyBatis 會(huì)自動(dòng)將 #{uid} 替換為實(shí)際傳入的 uid 參數(shù)的值,并將其進(jìn)行預(yù)編譯處理,從而確保 SQL 查詢(xún)語(yǔ)句的安全性。
日志打印看到的也不是sql語(yǔ)句,而是通過(guò)占位符模式進(jìn)行內(nèi)容的填充
這里數(shù)據(jù)是int類(lèi)型的,我們換用String類(lèi)型數(shù)據(jù)嘗試
結(jié)果:
當(dāng)將#{}替換為${} 再次進(jìn)行執(zhí)行.報(bào)錯(cuò)了
原因:${}是直接替換字符串的,你輸入的是什么就完全替換
'${username}',才不會(huì)報(bào)錯(cuò),不加單引號(hào)sql語(yǔ)句就會(huì)出現(xiàn)問(wèn)題
這樣只能保障不報(bào)錯(cuò),但是會(huì)存在安全性問(wèn)題
例如登陸場(chǎng)景下:sql注入問(wèn)題
為了只返回一條數(shù)據(jù),先將表中數(shù)據(jù)刪除
mysql> select*from userinfo; +----+----------+----------+-------+---------------------+---------------------+-------+ | id | username | password | photo | createtime | updatetime | state | +----+----------+----------+-------+---------------------+---------------------+-------+ | 2 | lisi | lisi | | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 | 1 | +----+----------+----------+-------+---------------------+---------------------+-------+ 1 row in set (0.00 sec)
正常行為:
攻擊者發(fā)送如下輸入:
mysql> select 1 = '1'; +---------+ | 1 = '1' | +---------+ | 1 | +---------+ 1 row in set (0.00 sec)
那么該 SQL 查詢(xún)語(yǔ)句將會(huì)被替換為:
SELECT * FROM users WHERE username = '' or '1'='1' AND password = '' or '1'='1'
使用不存在的username,不存在的password也能查到用戶(hù)信息
這就是使用${key}時(shí)的sql注入攻擊.
將${key}替換為#{key}
占位符的方式,不會(huì)出現(xiàn)安全性問(wèn)題
${}在特定場(chǎng)景下,也有優(yōu)點(diǎn)
此時(shí)使用#{key}會(huì)被錯(cuò)誤地解析為字符串:
SELECT * FROM userinfo ORDER BY id "desc"
即使設(shè)置了 ord 參數(shù)的值為 ASC 或 DESC,也無(wú)法正確地替換該字符串
需要將該 SQL 語(yǔ)句更改為以下形式進(jìn)行直接替換:
SELECT * FROM userinfo ORDER BY id ${ord}
總結(jié):
1.${key} 主要用于字符串替換,可以方便地將參數(shù)替換為 SQL 語(yǔ)句
使用 ${key} 時(shí)需要注意安全性問(wèn)題,應(yīng)該避免潛在的 SQL 注入攻擊
2.#{key} 主要用于預(yù)處理 SQL 語(yǔ)句,可以有效地防止 SQL 注入攻擊
#{key} 底層原理:
在 MyBatis 中,#{} 在底層的實(shí)現(xiàn)中使用了 PreparedStatement 進(jìn)行參數(shù)的預(yù)處理和類(lèi)型轉(zhuǎn)換。
當(dāng) MyBatis 執(zhí)行 SQL 語(yǔ)句時(shí),它會(huì)將 SQL 語(yǔ)句中的 #{} 占位符替換為 JDBC 的 Placeholder(如 ?),然后生成一個(gè)預(yù)編譯的 SQL 語(yǔ)句。這個(gè)預(yù)編譯的 SQL 語(yǔ)句會(huì)被傳遞給 DriverManager,并由 DriverManager 創(chuàng)建一個(gè) PreparedStatement 對(duì)象。
然后,MyBatis 會(huì)將查詢(xún)參數(shù)的值按照 JDBC 規(guī)范設(shè)置到 PreparedStatement 的參數(shù)位置上,然后執(zhí)行 PreparedStatement 對(duì)象的查詢(xún)操作,最后將查詢(xún)結(jié)果映射成 Java 對(duì)象并返回給調(diào)用方。
使用預(yù)處理語(yǔ)句可以提高 SQL 的執(zhí)行效率,避免 SQL 注入等安全問(wèn)題,并且可以自動(dòng)進(jìn)行類(lèi)型轉(zhuǎn)換,讓開(kāi)發(fā)人員更加專(zhuān)注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。
需要注意的是,由于預(yù)處理語(yǔ)句必須在編譯和執(zhí)行之間進(jìn)行變量綁定,因此不能在運(yùn)行時(shí)動(dòng)態(tài)地構(gòu)建 SQL 語(yǔ)句。如果需要在運(yùn)行時(shí)動(dòng)態(tài)地構(gòu)建 SQL 語(yǔ)句,需要使用 ${} 實(shí)現(xiàn)字符串替換,并且需要注意防止 SQL 注入攻擊的問(wèn)題。
PreparedStatement 是 JDBC 提供的一種接口,用于提高執(zhí)行 SQL 的性能和安全性 PreparedStatement 的特點(diǎn):
- 預(yù)編譯:PreparedStatement 對(duì)象在創(chuàng)建時(shí)會(huì)將 SQL 語(yǔ)句編譯成一個(gè)可執(zhí)行的語(yǔ)句,這樣可以提高 SQL 的執(zhí)行效率。
- 參數(shù)綁定:PreparedStatement 可以在執(zhí)行前設(shè)置參數(shù),可以使用占位符 (?) 或命名參數(shù)(:param)來(lái)代替實(shí)際的參數(shù)值,從而避免了 SQL 注入攻擊。
- 批處理:PreparedStatement 支持批量處理,可以將多個(gè) SQL 語(yǔ)句一次性提交到數(shù)據(jù)庫(kù)執(zhí)行,從而提高數(shù)據(jù)庫(kù)處理的效率。
- 預(yù)處理語(yǔ)句緩存:預(yù)編譯語(yǔ)句可以緩存,再次執(zhí)行同樣的 SQL 語(yǔ)句時(shí),可以直接從緩存中獲取已編譯的語(yǔ)句,避免了每次都重新編譯 SQL,從而提高 SQL 的執(zhí)行效率。
修改密碼
運(yùn)行單元測(cè)試
成功修改
mysql> select*from userinfo ; +----+----------+----------+-------+---------------------+---------------------+-------+ | id | username | password | photo | createtime | updatetime | state | +----+----------+----------+-------+---------------------+---------------------+-------+ | 2 | lisi | 123456 | | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 | 1 | +----+----------+----------+-------+---------------------+---------------------+-------+ 1 row in set (0.00 sec)
在單元測(cè)試情況下,我們是不愿意改動(dòng)數(shù)據(jù)庫(kù)的,也就是不污染數(shù)據(jù)庫(kù)
默認(rèn)情況下,是會(huì)持久化改動(dòng)數(shù)據(jù)庫(kù)的
這里就用到@Transactional注解了
正常執(zhí)行,但不污染數(shù)據(jù)庫(kù),執(zhí)行時(shí)開(kāi)啟事務(wù),執(zhí)行結(jié)束后進(jìn)行回滾操作,就不會(huì)污染數(shù)據(jù)庫(kù)
看看數(shù)據(jù)庫(kù)中,沒(méi)有改變,說(shuō)明進(jìn)行了回滾...并且成功驗(yàn)證了程序,返回修改:1
mysql> select*from userinfo ; +----+----------+----------+-------+---------------------+---------------------+-------+ | id | username | password | photo | createtime | updatetime | state | +----+----------+----------+-------+---------------------+---------------------+-------+ | 2 | lisi | 123456 | | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 | 1 | +----+----------+----------+-------+---------------------+---------------------+-------+ 1 row in set (0.00 sec)
下來(lái)學(xué)習(xí)刪除操作
//刪除用戶(hù),返回受影響行數(shù) int deleteById(@Param("id")Integer id); <delete id="deleteById"> <!-- 默認(rèn)返回受影響的行數(shù)--> delete from userinfo where id = #{id} </delete> @Transactional @Test void deleteById() { int result = userMapper.deleteById(2); System.out.println("刪除: "+result); }
執(zhí)行結(jié)果
因?yàn)樵O(shè)置事務(wù)了,所以測(cè)試完成后進(jìn)行了回滾,不會(huì)污染數(shù)據(jù)庫(kù)
普通得增加模塊,這里我們不進(jìn)行回滾了,讓數(shù)據(jù)插入到數(shù)據(jù)庫(kù)中
//添加用戶(hù) int addUser(UserEntity user); <insert id="addUser"> <!-- 默認(rèn)返回受影響的行數(shù)--> insert into userinfo(username,password) values(#{username},#{password}) </insert> @Test void addUser() { UserEntity user = new UserEntity(); user.setUsername("zhangsan"); user.setPassword("123456"); int result = userMapper.addUser(user); System.out.println(result); }
執(zhí)行結(jié)果,返回影響行數(shù):1.數(shù)據(jù)庫(kù)中新增了一條數(shù)據(jù)
==> Preparing: insert into userinfo(username,password) values(?,?)
==> Parameters: zhangsan(String), 123456(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3e4e4c1]
1
mysql> select*from userinfo ; +----+----------+----------+-------+---------------------+---------------------+-------+ | id | username | password | photo | createtime | updatetime | state | +----+----------+----------+-------+---------------------+---------------------+-------+ | 2 | lisi | 123456 | | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 | 1 | | 3 | zhangsan | 123456 | | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 | 1 | +----+----------+----------+-------+---------------------+---------------------+-------+ 2 rows in set (0.00 sec)
添加用戶(hù)得到id
//添加用戶(hù)得到ID int addUserGetId(UserEntity user); <insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id"> <!--返回受影響的行數(shù) 和 id--> <!--useGeneratedKeys="true"keyProperty="id"是否自動(dòng)生成id,拿到id放到id字段中--> insert into userinfo(username,password) values(#{username},#{password}) </insert> @Test void addUserGetId() { UserEntity user = new UserEntity(); user.setUsername("wangwu"); user.setPassword("123456"); int result = userMapper.addUserGetId(user); System.out.println("ID: "+user.getId()); }
執(zhí)行結(jié)果
JDBC Connection [HikariProxyConnection@1775046789 wrapping com.mysql.cj.jdbc.ConnectionImpl@60bb7995] will not be managed by Spring
==> Preparing: insert into userinfo(username,password) values(?,?)
==> Parameters: wangwu(String), 123456(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@ef60710]
ID: 4
mysql> select*from userinfo ; +----+----------+----------+-------+---------------------+---------------------+-------+ | id | username | password | photo | createtime | updatetime | state | +----+----------+----------+-------+---------------------+---------------------+-------+ | 2 | lisi | 123456 | | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 | 1 | | 3 | zhangsan | 123456 | | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 | 1 | | 4 | wangwu | 123456 | | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 | 1 | +----+----------+----------+-------+---------------------+---------------------+-------+ 3 rows in set (0.00 sec)
模糊查詢(xún)-concat
使用#{key}方式的查詢(xún)結(jié)果會(huì)報(bào)錯(cuò)
因?yàn)閭鬟f參數(shù)為string類(lèi)型,會(huì)被#{}解析為字符串,因此會(huì)自動(dòng)加上'',那么占位符替換后變成
'%'li'%'
mysql> select*from userinfo where username like '%'li'%'; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'li'%'' at line 1
需要借助concat來(lái)進(jìn)行拼接
mysql> select concat ('%','li','%'); +-----------------------+ | concat ('%','li','%') | +-----------------------+ | %li% | +-----------------------+ 1 row in set (0.00 sec)
執(zhí)行結(jié)果
JDBC Connection [HikariProxyConnection@1622899093 wrapping com.mysql.cj.jdbc.ConnectionImpl@40fa8766] will not be managed by Spring
==> Preparing: select * from userinfo where username like concat('%',?,'%')
==> Parameters: li(String)
<== Columns: id, username, password, photo, createtime, updatetime, state
<== Row: 2, lisi, 123456, , 2022-12-06 17:10:48, 2022-12-06 18:10:48, 1
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@56399b9e]
UserEntity(id=2, username=lisi, password=123456, photo=, createtime=2022-12-06T17:10:48, updatetime=2022-12-06T18:10:48, state=1)
錯(cuò)誤的模糊匹配查詢(xún)會(huì)導(dǎo)致索引失效
1.like 'zhang%'
2.like '%zhang'
3.like '%zhang%'
只有第一種查詢(xún)會(huì)走索引,后面兩個(gè)都不會(huì)走索引,索引失效了
由于DBA和后端開(kāi)發(fā)人員代碼風(fēng)格不同,可能會(huì)出現(xiàn)不兼容的問(wèn)題,比如
然后我們執(zhí)行該單元測(cè)試
我們發(fā)現(xiàn)JDBC成功執(zhí)行,但是由于password映射不上pwd,pwd的值為空
此時(shí)resultType="com.example.demo.entity.UserEntity"就失效了,因?yàn)楸碇凶侄魏皖?lèi)的屬性不一樣了,無(wú)法一對(duì)一直接映射了
解決方法--<resultMap>
<resultMap> 是 MyBatis 中用于映射查詢(xún)結(jié)果集的標(biāo)簽。它能夠解決以下兩個(gè)問(wèn)題:
- 解決列名與屬性名不一致的問(wèn)題:在數(shù)據(jù)庫(kù)中,有些列名可能并不符合 Java 對(duì)象中屬性的命名規(guī)范,這時(shí)可以通過(guò) <resultMap> 映射來(lái)將查詢(xún)結(jié)果集中的列名與 Java 對(duì)象中屬性名進(jìn)行映射,使得結(jié)果集與對(duì)象之間能夠正確匹配。
- 解決多表關(guān)聯(lián)查詢(xún)的問(wèn)題:在進(jìn)行多表關(guān)聯(lián)查詢(xún)時(shí),查詢(xún)結(jié)果集通常會(huì)包含多個(gè)表中的列,這時(shí)可以使用 <resultMap> 將不同表中的列映射到不同的 Java 對(duì)象屬性中,從而完成對(duì)結(jié)果集的映射轉(zhuǎn)換。
第一步,設(shè)置映射關(guān)系
<resultMap id="BaseMap" type="com.example.demo.entity.UserEntity"> <!--設(shè)置主鍵 property:屬性,類(lèi)中的 column:字段,數(shù)據(jù)庫(kù)表中的 --> <id property="id" column="id"></id> <!--設(shè)置普通參數(shù)--> <result property="pwd" column="password"></result> <result property="username" column="username"></result> <result property="createtime" column="createtime"></result> <result property="photo" column="photo"></result> <result property="updatetime" column="updatetime"></result> <result property="state" column="state"></result> </resultMap>
第二步,修改返回類(lèi)型
<select id="getUserById" resultMap="BaseMap"> select * from userinfo where id = #{uid} </select>
執(zhí)行結(jié)果
還有比較簡(jiǎn)單的方式,在sql語(yǔ)句中設(shè)置重命名也能成功映射
<select id="getUserByName" resultType="com.example.demo.entity.UserEntity"> select id,username,password as pwd from userinfo where username = '${username}' </select>
多表聯(lián)查
mysql> update userinfo set id = 1 where username = 'lisi'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
文章信息表 mysql> select*from articleinfo; +----+-------+----------+---------------------+---------------------+-----+--------+-------+ | id | title | content | createtime | updatetime | uid | rcount | state | +----+-------+----------+---------------------+---------------------+-----+--------+-------+ | 1 | Java | Java正文 | 2023-05-15 09:12:59 | 2023-05-15 09:12:59 | 1 | 1 | 1 | +----+-------+----------+---------------------+---------------------+-----+--------+-------+ 1 row in set (0.01 sec)
為了對(duì)應(yīng),將lisi 的id修改為1
mysql> update userinfo set id = 1 where username = 'lisi'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select*from userinfo; +----+----------+----------+-------+---------------------+---------------------+-------+ | id | username | password | photo | createtime | updatetime | state | +----+----------+----------+-------+---------------------+---------------------+-------+ | 1 | lisi | 123456 | | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 | 1 | | 3 | zhangsan | 123456 | | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 | 1 | | 4 | wangwu | 123456 | | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 | 1 | +----+----------+----------+-------+---------------------+---------------------+-------+ 3 rows in set (0.00 sec)
創(chuàng)建文章表實(shí)體類(lèi)
@Data public class ArticleInfo { private int id; private String title; private String content; private LocalDateTime createtime; private LocalDateTime updatrtime; private int uid; private int rcount; private int state; }
擴(kuò)展類(lèi)
package com.example.demo.entity.vo; import com.example.demo.entity.ArticleInfo; import lombok.Data; @Data public class ArticleInfoVO extends ArticleInfo { private String username; }
在Mapper中創(chuàng)建接口
package com.example.demo.mapper; import com.example.demo.entity.vo.ArticleInfoVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface ArticleMapper { //查詢(xún)文章詳情 ArticleInfoVO getDetail(@Param("id")Integer id); }
在mybatis包下創(chuàng)建xml文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mapper.ArticleMapper"> <select id="getDetail" resultType="com.example.demo.entity.vo.ArticleInfoVO"> select a.*,u.username from articleinfo a left join userinfo u on u.id = a.uid where a.id = #{id} </select> </mapper>
創(chuàng)建測(cè)試類(lèi)
@SpringBootTest class ArticleMapperTest { @Autowired private ArticleMapper articleMapper; @Test void getDetail() { ArticleInfoVO articleInfoVO = articleMapper.getDetail(1); System.out.println(articleInfoVO); } }
執(zhí)行單元測(cè)試
JDBC執(zhí)行是正確的,但是返回只有username,并沒(méi)有查詢(xún)到父類(lèi)的屬性
其實(shí)是lombok出現(xiàn)了問(wèn)題,打印時(shí)沒(méi)包含父類(lèi)的屬性
解決辦法:手動(dòng)設(shè)置toString
public class ArticleInfoVO extends ArticleInfo { private String username; @Override public String toString() { return "ArticleInfoVO{" + "username='" + username + '\'' + "} " + super.toString(); } }
查看字節(jié)碼
再次執(zhí)行
JDBC Connection [HikariProxyConnection@166022233 wrapping com.mysql.cj.jdbc.ConnectionImpl@5dbb50f3] will not be managed by Spring ==> Preparing: select a.*,u.username from articleinfo a left join userinfo u on u.id = a.uid where a.id = ? ==> Parameters: 1(Integer) <== Columns: id, title, content, createtime, updatetime, uid, rcount, state, username <== Row: 1, Java, <<BLOB>>, 2023-05-15 09:12:59, 2023-05-15 09:12:59, 1, 1, 1, lisi <== Total: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3662bdff] ArticleInfoVO{username='lisi'} ArticleInfo(id=1, title=Java, content=Java正文, createtime=2023-05-15T09:12:59, updatrtime=null, uid=1, rcount=1, state=1)
查詢(xún)一個(gè)用戶(hù)的所有文章
先將插入一條文章記錄
mysql> select*from articleinfo; +----+-------+-----------+---------------------+---------------------+-----+--------+-------+ | id | title | content | createtime | updatetime | uid | rcount | state | +----+-------+-----------+---------------------+---------------------+-----+--------+-------+ | 1 | Java | Java正文 | 2023-05-15 09:12:59 | 2023-05-15 09:12:59 | 1 | 1 | 1 | | 2 | mysql | mysql正文 | 2023-05-19 11:14:49 | 2023-05-19 11:14:49 | 1 | 1 | 1 | +----+-------+-----------+---------------------+---------------------+-----+--------+-------+ 2 rows in set (0.00 sec)
其他代碼
//查詢(xún)一個(gè)用戶(hù)所有文章 List<ArticleInfoVO> getListByUid(@Param("uid")Integer uid); <select id="getListByUid" resultType="com.example.demo.entity.vo.ArticleInfoVO"> select a.*,u.username from articleinfo a left join userinfo u on u.id = a.uid where a.uid = #{uid} </select> @Test void getListByUid() { Integer uid =1; List<ArticleInfoVO> list = articleMapper.getListByUid(uid); //使用多線程的方式,順序可能會(huì)不同 list.stream().parallel().forEach(System.out::println); }
執(zhí)行
總結(jié)
到此這篇關(guān)于MyBatis查詢(xún)數(shù)據(jù)庫(kù)的文章就介紹到這了,更多相關(guān)MyBatis查詢(xún)數(shù)據(jù)庫(kù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Java和高德地圖API將經(jīng)緯度轉(zhuǎn)換為地理位置信息的步驟
這篇文章詳細(xì)介紹了如何將GPS坐標(biāo)轉(zhuǎn)換為人類(lèi)可讀的地理位置,介紹了環(huán)境準(zhǔn)備、代碼實(shí)現(xiàn)、異常處理及優(yōu)化步驟,首先創(chuàng)建LocationFinder類(lèi),實(shí)現(xiàn)getLocationFromCoordinates方法,利用高德逆地理編碼API轉(zhuǎn)換坐標(biāo),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09hashMap擴(kuò)容時(shí)應(yīng)該注意這些死循環(huán)問(wèn)題
今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著hashMap擴(kuò)容時(shí)的死循環(huán)問(wèn)題展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06Springboot項(xiàng)目中運(yùn)用vue+ElementUI+echarts前后端交互實(shí)現(xiàn)動(dòng)態(tài)圓環(huán)圖(推薦)
今天給大家?guī)?lái)一篇教程關(guān)于Springboot項(xiàng)目中運(yùn)用vue+ElementUI+echarts前后端交互實(shí)現(xiàn)動(dòng)態(tài)圓環(huán)圖的技能,包括環(huán)境配置及圓環(huán)圖前端后端實(shí)現(xiàn)代碼,感興趣的朋友一起看看吧2021-06-06關(guān)于SpringBoot的@ConfigurationProperties注解和松散綁定、數(shù)據(jù)校驗(yàn)
這篇文章主要介紹了關(guān)于SpringBoot的@ConfigurationProperties注解和松散綁定、數(shù)據(jù)校驗(yàn),@ConfigurationProperties主要作用就是將prefix屬性指定的前綴配置項(xiàng)的值綁定到這個(gè)JavaBean上?,通過(guò)指定的前綴,來(lái)綁定配置文件中的配置,需要的朋友可以參考下2023-05-05一文帶你深入了解Java中延時(shí)任務(wù)的實(shí)現(xiàn)
延時(shí)任務(wù)相信大家都不陌生,在現(xiàn)實(shí)的業(yè)務(wù)中應(yīng)用場(chǎng)景可以說(shuō)是比比皆是。這篇文章主要為大家介紹幾種實(shí)現(xiàn)延時(shí)任務(wù)的辦法,感興趣的可以了解一下2022-11-11解析web.xml中在Servlet中獲取context-param和init-param內(nèi)的參數(shù)
本篇文章是對(duì)web.xml中在Servlet中獲取context-param和init-param內(nèi)的參數(shù)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-07-07idea數(shù)據(jù)庫(kù)驅(qū)動(dòng)下載失敗的問(wèn)題及解決
這篇文章主要介紹了idea數(shù)據(jù)庫(kù)驅(qū)動(dòng)下載失敗的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01springboot接口參數(shù)為L(zhǎng)ist的問(wèn)題
這篇文章主要介紹了springboot接口參數(shù)為L(zhǎng)ist的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11