Mybatis-Plus进阶使用

一、逻辑删除

曾经我们写的删除代码都是物理删除。

逻辑删除:删除转变为更新

update user set deleted=1 where id = 1 and deleted=0

查找: 追加 where 条件过滤掉已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段

查找: select id,name,deleted from user where deleted=0

1.修改表结构

  • 给 user 表增加 deleted 字段(1代表删除0代表未删除)
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `uemail` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '邮箱',
  `deleted` int DEFAULT '0' COMMENT '删除与否',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
  • 修改字段的方式
ALTER TABLE user ADD column `deleted` int DEFAULT '0' COMMENT '删除与否';

2.开启配置支持

  • 配置com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig

mybatis-plus:
  global-config:
    db-config:
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

3.@TableLogic

  • 实体类字段上加上@TableLogic注解

@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(select = false)
    private Integer age;
    @TableField(value = "uemail")
    private String email;
    @TableLogic
    private Integer deleted;
}

4.测试

  • 测试删除 
@Test
public void testLogicDeleted(){
    int ret = userMapper.deleteById(3L);
    System.out.println("删除受影响行数为:"+ret);
}
==>  Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 3(Long)
<==    Updates: 1
  • 测试查询
@Test
public void testLogicDeletedSelect(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    List<User> user = userMapper.selectList(wrapper);
    System.out.println(user);
}
==>  Preparing: SELECT id,name,uemail AS email,deleted FROM user WHERE deleted=0
==> Parameters: 
<==      Total: 3
  •  数据库结果

三、自动填充功能

开发中,比如

  • 插入数据的时候需要插入创建时间
  • 更新数据的时候需要添加更新时间

1.修改表结构

  • 修改表结构
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `uemail` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '邮箱',
  `deleted` int DEFAULT '0' COMMENT '删除与否',
  `sex` int DEFAULT '1' COMMENT '性别',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

2.@TableField

  • FieldFill
public enum FieldFill {
    DEFAULT,//不处理
    INSERT,//插入查出
    UPDATE,//更新处理
    INSERT_UPDATE;//插入和更新处理
}
  •  添加字段
@TableField(value = "create_time",fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time",fill = FieldFill.UPDATE)
private Date updateTime;

 3.自定义实现类 MyMetaObjectHandler

  • 自定义实现类 MyMetaObjectHandler
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        setFieldValByName("createTime",new Date(),metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        setFieldValByName("updateTime",new Date(),metaObject);
    }
}

4.测试

  • 测试插入操作
@Test
public void testFillInsert(){
    User entity = new User();
    entity.setEmail("erew");
    entity.setAge(19);
    entity.setName("小红");
    entity.setSex(SexEnum.MAN);
    int insert = userMapper.insert(entity);
    System.out.println("受影响行数为:"+insert);
}
==>  Preparing: INSERT INTO user ( name, age, uemail, sex, create_time ) VALUES ( ?, ?, ?, ?, ? )
==> Parameters: 小红(String), 19(Integer), erew(String), 1(Integer), 2022-12-03 11:34:41.169(Timestamp)
<==    Updates: 1
  • 测试更新操作
@Test
public void testFillUpdate(){
    User entity = new User();
    entity.setId(15L);
    entity.setEmail("erew");
    entity.setAge(19);
    entity.setName("小红");
    entity.setSex(SexEnum.MAN);
    int insert = userMapper.updateById(entity);
    System.out.println("受影响行数为:"+insert);
}
==>  Preparing: UPDATE user SET name=?, age=?, uemail=?, sex=?, update_time=? WHERE id=? AND deleted=0
==> Parameters: 小红(String), 19(Integer), erew(String), 1(Integer), 2022-12-03 11:37:10.716(Timestamp), 15(Long)
<==    Updates: 1

四、SQL注入器

1.SQL注入的原理分析

MyBatisPlus 是使用 ISqlInjector 接口负责SQL注入工作

  • 具体实现注入
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);
        if (modelClass != null) {
            String className = mapperClass.toString();
            Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
            if (!mapperRegistryCache.contains(className)) {
                TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);
                if (CollectionUtils.isNotEmpty(methodList)) {
                    methodList.forEach((m) -> {
                        m.inject(builderAssistant, mapperClass, modelClass, tableInfo);
                    });
                } else {
                    this.logger.debug(mapperClass.toString() + ", No effective injection method was found.");
                }

                mapperRegistryCache.add(className);
            }
        }

    }
  • 实际的实现代码
methodList.forEach((m) -> {
                        m.inject(builderAssistant, mapperClass, modelClass, tableInfo);
                    });
  • m.inject 调用具体方法
this.injectMappedStatement(mapperClass, modelClass, tableInfo);			
  • 最终具体的执行就是自己定义的类

2.扩充BaseMapper中方法

2.1编写自己的BaseMapper

  • 编写MyBaseMapper
public interface MyBaseMapper<T> extends BaseMapper<T> {
    List<T> listAll();
}
  • 需要用到的mapper 直接继承即可
@Repository
public interface UserMapper extends MyBaseMapper<User> {
    
}

2.2编写MySqlInjector

  • MySqlInjector
@Component	
public class MySqlInjector extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        //默认的mybatis-plus 的注入方法
        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
        //添加自己的 listAll 类
        methodList.add(new ListAll());
        return methodList;
    }
}

2.3编写ListAll类

  • 创建ListAll 类
public class ListAll extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sqlMethod = "listAll";
        String sql = "select * from "+tableInfo.getTableName();
        SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
        return this.addSelectMappedStatementForTable(mapperClass,sqlMethod,sqlSource,tableInfo);
    }
}

2.4测试

@Test
public void testInjector(){
    List<User> users = userMapper.listAll();
    System.out.println(users);
}
==>  Preparing: select * from user
==> Parameters: 
<==      Total: 13

五、插件

1.拦截器核心代码

MybatisPlusInterceptor

该插件是核心插件,目前代理了 `Executor#query` 和 `Executor#update` 和`StatementHandler#prepare` 方法

  • 拦截器核心代码  
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
})
@Component
public class MyInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("拦截前操作");
        //做拦截操作
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        System.out.println("装配插件");
        //装配插件
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        System.out.println("设置属性");
        //属性设置
    }
}
  •  交给容器管理即可实现拦截操作

2.分页插件

@Configuration
@MapperScan("cn.sycoder.mapper")
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

 3.乐观锁插件

OptimisticLockerInnerInterceptor

当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

3.1乐观锁补充 

  • 在数据库增加一个 version 字段来管理数据,假设没有其它人操作过这条数据,先读取本条数据的version,然后再修改的时候,判断之前拿出来的version 和现在的version是否一致,如果不一致,则表示被人操作过,不能够正常实现更新
  • 演示
update user set age = 123123,version = version+1  where id = 16 and version = 0;

3.2添加乐观锁配置

@Configuration
@MapperScan("cn.wjcoder.mapper")
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

3.3修改表结构

CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `uemail` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '邮箱',
  `deleted` int DEFAULT '0' COMMENT '删除与否',
  `sex` int DEFAULT '1' COMMENT '性别',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `version` int DEFAULT NULL COMMENT '乐观锁控制',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

3.4添加 version 字段

  • 实体添加字段
@Version
private Integer version;

3.5测试

@Test
public void testVersion(){
    User user = userMapper.selectById(16L);
    user.setName("云哥666");
    int count = userMapper.updateById(user);
    System.out.println("受影响行数:"+count);
}
==>  Preparing: UPDATE user SET name=?, uemail=?, sex=?, create_time=?, update_time=?, version=? WHERE id=? AND version=? AND deleted=0
==> Parameters: 云哥666(String), sy@qq.com(String), 1(Integer), 2022-12-03 11:34:41.0(Timestamp), 2022-12-03 17:44:05.425(Timestamp), 3(Integer), 16(Long), 2(Integer)
<==    Updates: 1

4.防全表更新与删除插件

BlockAttackInnerInterceptor

针对 update 和 delete 语句 作用: 阻止恶意的全表更新删除

4.1配置拦截器

@Configuration
@MapperScan("cn.sycoder.mapper")
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}

4.2测试

  • 测试全表删除
@Test
public void testDeleteAll(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    int count = userMapper.delete(wrapper);
    System.out.println("受影响行数:"+count);
}

六、代码生成器

简化开发流程,提高开发效率,让程序写很少的代码就能实现开发

1.创建 mybatis-plus-02 模块

2.添加依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.10.1</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.10.1</version>
        </dependency>
    </dependencies>

3.使用

public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis-plus", "root", "123456")
                .globalConfig(builder -> {
                    builder.author("sy") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("F:\\03-Spring\\MyBatisPlus\\homework\\mybatis-plus-02\\src\\main\\java\\"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("cn") // 设置父包名
                            .moduleName("sycoder") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, "F:\\03-Spring\\MyBatisPlus\\homework\\mybatis-plus-02\\src\\main\\resources\\cn\\sycoder")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("user"); // 设置需要生成的表名
//                            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();

    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JConsole使用教程

JConsole是一个Java虚拟机的监控和管理工具&#xff0c;可以监控Java应用程序的内存使用、线程和类信息等。 以下是JConsole的使用教程&#xff1a; 1.启动JConsole JConsole是一个Java自带的工具&#xff0c;可以在bin目录下找到jconsole.exe文件。双击运行该文件即可启动JC…

正则表达式-元字符

文章目录一、正则表达式-元字符总结一、正则表达式-元字符 正则表达式 - 元字符 下表包含了元字符的完整列表以及它们在正则表达式上下文中的行为&#xff1a; 字符描述\将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如&#xf…

强人工智能时代,区块链还有戏吗?

最近很多人都在问我&#xff0c;ChatGPT 把 AI 又带火了&#xff0c;区块链和 Web3 被抢了风头&#xff0c;以后还有戏吗&#xff1f;还有比较了解我的朋友问&#xff0c;当年你放弃 AI 而选择区块链&#xff0c;有没有后悔&#xff1f;这里有一个小背景。2017 年初我离开 IBM …

银行数字化转型导师坚鹏:如何制定银行数字化转型年度培训规划

如何制定银行数字化转型年度培训规划 ——以推动银行数字化转型战略落地为核心&#xff0c;实现知行果合一课程背景&#xff1a; 很多银行都在开展银行数字化转型培训工作&#xff0c;目前存在以下问题急需解决&#xff1a; 缺少针对性的银行数字化转型年度培训规划 不清楚如…

Spring --- 声明式事务

一、JdbcTemplate 1.1、简介 Spring 框架对 JDBC 进行封装&#xff0c;使用 JdbcTemplate 方便实现对数据库操作 1.2、准备工作 ① 加入依赖 <dependencies><!-- 基于Maven依赖传递性&#xff0c;导入spring-context依赖即可导入当前所需所有jar包 --><depen…

openAi ChatGPT调用性能优化的一些小妙招

参考的demo:GitHub - ddiu8081/chatgpt-demo: A demo repo based on OpenAI API. 扭曲调教&#xff1a; openai提供的chat接口&#xff08;https://api.openai.com/v1/chat/completions&#xff09;由于其模型很大&#xff08;什么1750亿个参数啥的&#xff09;&#xff0c;单…

Redis之底层数据结构

一 Redis数据结构 Redis底层数据结构有三层意思&#xff1a; 从Redis本身数据存储的结构层面来看&#xff0c;Redis数据结构是一个HashMap。从使用者角度来看&#xff0c;Redis的数据结构是String&#xff0c;List&#xff0c;Hash&#xff0c;Set&#xff0c;Sorted Set。从…

Docker 镜像使用

目录 1、列出镜像列表 2、获取一个新的镜像 3、查找镜像 4、拖取镜像 5、删除镜像 6、创建镜像 a.更新镜像 b.构建镜像 设置镜像标签 当运行容器时&#xff0c;使用的镜像如果在本地中不存在&#xff0c;docker 就会自动从 docker 镜像仓库中下载&#xff0c;默认是从 …

现在大专生转IT可行吗?

当然可行的。 大专也是人&#xff0c;为什么不可以选择喜欢的专业学习&#xff0c;现在大学生遍地都是&#xff0c;学历已经不是限制你发展的因素了。有的人就是不擅长理论学习&#xff0c;更喜欢技术。IT也只是一个普普通通的技术行业&#xff0c;跟其他技术行业一样&#xf…

wsl=

安装wsl wsl --install,用户名wu,密码 123456&#xff0c; https://learn.microsoft.com/en-us/windows/wsl/install 安装anaconda, 把anaconda移动到wu目录下&#xff0c;在wu用户以及用户目录下执行bash Anaconda-文件名&#xff0c;安装目录为/home/wu/anaconda3 配置cond…

C#方法详解

总目录 文章目录总目录前言一、方法概述1、方法签名2、调用/访问方法/方法形参与实参二、扩展方法1.实现扩展方法2、扩展方法调用3、优先级问题4、其他扩展方法示例1.获得枚举的Description2.其他常用扩展方法三、方法参数列表详解1、可选自变量&#xff08;可选参数&#xff0…

深入剖析 MVC 模式与三层架构

文章目录1. 前言2. MVC模式3. 三层架构4. MVC和三层架构5. 总结5.1 IDEA 小技巧1. 前言 前面我们探讨了 JSP 的使用&#xff0c;随着计算机技术的不断更新迭代&#xff0c;JSP 的技术由于存在很多的缺点&#xff0c;已经逐渐退出了历史的舞台&#xff0c;所以在学习时&#xf…

Google代码覆盖率最佳实践

软件质量保障: 所寫即所思&#xff5c;一个阿里质量人对测试的所感所悟。谷歌一直倡导的领域之一是使用代码覆盖率数据评估风险并识别测试中的真空。然而&#xff0c;代码覆盖率的价值一直是个争议的话题。每次聊到代码覆盖率时&#xff0c;似乎都会引发无尽的争论。由于大家固…

88、K-Planes: Explicit Radiance Fields in Space, Time, and Appearance

简介 主页&#xff1a;https://sarafridov.github.io/K-Planes/ 图像使用一个平面表示&#xff0c;静态三维场景用三个平面表示&#xff0c;后续动态场景用三个平面加一维时间 t 表示&#xff0c;论文提出使用六个平面表示动静态场景&#xff0c;即静态场景占三个平面&#x…

【超详细文件操作(三)】C语言

作者&#xff1a;日出等日落 专栏&#xff1a;C语言 只有流过血的手指&#xff0c;才能弹出世间的绝唱。 ——泰戈尔 目录 1.文件的随机读写 1.1 fseek函数 1.1.1 下面使用fseek函数 1.2 ftell函数 1.3 rewind函数 …

Spring源码分析-Bean创建流程三

目录 一、 列举一些创建对象有哪几种方式 二、自定义BeanPostProcess生成代理对象 1、实战案例 2、源码分析 三、通过supplier创建对象 1、实战案例 2、源码分析 四、通过FactoryMethod创建对象 1、实战案例 2、源码分析 五、小总结 一、 列举一些创建对象有哪几种方…

作为一个女测试员是什么样的体验?

面试时极度紧张&#xff0c;语无伦次&#xff0c;觉得肯定没戏&#xff0c;最后却拿到高薪offer。 工作之后我听同事们讲&#xff0c;测试总监面试官并没打算要我&#xff0c;但身边的人都问他&#xff1a; 那个小姐姐什么时候来报道&#xff1f;... 于是在众人的期待的目光…

撮合交易系统简介

1 撮合交易系统简介 金融市场&#xff1a; 为了应对更高峰值的成交量&#xff0c;国内各金融机构&#xff0c;主要是交易所和银联、中心之间需求越来越多&#xff1a; 其中最重要的就是撮合系统&#xff1a; 系统拓扑图&#xff1a; 委托终端/柜台&#xff1a; 网关&#xff1…

一四三、人脸识别自动点赞、关注

文章目录脚本功能获取video当前播放帧图片将图片传到后台调用百度人脸识别接口拿到识别结果处理逻辑效果展示问题记录脚本功能 通过获取video当前播放帧图片&#xff0c;截图调用后台接口&#xff0c;再调用百度人脸识别拿到人脸信息&#xff08;年龄、颜值、性别等&#xff09…

元宇宙医生虚拟形象提高远程医疗服务质量

与现实中不同&#xff0c;3D虚拟形象是由个人在数字空间中自由选择并进行扮演的。这种3D虚拟形象在元宇宙中的重要性越来越突出。 在元宇宙虚拟空间中&#xff0c;用户借助元宇宙3D虚拟形象就能与其他用户互动、交流并获得真实的沉浸式体验&#xff0c;因此能广泛融入各种生活、…
最新文章