MyBatis查詢數(shù)據(jù)庫語句總結(jié)
1.MyBatis是什么?
MyBatis是一款開源的基于Java的持久層框架,它可以幫助Java開發(fā)人員簡化數(shù)據(jù)庫操作,通過XML配置文件或注解,把Java對象映射到對應(yīng)的數(shù)據(jù)庫表中,實現(xiàn)對象關(guān)系和數(shù)據(jù)關(guān)系的互相轉(zhuǎn)換,從而使得Java應(yīng)用程序能夠更簡單的操作和讀取數(shù)據(jù)庫。
MyBatis相對于其他ORM框架最大的特點就是它提供了優(yōu)秀的SQL編寫和調(diào)試機制。通過MyBatis,我們可以直接編寫出原生SQL語句,并通過MyBatis的一些工具,如參數(shù)映射、結(jié)果集映射等機制,把查詢結(jié)果自動映射成Java對象或者List/Map等數(shù)據(jù)格式。同時,MyBatis還提供了懶加載、緩存和事務(wù)等高級特性,可以支持各種復(fù)雜的業(yè)務(wù)場景。
核心思想
將JavaBean中的數(shù)據(jù)和SQL語句中的數(shù)據(jù)進行映射,因此,在使用MyBatis時,我們需要定義一個Mapper接口,然后在XML中描述這個接口中每個方法對應(yīng)的SQL語句,并指定參數(shù)和返回值類型,最后,讓MyBatis自動生成這個Mapper接口的實現(xiàn)類,這樣就可以方便地對數(shù)據(jù)庫進行操作。
持久層
在 Java 程序開發(fā)中,數(shù)據(jù)通常被存儲在關(guān)系型數(shù)據(jù)庫或者非關(guān)系型數(shù)據(jù)庫中,而持久層就是用來處理和管理這些數(shù)據(jù)存取操作的技術(shù)層次。持久層的作用是將應(yīng)用程序中的對象轉(zhuǎn)換成可存儲的形式,然后將其存儲到數(shù)據(jù)存儲介質(zhì)中,同時還要負(fù)責(zé)從數(shù)據(jù)存儲介質(zhì)中獲取數(shù)據(jù)并將其轉(zhuǎn)換成應(yīng)用程序所需要的對象形式,這樣就把數(shù)據(jù)存儲和數(shù)據(jù)訪問分開了,提高了程序的可維護性和擴展性。
持久層技術(shù)通常包括以下方面的內(nèi)容:
- 對象-關(guān)系映射(ORM):ORM將Java對象與關(guān)系型數(shù)據(jù)庫之間進行了映射,使得Java對象可以像操作內(nèi)存一樣操作數(shù)據(jù)庫,簡化了開發(fā)。
- 數(shù)據(jù)訪問層(DAO):定義了對數(shù)據(jù)訪問的接口,封裝了數(shù)據(jù)存取的細(xì)節(jié),使得上層業(yè)務(wù)邏輯不需要關(guān)注數(shù)據(jù)存取的具體實現(xiàn)方式。
- 持久化框架:用于簡化數(shù)據(jù)庫訪問、提高性能、降低開發(fā)難度、提供高級功能,如緩存、批量處理、分布式共享等等。
JDBC編程的缺點
- 繁瑣的代碼編寫:JDBC需要開發(fā)人員自己手動編寫大量的結(jié)果集映射、異常處理等代碼,很容易出現(xiàn)重復(fù)代碼和錯誤。
- 容易造成資源泄漏:使用JDBC需要顯式地打開和關(guān)閉連接、Statement和ResultSet等對象,如果不仔細(xì)處理,可能會導(dǎo)致資源泄漏,從而影響應(yīng)用程序的性能和穩(wěn)定性。
- SQL語句的硬編碼:JDBC需要直接在Java代碼中編寫SQL語句,這樣就容易出現(xiàn)硬編碼,對于SQL語句的修改和維護也不太方便。
- 難以應(yīng)對復(fù)雜業(yè)務(wù)場景:如果某個應(yīng)用有比較復(fù)雜的業(yè)務(wù)邏輯需求,例如多表關(guān)聯(lián)查詢、分頁查詢、事務(wù)控制等,使用JDBC來實現(xiàn)可能比較困難,需要編寫大量的復(fù)雜代碼。
MyBatis可以解決這些問題.ORM框架可以將Java對象和數(shù)據(jù)庫表自動映射,并提供了更加優(yōu)雅和高效的API,讓開發(fā)人員專注于業(yè)務(wù)邏輯,而不是繁瑣的數(shù)據(jù)訪問代碼。同時,ORM框架還可以提供緩存、懶加載和批處理等高級特性,從而進一步提高程序的性能和可維護性。
MyBatis與JDBC
MyBatis是基于JDBC實現(xiàn)的。MyBatis雖然與JDBC有很大的相似之處,但是它封裝了JDBC的細(xì)節(jié),使得開發(fā)人員可以更加便捷和高效地進行數(shù)據(jù)庫操作。
在使用MyBatis進行數(shù)據(jù)訪問時,程序仍然需要使用JDBC的API來獲取連接對象、創(chuàng)建Statement對象、執(zhí)行SQL語句以及處理查詢結(jié)果等操作。不過,MyBatis對JDBC進行了簡化和封裝,提供了面向?qū)ο蟮姆绞皆L問數(shù)據(jù)庫,通過 XML 或注解方式配置 SQL 語句,并且提供了一些高級特性,如緩存、動態(tài) SQL 和對象關(guān)系映射(ORM)等。
MyBatis相對于JDBC的優(yōu)勢在于,MyBatis更加簡單易用,提升了開發(fā)效率;同時,MyBatis還提供了更多的功能,如自動映射、動態(tài) SQL 和二級緩存等,可以提升應(yīng)用程序的性能和可維護性。
因此,MyBatis是一種基于JDBC實現(xiàn)的高級數(shù)據(jù)訪問框架,它封裝了JDBC的底層細(xì)節(jié),提供了一種高級的、面向?qū)ο蟮姆绞絹碓L問數(shù)據(jù)庫,并且提供了多種高級特性,以方便開發(fā)人員進行數(shù)據(jù)訪問和業(yè)務(wù)邏輯的實現(xiàn)。
ORM框架
ORM是對象關(guān)系映射(Object-Relational Mapping)的縮寫,是一種將對象模型和關(guān)系數(shù)據(jù)庫模型進行轉(zhuǎn)換、映射的技術(shù)。通過ORM框架,可以將Java對象映射到關(guān)系數(shù)據(jù)庫表中的行,從而通過面向?qū)ο蟮姆绞絹聿僮鲾?shù)據(jù)庫,而無需編寫大量的SQL語句。
Java的類,對象,對象屬性映射到ORM框架中是什么
類映射:在MyBatis中,Java的類映射到數(shù)據(jù)庫中的某個表
對象映射:在MyBatis中,Java的對象映射到數(shù)據(jù)庫中的數(shù)據(jù)行
屬性映射:Java對象的屬性和數(shù)據(jù)庫表的列是一一對應(yīng)的
2.配置MyBatis環(huán)境
1.添加MyBatis框架支持
運行后會報錯
2.設(shè)置MyBatis配置信息
1.設(shè)置數(shù)據(jù)庫連接相關(guān)信息
2.配置xml保存路徑,xml命名規(guī)則
classpath
表示類路徑,即應(yīng)用程序的CLASSPATH環(huán)境變量所指的路徑,用于指示 MyBatis 框架在類路徑下查找配置文件。
/mybatis/
是一個目錄路徑,表示 MyBatis 框架將在類路徑下的
mybatis
目錄中查找 *Mapper.xml 配置文件。
*Mapper.xml
*
是通配符,表示所有以Mapper.xml
結(jié)尾的文件都應(yīng)該被 MyBatis 框架解析和加載。綜合起來,
classpath:/mybatis/*Mapper.xml
用于指示 MyBatis 框架在類路徑下的mybatis
目錄中查找所有以Mapper.xml
結(jié)尾的配置文件,并將其解析為 MyBatis 的 Mapper 映射文件。舉個例子,假設(shè)我們的應(yīng)用程序類路徑中有一個名為
mybatis
的目錄,該目錄下包含了兩個 MyBatis 的映射文件:UserMapper.xml
和OrderMapper.xml
。那么,使用classpath:/mybatis/*Mapper.xml
這個路徑表達(dá)式,MyBatis 框架將會自動解析這兩個映射文件,并將其配置信息綁定到對應(yīng)的 Java 接口或類上,從而實現(xiàn)與數(shù)據(jù)庫的數(shù)據(jù)訪問操作。
至此,配置環(huán)境就完成了
3. MyBatis模式開發(fā)
先向數(shù)據(jù)庫中插入一些數(shù)據(jù)
首先,創(chuàng)建一個實體類UserEntity,包含上述幾個屬性,必須對應(yīng)。
接著,在Mapper接口中聲明對UserEntity表進行操作的方法:
Mapper是MyBatis框架中的一個概念,用于描述一組將Java對象映射到SQL語句的規(guī)則。通常情況下,Mapper會包含一組接口和配置文件,用于定義Java對象和數(shù)據(jù)庫表之間的映射關(guān)系。
然后,在Mapper.xml文件中,實現(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對應(yīng)接口中的方法名
namesapce:實現(xiàn)接口(包名加類名)
按照標(biāo)準(zhǔn)分層,然后,創(chuàng)建Service層訪問Mapper
Service層處理業(yè)務(wù)邏輯,并對外提供服務(wù)接口
創(chuàng)建Controller層
Controller層:負(fù)責(zé)接收用戶請求,調(diào)用 Service 層處理請求,并通過視圖呈現(xiàn)處理結(jié)果
啟動項目,成功訪問
向表中插入一條數(shù)據(jù)
刷新后,更新數(shù)據(jù)庫中插入的新數(shù)據(jù)
上述是最簡單的查詢操作
4.單元測試
此處介紹一下單元測試的概念
SpringBoot已經(jīng)內(nèi)置了單元測試的依賴,不用添加支持
spring-boot-starter-test
是 Spring Boot 提供的一種測試 Starter,旨在提供各種常見的測試工具和框架,使開發(fā)人員能夠更加輕松地編寫、運行、管理 Spring Boot 應(yīng)用程序的單元測試和集成測試
包含了以下幾個主要組件:
- JUnit:一個廣泛使用的 Java 單元測試框架,能夠幫助開發(fā)人員快速構(gòu)建單元測試用例,并自動執(zhí)行這些測試用例;()我們此處使用的JUit5)
- Spring Test:一個專門為 Spring 應(yīng)用程序設(shè)計的測試框架,能夠幫助開發(fā)人員快速搭建 Spring 容器環(huán)境,在容器環(huán)境中執(zhí)行測試用例,并驗證應(yīng)用程序的正確性;
- AssertJ:一個優(yōu)秀的斷言庫,能夠幫助開發(fā)人員編寫簡潔、易于理解、易于擴展的斷言語句;
- Hamcrest:另一個流行的斷言庫,也提供了類似于 AssertJ 的各種斷言方法;
- Mockito:一個強大的 Java 模擬框架,能夠幫助開發(fā)人員快速創(chuàng)建模擬對象,并進行各種測試;
- JsonPath:一個用于處理 JSON 路徑的庫,能夠幫助開發(fā)人員快速過濾、提取 JSON 數(shù)據(jù)。
優(yōu)點主要有以下幾個:
- 提升測試效率:借助 spring-boot-starter-test Starter 提供的各種測試工具和框架,開發(fā)人員能夠更加輕松地編寫、運行、管理單元測試和集成測試,從而大大提升測試效率;
- 方便集成 Spring Boot:spring-boot-starter-test Starter 與 Spring Boot 完美集成,可直接使用 Spring Boot 的自動配置機制和依賴注入機制,使得單元測試與實際應(yīng)用程序開發(fā)更加無縫銜接;
- 簡化代碼編寫:借助 spring-boot-starter-test Starter 提供的各種測試工具和框架,開發(fā)人員不需要編寫大量的測試框架代碼,只需要關(guān)注業(yè)務(wù)邏輯和測試用例的編寫即可;
- 多樣化的測試支持:spring-boot-starter-test Starter 支持多種類型的測試:單元測試、集成測試、端到端測試等等;
- 良好的社區(qū)支持與更新:spring-boot-starter-test Starter 是 Spring Boot 社區(qū)的核心組件之一,擁有龐大的用戶群和活躍的維護者團隊,能夠及時更新和修復(fù)各種測試工具和框架的問題。
單元測試實現(xiàn)步驟
1.在要測試的類上右鍵Generate
會生成測試類
寫測試信息.不要忘記添加注解!!
啟動測試類
這是測試類的創(chuàng)建,向測試類中添加測試時,會報一個錯誤
意思是已經(jīng)存在了這個測試類,是否向其中追加測試,直接點擊Ok
單元測試就簡單了解到這里
5.增刪查改
下來看傳參問題
當(dāng)這兩處,參數(shù)名稱不一致,xml中應(yīng)該寫成什么
啟動測試類會報錯,參數(shù)id沒找到
修改xml文件
此時單元測試運行成功了 .
所以xml文件的參數(shù)命名要和@Param("uid")一致..如果沒有加這個注解,就要和原參數(shù)名相同
加了注解,生效的就是注解中的參數(shù)命名
Mybatis獲取動態(tài)參數(shù)的方式有兩種:
1.${key}
${key} 是一種占位符語法,被稱為字符串替換,用于將 SQL 語句中的參數(shù)占位符替換為實際的值。使用 ${} 語法會直接將參數(shù)替換為 SQL 字符串,而不是通過預(yù)編譯語句來傳遞參數(shù)。在使用 ${key} 時,如果不正確地處理輸入的參數(shù),可能會導(dǎo)致 SQL 注入攻擊。
可以打印出執(zhí)行的生成的sql語句,就能看到效果了
需要先配置一下
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 #日志打印級別(默認(rèn)是info,需要設(shè)置位debug,才能顯示) logging.level.com.example.demo=debug
然后執(zhí)行測試
可以看到這是直接將參數(shù)替換為 SQL 字符串
2.#{key}
在 MyBatis 中,#{key} 是一種占位符語法,被稱為參數(shù)占位符,用于將 SQL 語句中的參數(shù)占位符替換為實際的值。它可以幫助我們在 SQL 語句中使用參數(shù),使得 SQL 語句更加靈活和通用
具體來說,當(dāng)應(yīng)用程序調(diào)用 DAO 層的方法時,會傳遞一些參數(shù)給對應(yīng)的 SQL 語句,這些參數(shù)可以通過 #{} 占位符來引用。MyBatis 會自動將 #{} 中的參數(shù)值進行預(yù)編譯處理,保證輸入數(shù)據(jù)的安全性,并將它們以安全的方式插入到 SQL 語句中,然后發(fā)送給數(shù)據(jù)庫執(zhí)行,從而防止了 SQL 注入攻擊等安全問題
啟動單元測試
這里的 #{uid} 是一個占位符,代表該位置需要動態(tài)替換為具體的參數(shù)值。在執(zhí)行 SQL 時,MyBatis 會自動將 #{uid} 替換為實際傳入的 uid 參數(shù)的值,并將其進行預(yù)編譯處理,從而確保 SQL 查詢語句的安全性。
日志打印看到的也不是sql語句,而是通過占位符模式進行內(nèi)容的填充
這里數(shù)據(jù)是int類型的,我們換用String類型數(shù)據(jù)嘗試
結(jié)果:
當(dāng)將#{}替換為${} 再次進行執(zhí)行.報錯了
原因:${}是直接替換字符串的,你輸入的是什么就完全替換
'${username}',才不會報錯,不加單引號sql語句就會出現(xiàn)問題
這樣只能保障不報錯,但是會存在安全性問題
例如登陸場景下:sql注入問題
為了只返回一條數(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 查詢語句將會被替換為:
SELECT * FROM users WHERE username = '' or '1'='1' AND password = '' or '1'='1'
使用不存在的username,不存在的password也能查到用戶信息
這就是使用${key}時的sql注入攻擊.
將${key}替換為#{key}
占位符的方式,不會出現(xiàn)安全性問題
${}在特定場景下,也有優(yōu)點
此時使用#{key}會被錯誤地解析為字符串:
SELECT * FROM userinfo ORDER BY id "desc"
即使設(shè)置了 ord 參數(shù)的值為 ASC 或 DESC,也無法正確地替換該字符串
需要將該 SQL 語句更改為以下形式進行直接替換:
SELECT * FROM userinfo ORDER BY id ${ord}
總結(jié):
1.${key} 主要用于字符串替換,可以方便地將參數(shù)替換為 SQL 語句
使用 ${key} 時需要注意安全性問題,應(yīng)該避免潛在的 SQL 注入攻擊
2.#{key} 主要用于預(yù)處理 SQL 語句,可以有效地防止 SQL 注入攻擊
#{key} 底層原理:
在 MyBatis 中,#{} 在底層的實現(xiàn)中使用了 PreparedStatement 進行參數(shù)的預(yù)處理和類型轉(zhuǎn)換。
當(dāng) MyBatis 執(zhí)行 SQL 語句時,它會將 SQL 語句中的 #{} 占位符替換為 JDBC 的 Placeholder(如 ?),然后生成一個預(yù)編譯的 SQL 語句。這個預(yù)編譯的 SQL 語句會被傳遞給 DriverManager,并由 DriverManager 創(chuàng)建一個 PreparedStatement 對象。
然后,MyBatis 會將查詢參數(shù)的值按照 JDBC 規(guī)范設(shè)置到 PreparedStatement 的參數(shù)位置上,然后執(zhí)行 PreparedStatement 對象的查詢操作,最后將查詢結(jié)果映射成 Java 對象并返回給調(diào)用方。
使用預(yù)處理語句可以提高 SQL 的執(zhí)行效率,避免 SQL 注入等安全問題,并且可以自動進行類型轉(zhuǎn)換,讓開發(fā)人員更加專注于業(yè)務(wù)邏輯的實現(xiàn)。
需要注意的是,由于預(yù)處理語句必須在編譯和執(zhí)行之間進行變量綁定,因此不能在運行時動態(tài)地構(gòu)建 SQL 語句。如果需要在運行時動態(tài)地構(gòu)建 SQL 語句,需要使用 ${} 實現(xiàn)字符串替換,并且需要注意防止 SQL 注入攻擊的問題。
PreparedStatement 是 JDBC 提供的一種接口,用于提高執(zhí)行 SQL 的性能和安全性 PreparedStatement 的特點:
- 預(yù)編譯:PreparedStatement 對象在創(chuàng)建時會將 SQL 語句編譯成一個可執(zhí)行的語句,這樣可以提高 SQL 的執(zhí)行效率。
- 參數(shù)綁定:PreparedStatement 可以在執(zhí)行前設(shè)置參數(shù),可以使用占位符 (?) 或命名參數(shù)(:param)來代替實際的參數(shù)值,從而避免了 SQL 注入攻擊。
- 批處理:PreparedStatement 支持批量處理,可以將多個 SQL 語句一次性提交到數(shù)據(jù)庫執(zhí)行,從而提高數(shù)據(jù)庫處理的效率。
- 預(yù)處理語句緩存:預(yù)編譯語句可以緩存,再次執(zhí)行同樣的 SQL 語句時,可以直接從緩存中獲取已編譯的語句,避免了每次都重新編譯 SQL,從而提高 SQL 的執(zhí)行效率。
修改密碼
運行單元測試
成功修改
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)
在單元測試情況下,我們是不愿意改動數(shù)據(jù)庫的,也就是不污染數(shù)據(jù)庫
默認(rèn)情況下,是會持久化改動數(shù)據(jù)庫的
這里就用到@Transactional注解了
正常執(zhí)行,但不污染數(shù)據(jù)庫,執(zhí)行時開啟事務(wù),執(zhí)行結(jié)束后進行回滾操作,就不會污染數(shù)據(jù)庫
看看數(shù)據(jù)庫中,沒有改變,說明進行了回滾...并且成功驗證了程序,返回修改: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)
下來學(xué)習(xí)刪除操作
//刪除用戶,返回受影響行數(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é)果
因為設(shè)置事務(wù)了,所以測試完成后進行了回滾,不會污染數(shù)據(jù)庫
普通得增加模塊,這里我們不進行回滾了,讓數(shù)據(jù)插入到數(shù)據(jù)庫中
//添加用戶 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ù)庫中新增了一條數(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)
添加用戶得到id
//添加用戶得到ID int addUserGetId(UserEntity user); <insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id"> <!--返回受影響的行數(shù) 和 id--> <!--useGeneratedKeys="true"keyProperty="id"是否自動生成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)
模糊查詢-concat
使用#{key}方式的查詢結(jié)果會報錯
因為傳遞參數(shù)為string類型,會被#{}解析為字符串,因此會自動加上'',那么占位符替換后變成
'%'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來進行拼接
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)
錯誤的模糊匹配查詢會導(dǎo)致索引失效
1.like 'zhang%'
2.like '%zhang'
3.like '%zhang%'
只有第一種查詢會走索引,后面兩個都不會走索引,索引失效了
由于DBA和后端開發(fā)人員代碼風(fēng)格不同,可能會出現(xiàn)不兼容的問題,比如
然后我們執(zhí)行該單元測試
我們發(fā)現(xiàn)JDBC成功執(zhí)行,但是由于password映射不上pwd,pwd的值為空
此時resultType="com.example.demo.entity.UserEntity"就失效了,因為表中字段和類的屬性不一樣了,無法一對一直接映射了
解決方法--<resultMap>
<resultMap> 是 MyBatis 中用于映射查詢結(jié)果集的標(biāo)簽。它能夠解決以下兩個問題:
- 解決列名與屬性名不一致的問題:在數(shù)據(jù)庫中,有些列名可能并不符合 Java 對象中屬性的命名規(guī)范,這時可以通過 <resultMap> 映射來將查詢結(jié)果集中的列名與 Java 對象中屬性名進行映射,使得結(jié)果集與對象之間能夠正確匹配。
- 解決多表關(guān)聯(lián)查詢的問題:在進行多表關(guān)聯(lián)查詢時,查詢結(jié)果集通常會包含多個表中的列,這時可以使用 <resultMap> 將不同表中的列映射到不同的 Java 對象屬性中,從而完成對結(jié)果集的映射轉(zhuǎn)換。
第一步,設(shè)置映射關(guān)系
<resultMap id="BaseMap" type="com.example.demo.entity.UserEntity"> <!--設(shè)置主鍵 property:屬性,類中的 column:字段,數(shù)據(jù)庫表中的 --> <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>
第二步,修改返回類型
<select id="getUserById" resultMap="BaseMap"> select * from userinfo where id = #{uid} </select>
執(zhí)行結(jié)果
還有比較簡單的方式,在sql語句中設(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)
為了對應(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)建文章表實體類
@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; }
擴展類
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 { //查詢文章詳情 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)建測試類
@SpringBootTest class ArticleMapperTest { @Autowired private ArticleMapper articleMapper; @Test void getDetail() { ArticleInfoVO articleInfoVO = articleMapper.getDetail(1); System.out.println(articleInfoVO); } }
執(zhí)行單元測試
JDBC執(zhí)行是正確的,但是返回只有username,并沒有查詢到父類的屬性
其實是lombok出現(xiàn)了問題,打印時沒包含父類的屬性
解決辦法:手動設(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)
查詢一個用戶的所有文章
先將插入一條文章記錄
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)
其他代碼
//查詢一個用戶所有文章 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); //使用多線程的方式,順序可能會不同 list.stream().parallel().forEach(System.out::println); }
執(zhí)行
總結(jié)
到此這篇關(guān)于MyBatis查詢數(shù)據(jù)庫的文章就介紹到這了,更多相關(guān)MyBatis查詢數(shù)據(jù)庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Java和高德地圖API將經(jīng)緯度轉(zhuǎn)換為地理位置信息的步驟
這篇文章詳細(xì)介紹了如何將GPS坐標(biāo)轉(zhuǎn)換為人類可讀的地理位置,介紹了環(huán)境準(zhǔn)備、代碼實現(xiàn)、異常處理及優(yōu)化步驟,首先創(chuàng)建LocationFinder類,實現(xiàn)getLocationFromCoordinates方法,利用高德逆地理編碼API轉(zhuǎn)換坐標(biāo),文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09hashMap擴容時應(yīng)該注意這些死循環(huán)問題
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著hashMap擴容時的死循環(huán)問題展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06Springboot項目中運用vue+ElementUI+echarts前后端交互實現(xiàn)動態(tài)圓環(huán)圖(推薦)
今天給大家?guī)硪黄坛剃P(guān)于Springboot項目中運用vue+ElementUI+echarts前后端交互實現(xiàn)動態(tài)圓環(huán)圖的技能,包括環(huán)境配置及圓環(huán)圖前端后端實現(xiàn)代碼,感興趣的朋友一起看看吧2021-06-06關(guān)于SpringBoot的@ConfigurationProperties注解和松散綁定、數(shù)據(jù)校驗
這篇文章主要介紹了關(guān)于SpringBoot的@ConfigurationProperties注解和松散綁定、數(shù)據(jù)校驗,@ConfigurationProperties主要作用就是將prefix屬性指定的前綴配置項的值綁定到這個JavaBean上?,通過指定的前綴,來綁定配置文件中的配置,需要的朋友可以參考下2023-05-05一文帶你深入了解Java中延時任務(wù)的實現(xiàn)
延時任務(wù)相信大家都不陌生,在現(xiàn)實的業(yè)務(wù)中應(yīng)用場景可以說是比比皆是。這篇文章主要為大家介紹幾種實現(xiàn)延時任務(wù)的辦法,感興趣的可以了解一下2022-11-11解析web.xml中在Servlet中獲取context-param和init-param內(nèi)的參數(shù)
本篇文章是對web.xml中在Servlet中獲取context-param和init-param內(nèi)的參數(shù)進行了詳細(xì)的分析介紹,需要的朋友參考下2013-07-07idea數(shù)據(jù)庫驅(qū)動下載失敗的問題及解決
這篇文章主要介紹了idea數(shù)據(jù)庫驅(qū)動下載失敗的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01