当前位置: 首页 > article >正文

【Spring Boot】深入解析:#{} 和 ${}

1.#{} 和 ${}的使用

1.1数据准备

1.1.1.MySQL数据准备

(1)创建数据库:

CREATE DATABASE mybatis_study DEFAULT CHARACTER SET utf8mb4;

(2)使用数据库

-- 使⽤数据数据
USE mybatis_study;

(3)创建用户表


-- 创建表[⽤⼾表]CREATE TABLE `user_info` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`username` VARCHAR ( 127 ) NOT NULL,`password` VARCHAR ( 127 ) NOT NULL,`age` TINYINT ( 4 ) NOT NULL,`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-⼥ 0-默认',`phone` VARCHAR ( 15 ) DEFAULT NULL,`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; 

(4)添加用户信息

-- 添加⽤⼾信息
INSERT INTO mybatis_study.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_study.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_study.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_study.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );

1.1.2.创建对应的实体类

实体类的属性名与表中的字段名⼀⼀对应

@Data
public class UserInfo {private Integer id;private String username;private String password;private Integer age;private Integer gender;private String phone;private Integer deleteFlag;private Date createTime;private Date updateTime;}

在这里插入图片描述
注意:在实际开发中不管什么实体类都要设置删除标志、创建时间、修改时间

1.2 获取Integer类型

1.2.1 #{}

Mapper接口:

@Mapper
public interface UserInfoMapper {// 获取参数中的 UserId@Select("select * from user_info where id = #{userId} ")UserInfo queryById(@Param("userId") Integer id);

测试代码:

@Slf4j
@SpringBootTest //启动Sring 容器
class UserInfoMapperTest {@Testvoid queryById() {UserInfo result = userInfoMapper.queryById(8);log.info(result.toString());}
}

运行结果:
在这里插入图片描述

通过日志可以发现,进行占位,传的参数进行绑定到占位符。

1.2.2 ${}

Mapper接口:

@Mapper
public interface UserInfoMapper {// 获取参数中的 UserId@Select("select * from user_info where id = ${userId} ")UserInfo queryById(@Param("userId") Integer id);

测试代码:

@Slf4j
@SpringBootTest //启动Sring 容器
class UserInfoMapperTest {@Testvoid queryById() {UserInfo result = userInfoMapper.queryById(8);log.info(result.toString());}
}

运行结果:
在这里插入图片描述

通过日志可以发现,SQL命令是完整的,因为,该方法是把字符串拼接在一起执行的。

1.3 获取String类型

1.3.1 #{}

Mapper接口:

@Mapper
public interface UserInfoMapper {// 获取参数中的 username@Select("select * from user_info where username = #{username} ")List<UserInfo> queryByUsername( String username);

测试代码:

@Slf4j
@SpringBootTest //启动Sring 容器
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryByUsername() {userMapper.queryByUsername("lisi");}
}

运行结果:
在这里插入图片描述

通过日志可以发现,进行占位,传的参数进行绑定到占位符。

1.3.2 ${}

Mapper接口:

@Mapper
public interface UserInfoMapper {// 获取参数中的 username@Select("select * from user_info where username = ${username} ")List<UserInfo> queryByUsername( String username);

测试代码:

@Slf4j
@SpringBootTest //启动Sring 容器
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryByUsername() {userMapper.queryByUsername("lisi");}
}

运行结果:报错
在这里插入图片描述
在这里插入图片描述
从SQL语句中明显的看到WHERE username 后面的字符串没有引号,导致报错。

因为,${}直接把字符内容直接放进SQL语句中而没有加单引号。

修改后的mapper接口:

@Mapper
public interface UserInfoMapper {// 获取参数中的 username@Select("select * from user_info where username = '${username}' ")UserInfo queryByUsername( String username);

在这里插入图片描述

2.#{} 和 ${}的区别

2.1 预编译SQL和即时SQL的执行过程

2.1.1 预编译SQL执行过程

#{}是预编译SQL

第一步:数据库客户端(如 JDBC 驱动)将 SQL 模板发送到数据库服务器。

// SQL模版
PreparedStatement pstmt = connection.prepareStatement("SELECT * FROM user WHERE id = ? AND name = ?");

第二步:SQL 预编译
(1)数据库解析 SQL 模板,生成执行计划(包括语法检查、语义分析、优化等),并缓存该计划。
(2)此时,占位符 ? 的具体值尚未填充,数据库只处理 SQL 的结构。

第三步:客户端通过 PreparedStatement 的方法设置参数值

//参数值以二进制形式单独发送到数据库,不会直接拼接到 SQL 中,避免了 SQL 注入
pstmt.setInt(1, 123);    // 绑定 id
pstmt.setString(2, "Alice"); // 绑定 name

第四步:SQL 执行
(1)数据库使用缓存的执行计划,将绑定参数代入执行计划,直接运行查询或更新操作。
(2)如果相同的 SQL 模板再次执行(仅参数不同),数据库可复用缓存的执行计划,减少编译开销。

2.1.2 即时QL执行过程

${}是即时SQL

第一步:SQL 语句拼接

SELECT * FROM user ORDER BY ${columnName}//如果 columnName = "age"//生成
SELECT * FROM user ORDER BY age; DROP TABLE user;

第二步:SQL 发送到数据库
客户端将拼接好的完整 SQL 字符串通过 Statement 或类似接口发送到数据库

Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);

第三步:SQL解析与编译
语法解析:检查 SQL 语句的语法是否正确。
语义分析:验证表名、列名等是否存在,权限是否足够。
优化:生成执行计划,选择最优的查询路径。

第四步:SQL执行
数据库根据生成的执行计划执行 SQL,完成查询或更新操作。

2.2性能比较

预编译SQL(#{})性能更高:
绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了
在这里插入图片描述
预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译 (只是输⼊的参数不同), 省去了解析优化等过程, 以此来提⾼效率

预编译SQL(#{})更安全(防⽌SQL注⼊):
由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些 SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击。

2.3 排序举例

排序需要用到SQL的关键字ascdesc,把该两个关键字设置为参数时需要用到${},因为#{}会把ascdesc认为是字符串

2.3.1 #{}

Mapper接口:

@Mapper
public interface UserInfoMapper {@Select("select * from userInfo order by username #{flag}")List<UserInfo> findAll(String flag);
}

测试代码

@Slf4j
@SpringBootTest //启动Sring 容器
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userMapper;@Testvoid findAll() {userMapper.findAll("asc");}
}

运行结果:
在这里插入图片描述

2.3.2 #{}

Mapper接口:

@Mapper
public interface UserInfoMapper {@Select("select * from userInfo order by username ${flag}")List<UserInfo> findAll(String flag);
}

测试代码

@Slf4j
@SpringBootTest //启动Sring 容器
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userMapper;@Testvoid findAll() {userMapper.findAll("asc");}
}

运行结果:
在这里插入图片描述

2.4 like 查询

2.4.1 #{}

Mapper接口:

@Mapper
public interface UserInfoMapper {@Select("select * from user_info where username like '%#{s}%'")List<UserInfo> queryLike(String s);
}

测试代码:

@Slf4j
@SpringBootTest //启动Sring 容器
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryLike() {String s = "6";userMapper.queryLike(s);}
}

运行结果:
在这里插入图片描述

把 #{} 改成 可以正确查出来 , 但是 {} 可以正确查出来, 但是 可以正确查出来,但是{}存在SQL注⼊的问题, 所以不能直接使⽤ ${}.解决办法: 使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:

修改后的Mapper接口:

@Mapper
public interface UserInfoMapper {@Select("select * from user_info where username like concat('%',#{s},'%') ")List<UserInfo> queryLike(String s);

运行结果:
在这里插入图片描述

2.4.2 ${}

Mapper接口:

@Mapper
public interface UserInfoMapper {@Select("select * from user_info where username like '%${s}%' ")List<UserInfo> queryLike(String s);
}

测试代码:

@Slf4j
@SpringBootTest //启动Sring 容器
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryLike() {String s = "6";userMapper.queryLike(s);}
}

运行结果:
在这里插入图片描述

3.什么是SQL注入?

SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的⽅法。

举例:
下面定义的接口是由username得到该username的信息

Mapper接口:

@Mapper
public interface UserInfoMapper {// 获取参数中的 username@Select("select * from user_info where username = ${username} ")List<UserInfo> queryByUsername( String username);

可以通过输入' or username='来获取该表中所有人的信息
测试代码:

@Slf4j
@SpringBootTest //启动Sring 容器
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userMapper;@Testvoid queryByUsername() {userMapper.queryByUsername("lisi");}
}

运行结果:
在这里插入图片描述

可以看出来, 查询的数据越界了接口的定义。所以⽤于查询的字段,尽量使⽤#{}预查询的⽅式

SQL注⼊是⼀种⾮常常⻅的数据库攻击⼿段, SQL注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀。

4.数据库连接池

4.1介绍

数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,⽽不是再重新建⽴⼀个.
在这里插入图片描述
没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQL语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源

使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把 Connection归还给连接池.

优点:
1.减少了⽹络开销
2.资源重⽤
3.提升了系统的性能

4.26.2使⽤

常⻅的数据库连接池:
•C3P0
•DBCP
•Druid
•Hikari
⽬前⽐较流⾏的是 Hikari, Druid
Hikari : SpringBoot默认使⽤的数据库连接池
在这里插入图片描述

Hikari 是⽇语"光"的意思(ひかり), Hikari也是以追求性能极致为⽬标

2.Druid
如果我们想把默认的数据库连接池切换为Druid数据库连接池, 只需要引⼊相关依赖即可

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version>
</dependency>

如果SpringBoot版本为2.X, 使⽤druid-spring-boot-starter 依赖

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version>
</dependency>

Druid连接池是阿⾥巴巴开源的数据库连接池项⽬
功能强⼤,性能优秀,是Java语⾔最好的数据库连接池之⼀

相关文章:

【Spring Boot】深入解析:#{} 和 ${}

1.#{} 和 ${}的使用 1.1数据准备 1.1.1.MySQL数据准备 &#xff08;1&#xff09;创建数据库&#xff1a; CREATE DATABASE mybatis_study DEFAULT CHARACTER SET utf8mb4;&#xff08;2&#xff09;使用数据库 -- 使⽤数据数据 USE mybatis_study;&#xff08;3&#xff…...

linux:启动后,ubuntu屏幕变成红色了

屏幕启动后变成 红色背景 通常说明 显卡驱动出了问题&#xff0c;或者是 图形界面加载失败 使用了 fallback 模式。这种现象在 NVIDIA 驱动安装失败或显卡与驱动不兼容时常见。 &#x1f3af; 先给你几个快速修复选项 ✅ 1. 进入 TTY 命令行界面 按下&#xff1a;Ctrl Alt …...

从实验室到产业端:解码 GPU 服务器的八大核心应用场景​

一、深度学习与人工智能的基石​ 在深度学习领域&#xff0c;GPU 服务器的并行计算架构成为训练大规模模型的核心引擎 —— 传统 CPU 集群训练千亿参数模型需数月&#xff0c;而基于某国际知名芯片厂商 H100 的 GPU 服务器可将周期缩短至数周&#xff0c;国内科技巨头 910B 芯…...

java—12 kafka

目录 一、消息队列的优缺点 二、常用MQ 1. Kafka 2. RocketMQ 3. RabbitMQ 4. ActiveMQ 5. ZeroMQ 6. MQ选型对比 适用场景——从公司基础建设力量角度出发 适用场景——从业务场景角度出发 四、基本概念和操作 1. kafka常用术语 2. kafka常用指令 3. 单播消息&a…...

YOLOv8 涨点新方案:SlideLoss FocalLoss 优化,小目标检测效果炸裂!

YOLOv8优化秘籍&#xff1a;用SlideLoss和FocalLoss提升小目标检测精度&#xff08;附代码实战&#xff09;​​ ​&#x1f4cc; 核心问题&#xff1a;YOLOv8在检测小物体时效果不够好&#xff1f;​​ YOLOv8虽然是强大的目标检测模型&#xff0c;但在处理小物体或类别不平…...

数据库-数据类型、约束 和 DQL语言

标题目录 数据类型数字类型INT 型BIGINT 型DOUBLE 类型 字符类型定长字符串变长字符串 日期类型 约束主键约束非空约束唯一性约束检查约束外键约束 DQL 语言WHERE 子句连接多个条件IN (列表)NOT IN (列表)BETWEEN...AND...DISTINCT多字段去重 模糊查询NULL 值判断排序&#xff…...

verilog和system verilog常用数据类型以及常量汇总

int和unsigned 在 Verilog-2001 中&#xff0c;没有 int 和 unsigned 这样的数据类型。这些关键字是 SystemVerilog 的特性&#xff0c;而不是 Verilog-2001 的一部分。 Verilog-2001 的数据类型 在 Verilog-2001 中&#xff0c;支持的数据类型主要包括以下几种&#xff1a; …...

Dify升级-linux环境下使用zip离线安装方式部署升级

Dify安装时Linux服务器到github网络不好&#xff0c;git clone拉去不下来代码。使用本地windows电脑下载zip包形式上传进行了安装。但是随着dfiy版本升级&#xff0c;本地使用最新版本的&#xff0c;也需要进行下升级。参考升级指导以及自己环境情况&#xff0c;升级步骤如下。…...

容器修仙传 我的灵根是Pod 第9章 时空禁术(Job与CronJob)

第三卷&#xff1a;上古遗迹元婴篇 第9章 时空禁术&#xff08;Job与CronJob&#xff09; 极北冰渊深处&#xff0c;万丈冰层下封印着上古禁术「轮回溯光阵」。 林衍的混沌灵根突然结出冰霜——这不是寒冷所致&#xff0c;而是阵法中逸散的时空乱流。冰壁上刻满血色符文&…...

Web3.0的认知补充(去中心化)

涉及开发技术&#xff1a; Vue Web3.js Solidity 基本认知 Web3.0含义&#xff1a; 新一代互联网思想&#xff1a;去中心化及用户为中心的互联网 数据&#xff1a;可读可写可授权 核心技术&#xff1a;区块链、NFT 应用&#xff1a;互联网上应用 NFT &…...

【Python网络爬虫实战指南】从数据采集到反反爬策略

目录 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比 二、实战演示环境配置要求核心代码实现案例1&#xff1a;静态页面抓取&#xff08;电商价格&#xff09;案例2&#xff1a;动态页面抓取&…...

Atlas 800I A2 离线部署 DeepSeek-R1-Distill-Llama-70B

一、环境信息 1.1、硬件信息 Atlas 800I A2 1.2、环境信息 注意&#xff1a;这里驱动固件最好用商业版&#xff0c;我这里用的社区版有点小问题 操作系统&#xff1a;openEuler 22.03 LTS NPU驱动&#xff1a;Ascend-hdk-910b-npu-driver_24.1.rc3_linux-aarch64.run NPU固…...

基于SpringBoot+Vue的影视系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 时代在飞速进步&#xff0c;每个行业都在努力发展现在先进技术&#xff0c;通过这些先进的技术来提高自己的水平和优势&#xff0c;影视推荐系统当然不能排除在外。影视系统是在实际应用和软件工程的开发原理之上&#xff0c;运用Java语言以及Spring Boot、VUE框架进行开…...

搭建Stable Diffusion图像生成系统实现通过网址访问(Ngrok+Flask实现项目系统公网测试,轻量易部署)

目录 前言 背景与需求 &#x1f3af; 需求分析 核心功能 网络优化 方案确认 1. 安装 Flask 和 Ngrok 2. 构建 Flask 应用 3. 使用 Ngrok 实现内网穿透 4. 测试图像生成接口 技术栈 实现流程 优化目标 实现细节 1. 迁移到Flask 2. 持久化提示词 3. 图像下载功能 …...

Java 21 的“无类主”特性:简化编程的第一步

在Java编程中&#xff0c;编写一个简单的“Hello, World!”程序通常需要以下代码&#xff1a; public class HelloWorld {public static void main(String[] args) {System.out.println("Hello, World!");} }这种结构包含了许多对初学者来说难以理解的概念&#xff…...

AI | 最近比较火的几个生成式对话 AI

关注&#xff1a;CodingTechWork 引言 生成式对话 AI 正在迅速改变我们与机器交互的方式&#xff0c;从智能助手到内容创作&#xff0c;其应用范围广泛且深远。本文将深入探讨几款当前热门的生成式对话 AI 模型&#xff0c;包括 Kimi、DeepSeek、ChatGPT、文心一言、通义千问和…...

差分信号抗噪声原理:

差分信号抗噪声原理&#xff1a; 差分信号除了能很好地解决发送和接收参考点电位不同的问题外&#xff0c;差分信号的另一个重要优势就是在一定条件下其抗干扰能力比单端信号更强。对于单端信号传输&#xff0c;外界对它的干扰噪声直接叠加在信号上&#xff0c;接收端直接检测输…...

6 种AI实用的方法,快速修复模糊照片

照片是我们记录生活的重要方式。但有时&#xff0c;由于各种原因&#xff0c;照片会变得模糊&#xff0c;无法展现出我们想要的效果。幸运的是&#xff0c;随着人工智能&#xff08;AI&#xff09;技术的发展&#xff0c;现在有多种方法可以利用 AI 修复模糊照片&#xff0c;让…...

JavaScript 的“积木”:函数入门与实践

引言&#xff1a;告别重复&#xff0c;拥抱模块化 想象一下&#xff0c;你在写代码时发现&#xff0c;有几段逻辑几乎一模一样&#xff0c;需要在不同的地方反复使用。你是选择每次都复制粘贴&#xff0c;还是希望能像搭积木一样&#xff0c;把这段逻辑封装起来&#xff0c;需…...

从入门到精通【MySQL】视图与用户权限管理

文章目录 &#x1f4d5;1. 视图✏️1.1 视图的基本概念✏️1.2 试图的基本操作&#x1f516;1.2.1 创建视图&#x1f516;1.2.2 使用视图&#x1f516;1.2.3 修改数据&#x1f516;1.2.4 删除视图 ✏️1.3 视图的优点 &#x1f4d5;2. 用户与权限管理✏️2.1 用户&#x1f516;…...

C++中的next_permutation全排列函数

目录 什么是全排列用法实现原理自定义比较函数 注意事项相关题目1.AB Problem2.P1088 火星人 什么是全排列 全排列是指从一组元素中按照一定顺序(按字典序排列&#xff09;取出所有元素进行排列的所有可能情况。 例如&#xff0c;对于集合{1,2,3}&#xff0c;它的全排列包括&a…...

修改el-select背景颜色

修改el-select背景颜色 /* 修改el-select样式--直接覆盖默认样式&#xff08;推荐&#xff09; */ ::v-deep .el-select .el-input__inner {background-color: #1d2b72 !important; /* 修改输入框背景色 */color: #fff; } ::v-deep .el-select .el-input__wrapper {background-…...

dockercompose文件仓库

mysql version: 3 # 使用docker-compose的版本&#xff0c;根据需要可以调整# 创建数据目录 # mkdir -p /home/docker/mysql/mysql_data # mkdir -p /home/docker/mysql/mysql_logs # 给予适当的权限&#xff08;确保MySQL容器可以读写这些目录&#xff09; # chmod 777 /ho…...

【C++入门:类和对象】[3]

C入门:类和对象 拷贝构造(拷贝初始化) 拷贝构造是构造函数的重载 class Date { public:Date(int year1,int month1,int day1) { _yearyear; _monthmonth; _dayday; } Date(const Date& d)//(拷贝构造,把d1传参给d)引用传参不改变使用const //注意使用&,不然会无穷递…...

【mdlib】0 全面介绍 mdlib - Rust 实现的 Markdown 工具集

mdlib 是由开发者 bahdotsh 创建的一个多功能 Markdown 工具集合&#xff0c;包含两个主要组件&#xff1a;一个轻量级 Markdown 解析库和一个功能完善的个人 Wiki 系统。该项目完全采用 Rust 实现&#xff0c;兼具高性能与跨平台特性。 核心组件 Markdown 解析库 特性&#…...

今日CSS学习浮动->定位

------------------------------------------------------------------------------------------------------- CSS的浮动 float 属性用于创建浮动框&#xff0c;将其移动到一边&#xff0c;直到左边缘或右边缘触及包含块或另一个浮动框的边缘。 float 属性定义元素在哪个方向浮…...

如何实现Spring Boot应用程序的安全性:全面指南

在现代 Web 开发中&#xff0c;安全性是 Spring Boot 应用程序的核心需求&#xff0c;尤其是在微服务、云原生和公开 API 场景中。Spring Boot 结合 Spring Security 提供了一套强大的工具&#xff0c;用于保护应用程序免受常见威胁&#xff0c;如未经授权的访问、数据泄露、跨…...

YOLOv8融合CPA-Enhancer【提高恶略天气的退化图像检测】

1.CPA介绍 CPA-Enhancer通过链式思考提示机制实现了对未知退化条件下图像的自适应增强&#xff0c;显著提升了物体检测性能。其插件式设计便于集成到现有检测框架中&#xff0c;并在物体检测及其他视觉任务中设立了新的性能标准&#xff0c;展现了广泛的应用潜力。 关于CPA-E…...

Python 项目环境配置与 Vanna 安装避坑指南 (PyCharm + venv)

在进行 Python 项目开发时&#xff0c;一个干净、隔离且配置正确的开发环境至关重要。尤其是在使用像 PyCharm 这样的集成开发环境 (IDE) 时&#xff0c;正确理解和配置虚拟环境 (Virtual Environment) 是避免许多常见问题的关键。本文结合之前安装 Vanna 库时遇到的问题&#…...

第52讲:农业AI + 区块链——迈向可信、智能、透明的未来农业

目录 一、为什么农业需要“AI+区块链”? 二、核心应用场景解读 1. 农产品溯源系统 2. 农业信贷与保险精准评估 3. 农业碳足迹追踪与碳汇交易 三、案例实战分享:智能溯源 + 区块链合约 四、面临挑战与展望 五、总结 在数字农业时代,“AI” 和 “区块链” 是两股不容忽…...