【Spring Boot 快速入門】四、MyBatis

目錄

  • MyBatis(一)入門
    • 簡介
    • MyBatis 入門
    • Lombok
    • MyBatis 基礎操作
      • 數據準備
      • 刪除
      • 預編譯
      • 新增
      • 更新
      • 查詢
    • XML 映射文件

MyBatis(一)入門

簡介

MyBatis 是一款 優秀的持久層框架,它支持 自定義 SQL、存儲過程以及高級映射,是 Java 開發中連接數據庫的常用工具之一,屬于 ORM(對象關系映射)框架 的一種實現形式。

MyBatis 最初是由 Apache 團隊開發的 iBatis 項目,后來由 Google Code 遷移到 GitHub 并更名為 MyBatis。它是一個半自動化的 ORM 框架,開發者自己寫 SQL,MyBatis 負責將 SQL 的執行結果與 Java 對象進行自動映射。

MyBatis 的核心特點:

  • SQL 編寫自由:開發者可以完全控制 SQL,實現靈活的數據庫操作
  • 簡單易用:學習成本低、配置清晰
  • 支持映射關系:支持一對一、一對多等對象映射
  • 動態 SQL:支持 if、choose、where 等標簽,動態拼接 SQL
  • 與 Spring 整合:配合 Spring Boot 使用非常方便
  • 緩存支持:內置一級緩存,支持二級緩存插件擴展

MyBatis 工作原理:

  1. Java 調用 Mapper 接口
  2. MyBatis 根據配置 XML/注解
  3. 執行 SQL
  4. 映射結果
  5. 返回 Java 對象

MyBatis 入門

步驟:

  1. 準備工作(創建工程、數據庫表、實體類)
  2. 引入 MyBatis 相關依賴,配置 MyBatis
  3. 編寫 SQL 語句(注解/XML)

創建工程除了添加 Spring Web 依賴,還要添加 MyBatis Framework 和 MySQL Driver 依賴:

在這里插入圖片描述

連接數據源,選擇 MySQL:

在這里插入圖片描述

填寫用戶名、密碼和要連接的數據庫名,點擊測試連接,成功即可應用:

在這里插入圖片描述

在配置文件 application.properties 中配置數據庫信息:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.username=root
spring.datasource.password=123456

創建數據庫及表:

create database if not exists `mybatis`;
use `mybatis`;
create table if not exists user (id int primary key auto_increment,name varchar(20),age tinyint,gender tinyint comment '1-male, 2-female',phone varchar(20)
)comment '用戶表';
insert into user (name, age, gender, phone) values('趙剛', 18, 1, '12345678901'),('王芳', 19, 2, '12345678902'),('林偉', 20, 1, '12345678903'),('馬麗', 21, 2, '12345678904'),('孫浩', 22, 1, '12345678905');

對應的實體類:

public class User {private Integer id;private String name;private Short age;private Short sex;private String phone;public User() {}public User(Integer id, String name, Short age, Short sex, String phone) {this.id = id;this.name = name;this.age = age;this.sex = sex;this.phone = phone;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Short getAge() {return age;}public void setAge(Short age) {this.age = age;}public Short getSex() {return sex;}public void setSex(Short sex) {this.sex = sex;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", sex=" + sex +", phone='" + phone + '\'' +'}';}
}

創建 mapper 接口(原來的 dao 層):

import com.example.demo.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;@Mapper //在運行時會自動生成該接口的實現類對象(代理對象),并且將該對象交給Spring的IOC容器管理
public interface UserMapper {@Select("select * from user")public List<User> list();
}

在測試類中編寫測試代碼并運行:

import com.example.demo.mapper.UserMapper;
import com.example.demo.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;@SpringBootTest
class Demo2ApplicationTests {@Autowiredprivate UserMapper userMapper;@Testpublic void testListUser() {List<User> userList = userMapper.list();for (User user : userList) {System.out.println(user);}}
}

控制臺顯示:

在這里插入圖片描述

Lombok

Lombok 是一個實用的 Java 類庫,能通過注解的形式自動生成構造器、getter/setter、equals、hashcode、toString 等方法,并可以自動化生成日志變量,簡化 Java 開發、提高效率。

注解作用
@Getter/@Setter為所有的屬性提供 get/set 方法
@ToString會給類自動生成易閱讀的 toString 方法
@EqualsAndHashCode根據類所擁有的非靜態字段自動重寫 equals 方法和 hashCode 方法
@Data提供了更綜合的生成代碼功能(@Getter + @Setter + @ToString + @EqualsAndHashCode)
@NoArgsConstructor為實體類生成無參的構造器方法
@AllArgsConstructor為實體類生成除了 static 修飾的字段之外帶有各參數的構造器方法。

Lombok 依賴:

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>

在一些 IDEA 老的版本中沒有集成 Lombok 插件,需要自行前往插件市場安裝應用。

MyBatis 基礎操作

數據準備

創建數據庫表:

-- 部門管理
create table dept(id int unsigned primary key auto_increment comment '主鍵ID',name varchar(10) not null unique comment '部門名稱',create_time datetime not null comment '創建時間',update_time datetime not null comment '修改時間'
) comment '部門表';insert into dept (id, name, create_time, update_time) values(1,'學工部',now(),now()),(2,'教研部',now(),now()),(3,'咨詢部',now(),now()),(4,'就業部',now(),now()),(5,'人事部',now(),now());-- 員工管理
create table emp (id int unsigned primary key auto_increment comment 'ID',username varchar(20) not null unique comment '用戶名',password varchar(32) default '123456' comment '密碼',name varchar(10) not null comment '姓名',gender tinyint unsigned not null comment '性別, 說明: 1 男, 2 女',job tinyint unsigned comment '職位, 說明: 1 班主任,2 講師, 3 學工主管, 4 教研主管, 5 咨詢師',entrydate date comment '入職時間',dept_id int unsigned comment '部門ID',create_time datetime not null comment '創建時間',update_time datetime not null comment '修改時間'
) comment '員工表';INSERT INTO emp(id, username, password, name, gender, job, entrydate,dept_id, create_time, update_time) VALUES(1,'zhangwei','123456','張偉',1,4,'2000-01-01',2,now(),now()),(2,'liqiang','123456','李強',1,2,'2015-01-01',2,now(),now()),(3,'wangjun','123456','王軍',1,2,'2008-05-01',2,now(),now()),(4,'liuyang','123456','劉洋',1,2,'2007-01-01',2,now(),now()),(5,'chenming','123456','陳明',1,2,'2012-12-05',2,now(),now()),(6,'humin','123456','胡敏',2,3,'2013-09-05',1,now(),now()),(7,'zhuyan','123456','朱妍',2,1,'2005-08-01',1,now(),now()),(8,'guoyan','123456','郭燕',2,1,'2014-11-09',1,now(),now()),(9,'linling','123456','林玲',2,1,'2011-03-11',1,now(),now()),(10,'heqian','123456','何倩',2,1,'2013-09-05',1,now(),now()),(11,'gaoxiang','123456','高翔',1,5,'2007-02-01',3,now(),now()),(12,'liangchao','123456','梁超',1,5,'2008-08-18',3,now(),now()),(13,'luoyi','123456','羅毅',1,5,'2012-11-01',3,now(),now()),(14,'mahui','123456','馬輝',1,2,'2002-08-01',2,now(),now()),(15,'huangyong','123456','黃勇',1,2,'2011-05-01',2,now(),now()),(16,'wupeng','123456','吳鵬',1,2,'2010-01-01',2,now(),now()),(17,'zhenlei','123456','鄭磊',1,NULL,'2015-03-21',NULL,now(),now());

創建實體類:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.time.LocalDate;
import java.time.LocalDateTime;@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {private Integer id;private String username;private String password;private String name;private Short gender;private Short job;private LocalDate entrydate;private Integer deptid;private LocalDateTime createTime;private LocalDateTime updateTime;
}

mapper 接口:

import org.apache.ibatis.annotations.Mapper;@Mapper
public interface EmpMapper {}

后面的操作都是按照以上數據進行

刪除

在 mapper 接口中編寫刪除操作的代碼:

//根據ID刪除數據
@Delete("delete from emp where id=#{id}") // #{} 是 MyBatis 中動態獲取數據的占位符
public int deleteById(Integer id);

在測試類中編寫測試方法的代碼:

@Autowired
private EmpMapper empMapper;
@Test
public void testDelete(){empMapper.deleteById(17);
}

一般這樣寫是沒有返回值,如果需要看是否刪除了數據,可以寫成以下形式;

@Test
public void testDelete(){int deleteNum =empMapper.deleteById(17);System.out.println("刪除了"+deleteNum+"行數據");
}

運行結果如下:
在這里插入圖片描述

預編譯

雖然前面的操作成功執行了,但是我們無法知道底層到底是怎么進行的,這個時候可以通過配置 MyBatis 日志來了解

在配置文件 application.properties 中加入以下配置即可開啟 MyBatis 日志,并輸出到控制臺中:

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

運行測試方法就可以在控制臺得到以下信息:

在這里插入圖片描述

這樣我們就能了解到底層是怎么進行的了

==>  Preparing: delete from emp where id=?
==> Parameters: 17(Integer)

這是 SQL 中的預編譯(Prepared Statement)是數據庫編程中一種常見的優化和防注入方式。它將 SQL 語句的結構與數據參數分開處理,大大提高了執行效率并增強了安全性。

SQL 預編譯是將 SQL 語句在數據庫執行前先進行編譯,生成執行計劃,在實際執行時只需提供參數即可。預編譯過程主要包括:

  1. 解析 SQL 結構
  2. 檢查語法和語義
  3. 生成執行計劃
  4. 緩存該語句(可重復使用)

使用 #{} 的方式,MyBatis 會生成使用 ? 占位符的預編譯 SQL,數據庫只需預編譯一次并可復用執行計劃,后續的不同參數只需要替換 ? 即可;而寫死參數的 SQL,每次都是新的語句,數據庫必須重新預編譯,效率低。

預編譯的優勢:

  1. 性能優化:SQL 結構只編譯一次,多次執行復用執行計劃,效率更高
  2. 防 SQL 注入:參數綁定,不會直接拼接SQL字符串,防止惡意注入
  3. 代碼更簡潔:統一結構 + 參數替換,代碼更清晰

那么什么又是 SQL 注入呢?用以下場景來演示:

  • 用戶登錄功能就是在數據庫表中查找是否有對應的用戶名和密碼

    //根據用戶名和密碼查詢用戶
    @Select("select * from emp where username='zhangwei' and password='123456'")
    public Emp getEmpByUsernameAndPassword();
    

    結果如下,登錄成功:

    在這里插入圖片描述

  • 而現在用戶名隨便輸入,密碼輸入“'or'1'='1”:

    //根據用戶名和密碼查詢用戶
    @Select("select count(*) from emp where username='asfgasgasf' and password=''or'1'='1'")
    public int getEmpByUsernameAndPassword();
    

    結果也是登錄成功:

    在這里插入圖片描述

這種情況就稱為 SQL 注入,出現這種情況的原因是:

  • 在 mapper 接口中寫的 SQL 語句 @Select("select count(*) from emp where username='asfgasgasf' and password=''or'1'='1'") 在數據庫中解析為 SELECT count(*) FROM emp WHERE username='asfgasgasf' AND password='' OR '1'='1',因為 '1'='1' 永遠為真,整個 WHERE 條件恒為真,導致查詢返回整張表的總行數。因此,如果后臺通過 count > 0 判斷用戶是否存在,就會錯誤地認為登錄驗證通過,從而實現 SQL 注入攻擊。

而用了預處理的代碼如下:

//根據用戶名和密碼查詢用戶
@Select("select count(*) from emp where username=#{username} and password=#{password}")
public int getEmpByUsernameAndPassword(String username,String password);@Test
public void testGetEmpByUsernameAndPassword(){int count = empMapper.getEmpByUsernameAndPassword("zhangsan","'or'1'='1");System.out.println(count);
}

運行測試結果如下:

在這里插入圖片描述

顯而易見,'or'1'='1 是以一個整體來替換 ?,不會當作 SQL 語法解析,也就無法注入了

參數占位符;

語法格式特點及說明使用時機
#{...}執行 SQL 時,會將#{...}替換為?,生成預編譯 SQL,會自動設置參數值,可有效防止 SQL 注入參數傳遞場景,一般參數傳遞都使用#{...}
${...}拼接 SQL,直接將參數拼接到 SQL 語句中,存在 SQL 注入問題對表名、列名進行動態設置等場景(需謹慎,做好校驗避免注入風險 )

新增

mapper 接口的代碼:

//新增員工
@Insert("insert into emp(username,password,name,gender,job,entrydate,dept_id,create_time,update_time) " +"values(#{username},#{password},#{name},#{gender},#{job},#{entryDate},#{deptId},#{createTime},#{updateTime})")
public void insert(Emp emp);

測試類中的代碼:

@Test
public void testInsert(){Emp emp = new Emp();emp.setUsername("張三");emp.setPassword("123456");emp.setName("張三");emp.setGender((short) 1);emp.setJob((short) 1);emp.setEntryDate(LocalDate.now());emp.setDeptId(1);emp.setCreateTime(LocalDateTime.now());emp.setUpdateTime(LocalDateTime.now());empMapper.insert(emp);
}

運行結果如下:

在這里插入圖片描述

主鍵返回:在數據添加成功后,需要獲取插入數據庫數據的主鍵。如:添加套餐數據時,還需要維護套餐菜品關系表數據

只需要加上 @Options 注解即可:

//新增員工
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into emp(username,password,name,gender,job,entrydate,dept_id,create_time,update_time) " +"values(#{username},#{password},#{name},#{gender},#{job},#{entryDate},#{deptId},#{createTime},#{updateTime})")
public void insert(Emp emp);

運行結果如下:

在這里插入圖片描述

更新

mapper 接口的代碼:

//更新員工
@Update("update emp set username=#{username},password=#{password},name=#{name},gender=#{gender},job=#{job}, entrydate=#{entryDate},dept_id=#{deptId},update_time=#{updateTime} where id=#{id}")
public void update(Emp emp);

測試類中的代碼:

@Test
public void testUpdate(){Emp emp = new Emp();emp.setId(19);emp.setUsername("lisi(update)");emp.setPassword("123456");emp.setName("李四(update)");emp.setGender((short) 1);emp.setJob((short) 2);emp.setEntryDate(LocalDate.now());emp.setDeptId(1);emp.setCreateTime(LocalDateTime.now());emp.setUpdateTime(LocalDateTime.now());empMapper.update(emp);
}

運行結果如下:

在這里插入圖片描述

查詢

mapper 接口的代碼:

//根據ID查詢員工
@Select("select * from emp where id=#{id}")
public Emp getById(Integer id);

測試類中的代碼:

@Test
public void testGetById(){Emp emp = empMapper.getById(1);System.out.println(emp);
}

運行結果如下:

在這里插入圖片描述

從結果中會發現,最后三個字段在數據庫表中明明是有數據,卻沒有被獲取到,這是因為 MyBatis 數據封裝的原因。

數據封裝:

  • 實體類屬性名和數據庫表查詢返回的字段名一致,MyBatis 會自動封裝。
  • 如果實體類屬性名和數據庫表查詢返回的字段名不一致,不能自動封裝。

解決方法:

  1. 給字段起別名:

    //根據ID查詢員工
    @Select("select id, username, password, name, gender, job, entrydate, dept_id deptId, create_time createTime, update_time updateTime from emp where id=#{id}")
    public Emp getById(Integer id);
    
  2. 通過 @Results,@Result 注解手動映射封裝

    //根據ID查詢員工
    @Results({@Result(column = "dept_id", property = "deptId"),@Result(column = "create_time", property = "createTime"),@Result(column = "update_time", property = "updateTime")
    })
    @Select("select * from emp where id=#{id}")
    public Emp getById(Integer id);
    
  3. 開啟 MyBatis 的駝峰命名自動映射開關(推薦,但是類中的屬性名必須要是駝峰命名,數據庫表字段名必須要是 _ 命名)

    //application.properties
    mybatis.configuration.map-underscore-to-camel-case=true
    

    運行結果如下:

    在這里插入圖片描述

以上根據 ID 查詢較為簡單,而下面的條件查詢則較為復雜。

mapper 接口的代碼:

//條件查詢員工
@Select("select * from emp where name like '%${name}%' and gender=#{gender} and entrydate between #{begin} and #{end} order by update_time desc")
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

測試類中的代碼:

@Test
public void testList(){List<Emp> list = empMapper.list("張", (short) 1, LocalDate.of(2000, 1, 1), LocalDate.now());System.out.println(list);
}

運行結果如下:

在這里插入圖片描述

但是接口代碼中使用的是 ${},存在 SQL 注入的問題,可以使用 SQL 中的 concat 函數來進行拼接:

//條件查詢員工
@Select("select * from emp where name like concat('%',#{name},'%') and gender=#{gender} and entrydate between #{begin} and #{end} order by update_time desc")
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

這樣就能使用 #{} 來解決 SQL 注入的問題。

XML 映射文件

XML 映射文件規范:

  • XML 映射文件的名稱與 mapper 接口名稱一致,并且將 XML 映射文件和 mapper 接口放置在相同包下(同包同名)
  • XML 映射文件的 namespace 屬性為 mapper 接口全限定名一致
  • XML 映射文件中 SQL 語句的 id 與 mapper 接口中的方法名一致,并保持返回類型一致

在 resources 包下創建 mapper 接口的同名包:

在這里插入圖片描述

在新創建的包下創建 mapper 接口的同名 XML 文件 EmpMapper.xml,并添加以下約束:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">

mapper 接口中的方法為:

public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

EmpMapper.xml 中寫 SQL 語句:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.EmpMapper"><select id="list" resultType="com.example.demo.pojo.Emp"><!--resultType 是單條記錄封裝的類型,要用對應實體類的全類名-->select * from emp where name like concat('%',#{name},'%') and gender=#{gender} and entrydate between #{begin} and #{end} order by update_time desc</select>
</mapper>

EmpMapper.xml 中的各項屬性要與 mapper 接口中的一致:

在這里插入圖片描述
如果有不一致的,則 XML 映射文件無法匹配上對應的 mapper 接口方法。

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

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

相關文章

Spring IOC 基于Cglib實現含構造函數的類實例化策略

作者&#xff1a;小凱 分享、讓自己和他人都能有所收獲&#xff01; 一、前言 技術成長&#xff0c;是對場景設計細節不斷的雕刻&#xff01; 你覺得自己的技術什么時候得到了快速的提高&#xff0c;是CRUD寫的多了以后嗎&#xff1f;想都不要想&#xff0c;絕對不可能&#xf…

composer 常用命令

### 設置鏡像源全局設置composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/當個項目設置composer config repo.packagist composer https://mirrors.aliyun.com/composer/恢復官方源composer config -g --unset repos.packagist### 常用源阿里云…

【python】Python爬蟲入門教程:使用requests庫

Python爬蟲入門教程&#xff1a;使用requests庫 爬蟲是數據獲取的重要手段&#xff0c;下面我將通過一個完整的示例&#xff0c;教你如何使用Python的requests庫編寫一個簡單的爬蟲。我們將以爬取豆瓣電影Top250為例。 【python】網絡爬蟲教程 - 教你用python爬取豆瓣電影 Top…

OpenCV圖像縮放:resize

圖像縮放是圖像處理中的基礎操作之一。無論是圖像預處理、數據增強還是圖像金字塔構建&#xff0c;cv::resize 都是我們最常用的函數之一。但你是否注意到&#xff0c;在 OpenCV 中同時還存在一個名為 cv::Mat::resize 的方法&#xff1f;這兩個函數雖然名字類似&#xff0c;但…

汽車、航空航天、適用工業虛擬裝配解決方案

一、現狀在制造業數字化轉型浪潮中&#xff0c;傳統裝配過程仍面臨諸多挑戰&#xff1a;物理樣機試錯成本高、裝配周期冗長、工藝優化依賴經驗、跨部門協作效率低下……如何打破“試錯-返工”的惡性循環&#xff1f;目前總裝工藝通過DELMIA、NX、Creo等工程軟件進行工藝裝配驗證…

頁面跳轉和前端路由的區別

傳統方式&#xff1a;通過改變瀏覽器地址欄的 URL 來實現window.location.href /new-page<a href"/new-page">跳轉到新頁面</a>會導致整個頁面重新加載會觸發瀏覽器向服務器發送新的請求頁面狀態不會保留&#xff0c;所有資源重新加載可以避免新上線的內…

C/C++核心知識點詳解

C/C核心知識點詳解 1. 變量的聲明與定義&#xff1a;內存分配的本質區別 核心概念 在C/C中&#xff0c;變量的聲明和定義是兩個完全不同的概念&#xff1a; 聲明&#xff08;Declaration&#xff09;&#xff1a;告訴編譯器變量的名稱和類型&#xff0c;但不分配內存空間定義&a…

物聯網發展:從概念到應用的演變歷程

物聯網的發展歷程是一部技術革新與社會需求共同驅動的進化史&#xff0c;其演變可劃分為概念萌芽、技術積累、應用拓展和智能融合四個階段&#xff0c;每個階段均以關鍵技術突破或社會需求變革為標志&#xff0c;最終形成萬物互聯的智能生態。以下是具體演變歷程&#xff1a;一…

一個人開發一個App(數據庫)

后端要保存數據&#xff0c;我還是選擇了關系型數據庫Mysql, 因為其它的不熟悉。 flutter端這次我選擇的是ObjectBox&#xff0c;以前都是直接用的sqlite3&#xff0c;看對比ObjectBox效率比sqlite3高許多&#xff0c;這次前端為了用戶體驗&#xff0c;我需要緩存數據&#xff…

天銘科技×藍卓 | “1+2+N”打造AI驅動的汽車零部件行業智能工廠

7月24日&#xff0c;杭州天銘科技股份有限公司&#xff08;簡稱 “天銘科技”&#xff09;與藍卓數字科技有限公司&#xff08;簡稱 “藍卓”&#xff09;簽訂全面戰略合作協議。天銘科技董事長張松、副總經理艾鴻冰&#xff0c;藍卓副董事長譚彰等領導出席簽約儀式&#xff0c…

技術復盤報告:Vue表格中多行文本字段數據保存丟失問題

1. 問題背景 在一個基于 Vue 2.0 和 ElementUI 的復雜數據維護頁面中&#xff0c;用戶報告了一個偶發但嚴重的問題&#xff1a;在表格中編輯一個多行文本&#xff08;textarea&#xff09;字段時&#xff0c;輸入的內容有時會在點擊“保存”后丟失。 具體表現&#xff1a; 前端…

#C語言——學習攻略:深挖指針路線(四)--字符指針變量,數組指針變量,二維數組傳參的本質,函數指針變量,函數指針數組

&#x1f31f;菜鳥主頁&#xff1a;晨非辰的主頁 &#x1f440;學習專欄&#xff1a;《C語言學習》 &#x1f4aa;學習階段&#xff1a;C語言方向初學者 ?名言欣賞&#xff1a;"暴力解法是上帝給的&#xff0c;優化解法是魔鬼教的。" 目錄 1. 字符指針變量 1.1 使…

SpringBoot收尾+myBatis plus

一、數據傳遞返回值為:字符串package com.apesource.springboot_web_04.controller;import com.apesource.springboot_web_04.pojo.Emp; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;/*** 返回值為:字符…

基于 Spring Boot 實現動態路由加載:從數據庫到前端菜單的完整方案

在后臺管理系統中&#xff0c;不同用戶角色往往擁有不同的操作權限&#xff0c;對應的菜單展示也需動態調整。動態路由加載正是解決這一問題的核心方案 —— 根據登錄用戶的權限&#xff0c;從數據庫查詢其可訪問的菜單&#xff0c;封裝成前端所需的路由結構并返回。本文將詳細…

VitePress學習-自定義主題

VitePress-自定義主題 代碼倉庫 基礎了解 初始化項目的時候選擇 custom theme 運行后會發現頁面挺丑的。 如果想要用默認主題怎么辦呢&#xff0c;修改Layout。 使用默認主題的Layout <script setup lang"ts"> import { useData } from vitepress; impo…

【GEO從入門到精通】生成式引擎與其他 AI 技術的關系

2.1.3 生成式引擎與其他 AI 技術的關系生成式引擎作為人工智能領域的創新力量&#xff0c;與其他 AI 技術緊密相連&#xff0c;相互促進&#xff0c;共同推動 生成式引擎優化&#xff08;GEO&#xff09; 的發展。這些技術使生成式引擎能夠為消費者提供更加個性化和精準的內容。…

JAVAEE--4.多線程案例

設計模式1.單例模式1.1餓漢模式1.2懶漢模式(單線程版)1.3懶漢模式(多線程版本)1.4懶漢模式(多線程版本進階版)2.阻塞隊列3.定時器4.線程池1.單例模式設計模式是"軟性約束",不是強制的,可以遵守也可以不遵守,按照設計模式寫代碼使代碼不會太差框架是"硬性約束&qu…

量化感知訓練(QAT)流程

WHAT&#xff1a;量化感知訓練&#xff08;Quantization-Aware Training, QAT&#xff09; 是一種在模型訓練階段引入量化誤差的技術。它的核心思想是&#xff1a;通過在前向傳播時插入“偽量化節點”引入量化誤差&#xff0c;將權重和激活模擬為低精度&#xff08;如 int8&…

docker 用于將鏡像打包為 tar 文件

docker save 是 Docker 中用于將鏡像打包為 tar 文件的命令&#xff0c;常用于鏡像的備份、遷移或離線傳輸。以下是其核心用法和注意事項&#xff1a;一、基本語法bashdocker save [選項] IMAGE [IMAGE...] > 文件名.tar # 或 docker save -o 文件名.tar IMAGE [IMAGE...]IM…

設計模式(六)創建型:單例模式詳解

設計模式&#xff08;六&#xff09;創建型&#xff1a;單例模式詳解單例模式&#xff08;Singleton Pattern&#xff09;是 GoF 23 種設計模式中最簡單卻最常被誤用的創建型模式。其核心價值在于確保一個類在整個應用程序生命周期中僅存在一個實例&#xff0c;并提供一個全局訪…