目录

  • 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;并提供一个全局访…