Flutter笔记:用于ORM的Floor框架简记

Flutter笔记
用于ORM的Floor框架简记

本文地址:https://blog.csdn.net/qq_28550263/article/details/133377191


【介绍】:最近想找用于Dart和Flutter的ORM框架,偶然间发现了Floor,觉得还不错,做一些记录。


  • 1. Floor 框架概述
    • 1.1 框架简介
    • 1.2 框架的构成
    • 1.3 安装
    • 1.4 架构体系
    • 2. 实体(Entity)
      • 支持的类型
      • 主键
      • 外键
      • 索引
      • 忽略字段
      • 继承
    • 数据库视图(Database Views)
  • 3. 数据访问对象(DAO - Data Access Object)
  • 4. 创建数据库
  • 5. 运行代码生成器
  • 6. 使用生成的代码


1. Floor 框架概述

1.1 框架简介

Floor 框架是一个用于在 Flutter 应用程序中使用 SQLite 数据库的开源库。它允许开发者轻松地将本地数据库集成到其应用程序中,并提供了一种类型安全、高效和易于使用的方法来执行数据库操作。

1.2 框架的构成

Floor 框架由多个关键模块组成,每个模块都有特定的功能,协同工作以实现在 Flutter 应用程序中使用 SQLite 数据库的目标。以下是 Floor 框架的主要模块以及它们的作用:

  1. floor(运行时库):

    • 作用: floor 是 Floor 框架的运行时库,包含用于运行时数据库操作的类和方法。
    • 关键类和方法:
    • FloorDatabase:抽象类,表示数据库的根类,生成的数据库类需要扩展它。
    • @Database:用于标记生成的数据库类,指定数据库版本号、实体列表和其他配置信息。
    • databaseBuilder:用于创建数据库实例的方法。
    • 数据库操作方法:生成的数据库类中包含与数据库表的交互方法,例如插入、查询、更新等。
  2. floor_generator(代码生成器):

    • 作用: floor_generator 是 Floor 框架的代码生成器模块,它会根据实体和 DAO 的定义生成数据库访问代码。
    • 生成的代码: 该模块生成包括数据库类、DAO 类以及其他与数据库交互相关的代码,使开发者能够轻松执行数据库操作,而无需手动编写大量的重复代码。
  3. floor_annotation(Floor 框架中的注解)

    • 作用: floor_annotation是Floor 框架中的注解。这些注解用于标记和配置数据库实体(Entities)、数据访问对象(DAOs)以及数据库操作,以便 Floor 框架的代码生成器 floor_generator 可以根据这些注解生成相应的数据库访问代码。
    • floor_annotation 提供了一种类型安全的方式来配置和定义数据库操作,并允许开发者在 Dart 代码中使用注解来描述数据库模型和操作,而无需手动编写大量的重复代码。
    • 例如:
      • @entity:用于标记 Dart 类,表示它是一个数据库实体(Entity)。实体类通常与数据库表相对应,用于定义表的结构和字段。
      • @dao:用于标记抽象类或接口,表示它是数据访问对象(DAO)。DAO 类包含了与数据库表相关的操作方法,如插入、查询、更新和删除。

等等

  1. sqflite(数据库驱动,由floor自动调用):
    • 作用: Floor 框架依赖于 sqflite 插件,它提供了在 Flutter 中与 SQLite 数据库进行底层交互的功能。
    • sqflite 插件负责管理 SQLite 数据库文件、执行原始 SQL 查询和处理数据库事务。

这些模块协同工作,使开发者能够轻松地在 Flutter 应用程序中集成和操作 SQLite 数据库。通过定义实体、DAO 和数据库,然后运行代码生成器,开发者可以获得类型安全、高效和易于维护的数据库访问代码,从而实现数据持久化和本地数据存储的需求。

  1. build_runner:build_runner 通常与 floor_generator 一起使用,以自动生成与数据库操作相关的 Dart 代码。

1.3 安装

flutter pub add floor
flutter pub add floor_generator build_runner --dev

其中:

  • floor: 包含了将在应用程序中使用的所有代码。
  • floor_generator: 包含了生成数据库类的代码。
  • build_runner: 允许以具体的方式生成源代码文件。

将在你的yaml中看到:

dependencies:flutter:sdk: flutterfloor: ^1.4.0dev_dependencies:floor_generator: ^1.4.0build_runner: ^2.1.2

版本可能随时间变化

1.4 架构体系

存储和访问数据的组件是 实体数据访问对象(DAO) 和 数据库

  • 一个 实体 代表一个 持久类,也就是一个 数据库表
  • Dao管理对实体的访问,并负责内存中 对象表行 之间的 映射
  • 数据库是底层 SQLite 数据库的中心访问点。它 保存Dao,除此之外,还负责 初始化数据库及其 模式。

https://raw.githubusercontent.com/vitusortner/floor/develop/img/floor-architecture.png

2. 实体(Entity)

一个实体是一个持久化类。Floor 自动创建内存中对象与数据库表行之间的映射。可以通过向 Entity 注释添加可选值来向 Floor 提供自定义元数据。它具有表名 tableName 的附加属性,该属性打开了为特定实体使用自定义名称的可能性,而不是使用类名。foreignKeys 允许向实体添加外键。有关如何使用这些内容的更多信息,请参阅外键部分。还支持索引。可以通过将索引添加到实体的 indices 值来使用它们。

  • @entity 注解用于标记了该类为持久类;
  • @PrimaryKey 将类的属性标记为主键列。该属性必须是 int 类型。当启用 autoGenerate 时,SQLite 可以自动生成该值;
  • 必须通过将 @primaryKey 注解添加到一个 int 属性上为你的表添加一个主键,例如:
    // entity/person.dartimport 'package: floor/floor.dart';
    class Person {final int id;final String name;Person(this.id, this.name);
    }
    
  • @ColumnInfo 使单个表列的自定义映射变得可能。使用该注释,可以为列指定自定义名称。如果要将表的列设置为可空,请将实体字段标记为可空;
  • Floor 自动使用实体类中定义的第一个构造函数来从数据库行创建内存中对象

支持的类型

Floor 实体可以保存以下 Dart 类型的值,这些类型与它们对应的 SQLite 类型相互映射。

  • int - INTEGER
  • double - REAL
  • String - TEXT
  • bool - INTEGER(0 = false,1 = true)
  • Uint8List - BLOB
  • enum - INTEGER(按索引 0…n 记录)

如果要存储可以由上述类型之一表示的复杂 Dart 对象,请查看类型转换器。

主键

每当需要复合主键(例如 n-m 关系)时,设置主键的语法与前面提到的设置主键的方式不同。不是使用 @PrimaryKey 注释字段,而是使用 @Entity 注释的 primaryKey 属性。它接受一个由列名组成的列表,这些列名组成复合主键。

(primaryKeys: ['id', 'name'])
class Person {final int id;final String name;Person(this.id, this.name);
}

外键

向引用实体的 Entity 注释添加 ForeignKeys 列表。childColumns 定义当前实体的列,而 parentColumns 定义父实体的列。在 onUpdate 和 onDelete 属性中定义外键操作后,可以触发外键操作。

// Dog 类,表示数据库中的狗实体
(tableName: 'dog', // 指定表名为 'dog'foreignKeys: [ForeignKey(childColumns: ['owner_id'], // 子列名为 'owner_id'parentColumns: ['id'], // 父列名为 'id'entity: Person, // 引用的实体为 Person 类)],
)
class Dog {() // PrimaryKey 注解,将 id 标记为主键列final int id;final String name; // 名称(name: 'owner_id') // ColumnInfo 注解,自定义列名为 'owner_id'final int ownerId; // 所有者的 ID// 构造函数,接受 id、name 和 ownerId 参数Dog(this.id, this.name, this.ownerId);
}
  • @Entity 注解标记了 Dog 类,指定了表名为 ‘dog’,同时指定了一个外键(foreign key),该外键将在 ‘owner_id’ 列上创建,它引用了另一个实体 Person。这意味着在数据库中将创建名为 ‘dog’ 的表,并在 ‘owner_id’ 列上创建一个外键,以确保数据的完整性。
  • @PrimaryKey 注解标记了 id 字段作为主键列,这意味着它在数据库中将用作主键。
  • @ColumnInfo 注解标记了 ownerId 字段,并自定义了列名为 ‘owner_id’,这允许在数据库表中使用不同的列名,而不是默认的字段名。
  • 构造函数接受 idnameownerId 参数,并用于创建 Dog 实例。

这些注释和注释说明了 Dog 类的结构,以及如何配置表名、外键和自定义列名。

索引

索引有助于加速查询、连接和分组操作。有关 SQLite 索引的更多信息,请参阅官方文档。要在 floor 中创建索引,请将索引列表添加到 @Entity 注释。下面的示例演示了如何在实体的 custom_name 列上创建索引。

此外,可以使用其 name 属性命名索引。要将索引设置为唯一索引,请使用 unique 属性。

// Person 类,表示数据库中的个人实体
(tableName: 'person', indices: [Index(value: ['custom_name'])])
class Person { // PrimaryKey 注解,将 id 标记为主键列final int id;(name: 'custom_name') // ColumnInfo 注解,自定义列名为 'custom_name'final String name; // 姓名// 构造函数,接受 id 和 name 参数Person(this.id, this.name);
}
  • @Entity 注解标记了 Person 类,指定了表名为 ‘person’,同时指定了一个索引,该索引将在 ‘custom_name’ 列上创建。这意味着在数据库中将创建名为 ‘person’ 的表,并在 ‘custom_name’ 列上创建一个索引,以提高查询性能。
  • @primaryKey 注解标记了 id 字段作为主键列,这意味着它在数据库中将用作主键。
  • @ColumnInfo 注解标记了 name 字段,并自定义了列名为 ‘custom_name’,这允许在数据库表中使用不同的列名,而不是默认的字段名。
    构造函数接受 id 和 name 参数,并用于创建 Person 实例。

忽略字段

默认情况下,实体的 getter、setter 和所有静态字段都会被忽略,并因此从库的映射中排除。如果要进一步忽略字段,应使用 @ignore 注释,并按以下示例中所示应用它。

// 人员类,表示数据库中的人员实体
class Person { // PrimaryKey 注解,将 id 标记为主键列final int id;final String name; // 姓名 // Ignore 注解,指定 nickname 字段将被忽略String nickname; // 昵称// 默认情况下被忽略的字段,不会包含在库的映射中String get combinedName => "$name ($nickname)";// 构造函数,接受 id 和 name 参数Person(this.id, this.name);
}

继承

与 Dao 一样,实体(和数据库视图)可以从共同的基类继承并使用它们的字段。实体只需扩展基类。这个构造将被视为如果基类的所有字段都是实体的一部分,这意味着数据库表将包含实体和基类的所有列。

基类不必为类单独注释。它的字段可以像正常的实体列一样进行注释。外键和索引必须在实体中声明,不能在基类中定义。

// 基础对象类,用于继承到其他实体
class BaseObject {// 主键标记,指定 id 作为主键列()final int id;// ColumnInfo 注解,用于自定义列名 create_time(name: 'create_time')final String createTime;// ColumnInfo 注解,用于自定义列名 update_time(name: 'update_time')final String updateTime;// 构造函数,接受 id、updateTime 和可选的 createTime 参数BaseObject(this.id,this.updateTime, {String createTime,}) : this.createTime = createTime ?? DateTime.now().toString();// 重写父类的 props 方法,通常用于数据比较List<Object> get props => [];
}// Comment 类,继承自 BaseObject 类
(tableName: 'comments') // Entity 注解,指定表名为 'comments'
class Comment extends BaseObject {final String author; // 评论作者final String content; // 评论内容// 构造函数,接受 author、可选的 id、content、createTime 和 updateTime 参数Comment(this.author, {int id,this.content = '', // 默认为空字符串String createTime,String updateTime,}) : super(id, updateTime, createTime: createTime); // 调用父类构造函数,传递 id、updateTime 和 createTime 参数
}

数据库视图(Database Views)

如果希望定义返回与实体不同类型的静态 SELECT 语句,最佳选择是使用 @DatabaseView。数据库视图可以被理解为虚拟表,可以像真实表一样进行查询。

在 Floor 中,数据库视图的定义和使用方式与实体类似,主要区别在于只读访问,这意味着无法执行更新、插入和删除操作。与实体类似,如果未设置 viewName,则使用类名。

('SELECT distinct(name) AS name FROM person', viewName: 'name')
class Name {final String name;Name(this.name);
}

数据库视图不具有任何外键、主键或索引。相反,您应该手动定义与您的语句匹配的索引,并将它们放入涉及实体的 @Entity 注释中。

与实体类似,setter、getter 和静态字段将自动被忽略(与实体类似),您可以使用 @ignore 注解来指定要忽略的附加字段。

在代码中定义数据库视图后,您需要将它添加到 @Database 注释的 views 字段中:

(version: 1, entities: [Person], views: [Name])
abstract class AppDatabase extends FloorDatabase {// DAO 获取器
}

然后,您可以通过 DAO 函数查询视图,就像查询实体一样。

数据库视图可以像实体一样从基类继承共同字段。

现在可以从 DAO 方法返回一个 Stream 对象,该方法查询数据库视图。但是,它将在整个数据库中的任何 @update@insert@delete 事件触发时发出,这可能会对运行时产生相当大的负担。请仅在明白自己在做什么时才添加它!这主要是因为检测哪些实体涉及到数据库视图的复杂性所致。

3. 数据访问对象(DAO - Data Access Object)

这个组件负责管理对底层的 SQLite 数据库的访问。抽象类包含了查询数据库的方法签名,这些方法必须返回 Future 或 Stream。

你可以通过将 @Query 注解添加到方法上来定义查询。SQL 语句必须放在括号中。方法必须返回你查询的实体的 Future 或 Stream。
@insert 标记了一个方法作为插入方法。

// dao/person_dao.dartimport 'package:floor/floor.dart';
abstract class PersonDao {// 查询并返回所有的 Person 实体对象列表('SELECT * FROM Person')Future<List<Person>> findAllPersons();// 根据提供的 id 查询并返回匹配的 Person 实体对象// 这是一个异步数据流,当数据发生变化时,会自动更新('SELECT * FROM Person WHERE id = :id')Stream<Person?> findPersonById(int id);// 将提供的 Person 对象插入到数据库中// 这是一个异步操作,不返回任何数据Future<void> insertPerson(Person person);
}

4. 创建数据库

它必须是一个继承自 FloorDatabase 的抽象类。此外,必须在类的签名中添加 @Database()。确保将创建的实体添加到 @Database 注解的 entities 属性中。为了使生成的代码工作,还需要添加列出的导入项。

确保在这个文件的导入项下面添加 part ‘database.g.dart’;。重要的是要注意 ‘database’ 必须与数据库定义的文件名进行交换。在这种情况下,文件名命名为 database.dart。

// database.dartimport 'dart:async';
import 'package:floor/floor.dart';
import 'package:sqflite/sqflite.dart' as sqflite; import 'dao/person_dao.dart'; 
import 'entity/person.dart'; part 'database.g.dart'; // Floor代码生成器将在这里生成相关的代码(version: 1, entities: [Person]) // 声明数据库版本号和包含的实体类
abstract class AppDatabase extends FloorDatabase {PersonDao get personDao; // 获取PersonDao实例,用于执行数据库操作
}

5. 运行代码生成器

使用以下命令来运行生成器:

flutter packages pub run build_runner build

上面的命令用于在 Flutter 项目中运行代码生成工具以手动构建生成的代码。build命令 只会运行一次生成过程,然后退出。还可以在 Flutter 项目中使用 build_runner 模块的观察模式,以便在开发过程中自动更新生成的代码。如果想要在文件更改时自动运行它,请使用以下命令:

flutter packages pub run build_runner watch

6. 使用生成的代码

要获取数据库的实例,使用生成的 $FloorAppDatabase 类,它允许访问数据库生成器。名称由 $Floor 和数据库类名组成。传递给 databaseBuilder() 的字符串将是数据库文件的名称。要初始化数据库,调用 build() 并确保等待结果。

为了检索 PersonDao 实例,只需在数据库实例上调用 persoDao getter 即可。
可以参考下面的代码。

// 使用数据库生成器创建数据库实例,传递数据库文件的名称('app_database.db')
final database = await $FloorAppDatabase.databaseBuilder('app_database.db').build();// 从数据库实例中获取 PersonDao 实例
final personDao = database.personDao;// 创建一个 Person 对象
final person = Person(1, 'Frank');// 将新的 Person 对象插入到数据库中
await personDao.insertPerson(person);// 通过 ID 查找数据库中的 Person 记录
final result = await personDao.findPersonById(1);// 输出查找到的 Person 记录
print('Found Person: $result');

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/144666.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

wsl2 更新报错问题解决记录

1、问题 win10 中安装的 wsl2&#xff0c;启动 docker desktop 时提示 wsl2 有问题&#xff1a; 于是点击推荐的地址连接到微软&#xff0c;下载 wsl2 的更新文件。之后运行&#xff0c;又报错&#xff1a; 更新被卡住。 2、解决方法 WinR 输入 cmd 打开命令行窗口&#x…

Spring面试题7:面试官:Spring是如何进行异常处理的呢?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring是如何进行异常处理的呢? Spring通过异常处理机制来处理应用程序中的异常。它提供了多种方式来处理异常,包括以下几种: 声明式事务管理:…

目标检测如何演变:从区域提议和 Haar 级联到零样本技术

目录 一、说明 二、目标检测路线图 2.1 路线图&#xff08;一般&#xff09; 2.2 路线图&#xff08;更传统的方法&#xff09; 2.3 路线图&#xff08;深度学习方法&#xff09; 2.4 对象检测指标的改进 三、传统检测方法 3.1 维奥拉-琼斯探测器 (2001) 3.2 HOG探测器…

Video Caption / 视频字幕:常用指标(BELU-4,ROUGE-L,METEOR,CIDEr,SPICE)和数据集总结

本文作为入门Video Caption / 视频字幕 的随笔记录&#xff0c;用于查漏补缺和回顾&#xff0c;难免有疏漏和不足指出&#xff0c;烦请指出&#xff01; 一、指标 Video Caption / 视频字幕常用的标准指标有四种&#xff1a;BLEU-1[1]&#xff0c;BLEU-2[1]&#xff0c;BLEU-3[…

Oracle 11g RAC部署笔记

搭了三次才搭好&#xff0c;要记录一下。 1. Oracle 11g RAC部署的相关步骤以及需要的包&#xff0c;可以参考这里。 Oracle 11g RAC部署_12006142的技术博客_51CTO博客Oracle 11g RAC部署&#xff0c;Oracle11gRAC部署操作环境&#xff1a;CentOS7.4Oracle11.2.0.4一、主机网…

第十四届蓝桥杯大赛软件赛决赛 C/C++ 大学 B 组 试题 C: 班级活动

[蓝桥杯 2023 国 B] 班级活动 【问题描述】 小明的老师准备组织一次班级活动。班上一共有 n n n 名&#xff08; n n n 为偶数&#xff09;同学&#xff0c;老师想把所有的同学进行分组&#xff0c;每两名同学一组。为了公平&#xff0c;老师给每名同学随机分配了一个 n n …

(三)Python变量类型和运算符

所有的编程语言都支持变量&#xff0c;Python 也不例外。变量是编程的起点&#xff0c;程序需要将数据存储到变量中。 变量在 Python 内部是有类型的&#xff0c;比如 int、float 等&#xff0c;但是我们在编程时无需关注变量类型&#xff0c;所有的变量都无需提前声明&#x…

腾讯云cvm云硬盘扩容

过去一直记得腾讯云的系统盘扩容,关于系统盘的扩容直接点资源调整-云硬盘扩容 系统盘扩容后就可以直接使用的&#xff1f; 但是现在操作了发现vda 200G 但是现在vda1不能自动扩容了&#xff1f; 腾讯云cvm云硬盘扩容 先看一眼官方文档吧&#xff1a;在线扩展系统盘分区及文…

Unity如何生成随机数(设置种子)

文章目录 随机类整数二维向量三维向量种子其他文章 随机类 我们可以使用Random类来生成一些随机数 Random类是用于生成随机数的类之一。它可以用于生成不同类型的随机数&#xff0c;如整数、浮点数和向量。 整数 我们可以使用Random.Range来生成指定范围内的随机整数或浮点数…

22 mysql range 查询

前言 这里主要是 探究一下 explain $sql 中各个 type 诸如 const, ref, range, index, all 的查询的影响, 以及一个初步的效率的判断 这里会调试源码来看一下 各个类型的查询 需要 lookUp 的记录 以及 相关的差异 此系列文章建议从 mysql const 查询 开始看 测试表结构…

Spring Boot:利用JPA进行数据库的增改

目录 JPA介绍Service接口Service和Autowired示例代码 Dao数据库操作层Repository示例代码 控制器文件示例代码-增加增加成功示例代码-修改修改成功 JPA介绍 JPA&#xff08;Javaa Persistence API)一种用于持久化 Java 对象到关系型数据库的标准规范。它提供了一种统一的方式来…

Linux 压缩和解压

1、tar命令&#xff08;复杂&#xff09; 使用tar命令均可以进行压缩和解压缩的操作 语法&#xff1a;tar [-c -v -x -f -z -C] 参数1 参数2 ... 参数N -c&#xff0c;创建压缩文件&#xff0c;用于压缩模式 -v&#xff0c;显示压缩、解压过程&#xff0c;用于查看进度 -x&am…

自动化测试工具之Selenium IDE录制教程

一、下载Selenium IDE 下载传送带&#xff1a;Selenium IDE Open source record and playback test automation for the web 这里Darren洋以firefox火狐浏览器为例&#xff0c;将以上下载url直接在firefox浏览器中打开&#xff0c;点击对应下载按钮后&#xff0c;就会进入添加…

ESP32IDF出现Syntax Warning in cmake code at column 47报错

前言 &#xff08;1&#xff09;ESP32的资料还是挺难找的&#xff0c;遇到bug处理起来挺折磨人的。今天分享一个我遇到的bug&#xff0c;以及处理思路。 报错日志 &#xff08;1&#xff09;前天在些博客的时候&#xff0c;做测试发现了一个奇怪的bug&#xff0c;报错日志如下。…

源码:TMS FlexCel Studio for .NET 7.19

TMS FlexCel Studio for .NET 是100% 托管代码 Excel 文件操作引擎以及 Excel 和 PDF 报告生成&#xff0c;适用于 .NET、Xamarin.iOS、Xamarin.Android、Xamarin.Mac、Windows Phone 和 Windows Store 功能概述 使用 FlexCel Studio for .NET 创建可动态快速读写 Excel 文件的…

Vue封装全局SVG组件

1.SVG图标配置 1.安装插件 npm install vite-plugin-svg-icons -D 2.Vite.config.ts中配置 import { createSvgIconsPlugin } from vite-plugin-svg-icons import path from path export default () > {return {plugins: [createSvgIconsPlugin({// Specify the icon fo…

小米云原生文件存储平台化实践:支撑 AI 训练、大模型、容器平台多项业务

小米作为全球知名的科技巨头公司&#xff0c;已经在数百款产品中广泛应用了 AI 技术&#xff0c;这些产品包括手机、电视、智能音箱、儿童手表和翻译机等。这些 AI 应用主要都是通过小米的深度学习训练平台完成的。 在训练平台的存储方案中&#xff0c;小米曾尝试了多种不同的…

【Python基础】常用模块学习:sys|os|pytest

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

java 阿里云上传照片

获取对象 Resourceprivate ALiYunConfig aLiYunConfig;代码配置类 import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;/*** 描述:** author zhaofeng* date 2023-09-05*/ D…

科技云报道:大模型的阴面:无法忽视的安全隐忧

科技云报道原创。 在AI大模型的身上&#xff0c;竟也出现了“to be or not to be”问题。 争议是伴随着大模型的能力惊艳四座而来的&#xff0c;争议的核心问题在于安全。安全有两个方面&#xff0c;一个是大模型带来的对人类伦理的思考&#xff0c;一个是大模型本身带来的隐…