Day23:事务管理、显示评论、添加评论
事务管理
事务的定义
什么是事务
- 事务是由N步数据库操作序列组成的逻辑执行单元,这系列操作要么全执行,要么全放弃执行。
事务的特性(ACID)
- 原子性(Atomicity):事务是应用中不可再分的最小执行体(事务中部分执行失败就会回滚 。
- 一致性(Consistency):事务执行的结果,须使数据从一个一致性状态,变为另一个一致性状态。
- **隔离性(Isolation)😗*各个事务的执行互不干扰,任何事务的内部操作对其他的事务都是隔离的。
- 持久性(Durability):事务一旦提交,对数据所做的任何改变都要记录到永久存储器中。
事务的隔离性
常见的并发异常
- 第一类丢失更新、第二类丢失更新。
- 脏读、不可重复读、幻读。
常见的隔离级别 (从低到高)
- Read Uncommitted:读取未提交的数据。
- Read Committed:读取已提交的数据。
- Repeatable Read:可重复读。
- Serializable:串行化。(可以解决所有的问题,但需要加锁降低数据库性能)
第一类丢失更新

(事务1的回滚导致事务2的数据更新失败)
第二类丢失更新

(事务1和事务2最终结果都是11,事务2不能接受)
脏读

(实际上事务2读到的11,实际上N已经是10了)
不可重复读

(事务2并没有对N变动,但先后结果不一样,查询单行数据导致不一致)
幻读

(查询多行数据导致不一致)
不用的处理方式对数据安全的影响

(一般中间两种比较适合)
数据库保证事务的实现机制
- 悲观锁(数据库)
- 共享锁(S锁):事务A对某数据加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁。
- 排他锁(X锁):事务A对某数据加了排他锁后,其他事务对该数据既不能加共享锁,也不能加排他锁。
- 乐观锁(自定义)
- 版本号、时间戳等
- 在更新数据前,检查版本号是否发生变化。若变化则取消本次更新,否则就更新数据(版本号+1)。
Spring事务管理
- 声明式事务(简单,常用的项目设置)
- 通过XML配置,声明某方法的事务特征。
- 通过注解,声明某方法的事务特征。
- 编程式事务(适合数据库中很多操作,只需要控制部分操作)
- 通过 TransactionTemplate 管理事务,并通过它执行数据库的操作。
示例
- 需求:一个用户自动注册完自动发送帖子
- 如果存在事务,整个会原子执行,报错后会回滚,也就是用户和帖子不会被创建在数据库中
声明式事务(常用,简单)
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public Object save1(){User user = new User();user.setUsername("test");user.setSalt("abc");user.setPassword(CommunityUtil.md5("123" + user.getSalt()));user.setEmail("742uu12@qq.com");user.setHeaderUrl("http://www.nowcoder.com/101.png");user.setCreateTime(new Date());userMapper.insertUser(user);//发布帖子DiscussPost post = new DiscussPost();post.setUserId(user.getId());post.setTitle("hello");post.setContent("新人报道");post.setCreateTime(new Date());discussPostMapper.insertDiscussPost(post);Integer.valueOf("abc");return "ok";
}
//A调B,两者都有事务
//(REQUIRED):B支持当前事务(外部事务A),如果不存在则创建新事务
//(REQUIRES_NEW):B创建一个新事务,并且暂停当前事务(外部事务A)
//(NESTED):B如果当前存在事务(外部事务A),则嵌套在该事务中执行(有独立的提交和回滚),否则和REQUIRED一样
- 使用Transactional注解,isolation规定策略,propagation规定传播方式;
- 故意写一个报错的句子 Integer.valueOf(“abc”);
使用TransactionTemplate
public String save2(){transactionTemplate.setIsolationLevel(TransactionTemplate.ISOLATION_READ_COMMITTED);transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);String result = transactionTemplate.execute(new TransactionCallback<String>() {@Overridepublic String doInTransaction(org.springframework.transaction.TransactionStatus transactionStatus) {User user = new User();user.setUsername("test");user.setSalt("abc");user.setPassword(CommunityUtil.md5("123" + user.getSalt()));user.setEmail("742uu12@qq.com");user.setHeaderUrl("http://www.nowcoder.com/101.png");user.setCreateTime(new Date());userMapper.insertUser(user);//发布帖子DiscussPost post = new DiscussPost();post.setUserId(user.getId());post.setTitle("hello");post.setContent("新人报道");post.setCreateTime(new Date());discussPostMapper.insertDiscussPost(post);return "ok";}});return result;
}
显示评论
- 数据层
- 根据实体查询一页评论数据。
- 根据实体查询评论的数量。
- 业务层
- 处理查询评论的业务。
- 处理查询评论数量的业务。
- 表现层
- 显示帖子详情数据时,
- 同时显示该帖子所有的评论数据。
数据层DAO
- 编写Comment实体类:
package com.newcoder.community.entity;public class Comment {int id;int userId;int entityType;int entityId;int targetId;String content;String status;String createTime;public int getId() {return id;}public void setId(int id) {this.id = id;}public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}public int getEntityType() {return entityType;}public void setEntityType(int entityType) {this.entityType = entityType;}public int getEntityId() {return entityId;}public void setEntityId(int entityId) {this.entityId = entityId;}public int getTargetId() {return targetId;}public void setTargetId(int targetId) {this.targetId = targetId;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public String getCreateTime() {return createTime;}public void setCreateTime(String createTime) {this.createTime = createTime;}@Overridepublic String toString() {return "Comment{" +"id=" + id +", userId=" + userId +", entityType=" + entityType +", entityId=" + entityId +", targetId=" + targetId +", content='" + content + '\'' +", status='" + status + '\'' +", createTime='" + createTime + '\'' +'}';}
}
- 定义CommentMapper接口
@Mapper
public interface CommentMapper {List<Comment> selectCommentsByEntity(int entityType, int entityId, int offset,int limit);int selectCountByEntity(int entityType, int entityId);}
- 编写comment-mapper.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 namespace="com.newcoder.community.dao.CommentMapper"><sql id="selectFields">id, user_id, entity_type, entity_id, target_id, content, status, create_time</sql><select id="selectCommentByEntity" resultType="Comment">select<include refid="selectFields"/>from commentwhere entity_type = #{entityType} and entity_id = #{entityId}order by create_time desc</select><select id="selectCommentCount" resultType="int">select count(id)from commentwhere entity_type = #{entityType}and entity_id = #{entityId}mapper ></select></mapper>
业务层
- 编写CommentService类
@Service
public class CommentService {@Autowiredprivate CommentMapper commentMapper;public List<Comment> findCommentsByEntity(int entityType, int entityId, int offset, int limit){return commentMapper.selectCommentsByEntity(entityType,entityId,offset,limit);}public int findCommentCount(int entityType, int entityId){return commentMapper.selectCountByEntity(entityType,entityId);}}
Controller层
修改之前的getDiscussPost函数:
@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)public String getDiscussPost(@PathVariable(name = "discussPostId") int discussPostId, Model model, Page page) {DiscussPost post = discussPostService.findDiscussPostById(discussPostId);model.addAttribute("post", post);//帖子的作者User user = userService.findUserById(post.getUserId());model.addAttribute("user", user);//评论分页信息page.setLimit(5);page.setPath("/discuss/detail/" + discussPostId);page.setRows(post.getCommentCount());//直接从帖子中取List<Comment> commentList = commentService.findCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());//遍历集合,将每个评论的其他信息查出来(这里嵌套是难点,之后可以在面试上说)List<Map<String, Object>> commentVoList = new ArrayList<>();if(commentList != null){for(Comment comment : commentList){//评论VoMap<String, Object> commentVo = new java.util.HashMap<>();//评论commentVo.put("comment",comment);//作者commentVo.put("user",userService.findUserById(comment.getUserId()));//回复列表(评论的评论)List<Comment> replyList = commentService.findCommentsByEntity(ENTITY_TYPE_COMMENT,comment.getId(),0,Integer.MAX_VALUE);List<Map<String,Object>> replyVoList = new ArrayList<>();if(replyList != null){for(Comment reply : replyList){Map<String,Object> replyVo = new java.util.HashMap<>();//回复replyVo.put("reply",reply);//作者replyVo.put("user",userService.findUserById(reply.getUserId()));//回复目标User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());replyVo.put("target",target);replyVoList.add(replyVo);}}commentVo.put("replys",replyVoList);//回复数量int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT,comment.getId());commentVo.put("replyCount",replyCount);commentVoList.add(commentVo);}}//将评论Vo列表传给前端model.addAttribute("comments",commentVoList);return "/site/discuss-detail";}
方法有点长,从14行开始,首先设置分页信息(只有评论分页,评论的评论不分页)
然后查询所有评论,接着查询评论的评论,都加入hashmap中。
修改index.html

修改discuss-detail.html
这里太复杂了,直接把html附上:
注意这里的分页可以复用首页的分页逻辑。
- 评论显示分页:复用index.html中的th:fragment=“pagination”
<nav class="mt-5" th:replace="index::pagination"><ul class="pagination justify-content-center"><li class="page-item"><a class="page-link" href="#">首页</a></li><li class="page-item disabled"><a class="page-link" href="#">上一页</a></li><li class="page-item active"><a class="page-link" href="#">1</a></li><li class="page-item"><a class="page-link" href="#">2</a></li><li class="page-item"><a class="page-link" href="#">3</a></li><li class="page-item"><a class="page-link" href="#">4</a></li><li class="page-item"><a class="page-link" href="#">5</a></li><li class="page-item"><a class="page-link" href="#">下一页</a></li><li class="page-item"><a class="page-link" href="#">末页</a></li></ul></nav>
添加评论
数据层DAO
- 在CommentMapper中添加insert帖子接口:
int insertComment(Comment comment);
- 返回值为什么是int:
在MyBatis中,insert方法通常返回一个int类型的值,这个值表示的是插入操作影响的行数。如果插入成功,这个值应该是1(因为插入一条数据影响一行);如果插入失败,这个值可能是0(没有行被影响)。这样,开发者可以通过检查这个返回值来判断插入操作是否成功。
- 修改comment-Mapper,修改sql语句:
<sql id="insertFields">user_id, entity_type, entity_id, target_id, content, status, create_time</sql><insert id="insertComment" parameterType="Comment">insert into comment (<include refid="insertFields"></include>)values (#{userId}, #{entityType}, #{entityId}, #{targetId}, #{content}, #{status}, #{createTime})</insert>
- 修改postmapper更新评论数量
int updateCommentCount(int id, int commentCount);
- 修改postmapper填写sql
<update id="updateCommentCount">update discuss_postset comment_count = #{commentCount}where id = #{id}</update>
业务层
- DiscussPostService:
public int updateCommentCount(int id, int commentCount) {return discussPostMapper.updateCommentCount(id, commentCount);}
- CommentService(引入事务管理,重点!!!)
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public int addComment(Comment comment){if(comment == null){throw new IllegalArgumentException("参数不能为空");}//转义HTML标记和过滤敏感词comment.setContent(HtmlUtils.htmlEscape(comment.getContent()));comment.setContent(sensitiveFilter.filter(comment.getContent()));int rows = commentMapper.insertComment(comment);//更新帖子评论数量(过滤楼中楼)if(comment.getEntityType() == ENTITY_TYPE_POST){int count = commentMapper.selectCountByEntity(comment.getEntityType(),comment.getEntityId());discussPostService.updateCommentCount(comment.getEntityId(), count);}return rows;
}
过滤敏感词、识别是帖子的评论而不是楼中楼,更新评论;
Controller层
添加一个新的CommentController:
@Controller
@RequestMapping("/comment")
public class CommentController {@Autowiredprivate CommentService commentService;@Autowiredprivate HostHolder hostHolder;@RequestMapping(path="add/{discussPostId}",method = RequestMethod.POST)public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment, Model model) {comment.setUserId(hostHolder.getUser().getId());comment.setStatus(0);comment.setCreateTime(new Date());commentService.addComment(comment);return "redirect:/discuss/detail/" + discussPostId;}
}
- 想要重定向回原页面,故用@PathVariable取id好拼接url。
修改模板
修改的是site/discuss-post.html
- 修改评论输入框
<div class="container mt-3"><form class="replyform" method="post" th:action="@{|/comment/add/${post.id}|}"><p class="mt-3"><a name="replyform"></a><textarea placeholder="在这里畅所欲言你的看法吧!" name="content"></textarea><input type="hidden" name="entityType" value="1"/><input type="hidden" name="entityId" th:value="${post.id}"/></p><p class="text-right"><button type="submit" class="btn btn-primary btn-sm"> 回 帖 </button></p></form>
</div>
在您的CommentController中,您使用了Comment对象来接收表单提交的数据。Spring MVC会自动将请求参数绑定到Comment对象的属性上,这是通过参数名和Comment对象属性名的匹配来实现的。因此,content表单元素的值会被自动绑定到Comment对象的content属性上。

- 修改楼中楼输入框:(就是回复评论的框)
<li class="pb-3 pt-3"><form method="post" th:action="@{|/comment/add/${post.id}|}"><div><input type="text" class="input-size" name="content" placeholder="请输入你的观点"/><input type="hidden" name="entityType" value="2"/><input type="hidden" name="entityId" th:value="${cvo.comment.id}"/></div><div class="text-right mt-2"><button type="button" class="btn btn-primary btn-sm" onclick="#"> 回 复 </button></div></form>
</li>

- 修改楼中楼中楼的框(就是回复评论的评论的框)
<div th:id="|huifu-${rvoStat.count}|" class="mt-4 collapse"><form method="post" th:action="@{|/comment/add/${post.id}|}"><div><input type="text" class="input-size" name = "content" th:placeholder="|回复${rvo.user.username}|"/><input type="hidden" name="entityType" value="2"/><input type="hidden" name="entityId" th:value="${cvo.comment.id}"/><input type="hidden" name="targetId" th:value="${rvo.user.id}"/></div><div class="text-right mt-2"><button type="submit" class="btn btn-primary btn-sm" onclick="#"> 回 复 </button></div></form>
</div>

相关文章:
Day23:事务管理、显示评论、添加评论
事务管理 事务的定义 什么是事务 事务是由N步数据库操作序列组成的逻辑执行单元,这系列操作要么全执行,要么全放弃执行。 事务的特性(ACID) 原子性(Atomicity):事务是应用中不可再分的最小执行体(事务中部分执行失败就会回滚 。一致性(C…...
第一篇:概述、 目录、适用范围及术语 --- IAB/MRC《增强现实(AR)广告(效果)测量指南1.0 》
第一篇:概述、目录、适用范围及术语 - IAB与MRC及《增强现实广告效果测量指南1.0》 --- 我为什么要翻译美国IAB科技公司系列标准 翻译计划 第一篇概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇广告效…...
pytorch常用的模块函数汇总(2)
目录 torch.utils.data:数据加载和处理模块,包括 Dataset 和 DataLoader 等工具,用于加载和处理训练数据。 torchvision:计算机视觉模块,提供了图像数据集、转换函数、预训练模型等,用于计算机视觉任务。 …...
OpenAI奥特曼豪赌1.42亿破解长生不老
生物初创公司 Retro Biosciences 由山姆奥特曼投资1.42亿英镑,公司目标是延长人类寿命。 山姆奥特曼投资背景: 38 岁的奥特曼一直是科技行业的重要参与者。尽管年纪轻轻,奥特曼凭借 ChatGPT 和 Sora 等产品席卷了科技领域。奥特曼对 Reddit…...
[晕事]今天做了件晕事29;iptables
今天办了一件晕事,主机之间做ping用tcpdump抓到了ping request,但是没有看到ping reply,查看主机的arp表,路由表都没有问题,忘记看iptables的规则。虽然在tcpdump看到包,只是代表包到了二层,并不…...
2018年亚马逊云科技推出基于Arm的定制芯片实例
2018年,亚马逊云技术推出了基于Arm的定制芯片。 据相关数据显示,基于Arm的性价比比基于x86的同类实例高出40%。 这打破了对 x86 的依赖,开创了架构的新时代,现在能够支持多种配置的密集计算任务。 这些举措为亚马逊云技术的其他创…...
用搜索引擎收集信息-常用方式
1,site csdn.net (下图表示只在csdn网站里搜索java) 2,filetype:pdf (表示只检索某pdf文件类型) 表示在浏览器里面查找有关java的pdf文件 3,intitle:花花 (表示搜索网页标题里面有花…...
Adobe推出20多个,企业版生成式AI定制、微调服务
3月27日,全球多媒体领导者Adobe在拉斯维加斯召开“Summit 2024”大会,重磅推出了Firefly Services。 Firefly Services提供了20 多个生成式AI和创意API服务,支持企业自有数据对模型进行定制、微调,同时可以与PS、Illustrator、Ex…...
叁[3],NavigationDrawerViewsActivity新增Fragment
1,环境 AndriodStudio JDK21 2,新建项目NavigationDrawerViewsActivity 3,新建包文件夹,ui右键菜单/New/Package 4,新建Fragment,app右键菜单/New/Fragment/Fragment(with ViewModel) 5,资源string增加键…...
备考ICA----Istio实验7---故障注入 Fault Injection 实验
备考ICA----Istio实验7—故障注入 Fault Injection 实验 Istio 的故障注入用于模拟应用程序中的故障现象,以测试应用程序的故障恢复能力。故障注入有两种: 1.delay延迟注入 2.abort中止注入 1. 环境准备 kubectl apply -f istio/samples/bookinfo/platform/kube/…...
[flask]异常抛出和捕获异常
Python学习之Flask全局异常处理流程_flask 异常处理-CSDN博客 读取文件错误 OSError: [Errno 22] Invalid argument:_[errno 22] invalid argument: ..\\data\\snli_1.0\\-CSDN博客 异常触发 assert触发异常: 在Python中,使用assert语句可以检查某个条…...
js逆向之实例某宝热卖(MD5)爬虫
目录 正常写 反爬 逆向分析 关键字搜索 打断点&分析代码 得出 sign 的由来 确定加密方式 写加密函数了 补全代码 免责声明:本文仅供技术交流学习,请勿用于其它违法行为. 正常写 还是老规矩,正常写代码,该带的都带上,我这种方法发现数据格式不完整. 应该后面也是大…...
7、jenkins项目构建细节-常用的构建触发器
文章目录 一、常用的构建细节1、触发远程构建2、其他工程构建后触发3、定时构建4、轮询SCM(Poll SCM)二、Git hook自动触发构建(☆☆☆)1、安装插件2、Jenkins设置自动构建3、Gitlab配置webhook三、Jenkins的参数化构建1、项目创建分支,并推送到gitlab上2、在Jenkins添加字…...
【前端学习——css篇】4.px和rem的区别
https://github.com/febobo/web-interview 4.px和rem的区别 ①px px,表示像素,所谓像素就是呈现在我们显示器上的一个个小点,每个像素点都是大小等同的,所以像素为计量单位被分在了绝对长度单位中 有些人会把px认为是相对长度&…...
深入解析Oracle数据库中的标量子查询(Scalar Subquery)及其等价改写方法
在Oracle数据库中,标量子查询(Scalar Subquery)是一种特殊的子查询,它返回单个值作为结果,而不是一组记录。标量子查询通常嵌套在另一个查询的SELECT列表、WHERE子句、HAVING子句或表达式中,它就像一个可以…...
Pytorch多机多卡分布式训练
多机多卡分布式: 多机基本上和单机多卡差不多: 第一台机器(主设备): torchrun --master_port 6666 --nproc_per_node8 --nnodes${nnodes} --node_rank0 --master_addr${master_addr} train_with_multi_machine_an…...
win11 环境配置 之 Jmeter
一、安装 JDK 1. 安装 jdk 截至当前最新时间: 2024.3.27 jdk最新的版本 是 官网下载地址: https://www.oracle.com/java/technologies/downloads/ 建议下载 jdk17 另存为到该电脑的 D 盘下,新建jdk文件夹 开始安装到 jdk 文件夹下 2. 配…...
蓝桥杯刷题之路径之谜
题目来源 路径之谜 不愧是国赛的题目 题意 题目中会给你两个数组,我这里是分别用row和col来表示 每走一步,往左边和上边射一箭,走到终点的时候row数组和col数组中的值必须全部等于0这个注意哈,看题目看了半天,因为…...
【深度学习】图片预处理,分辨出模糊图片
ref:https://pyimagesearch.com/2015/09/07/blur-detection-with-opencv/ 论文 ref:https://www.cse.cuhk.edu.hk/leojia/all_final_papers/blur_detect_cvpr08.pdf 遇到模糊的图片,还要处理一下,把它挑出来,要么修复,要么弃用。否…...
基础NLP知识了解
基础NLP知识… 线性变换 通过一个线性变换将隐藏状态映射到另一个维度空间,以获得预期维度的向量 $ outputs hidden_layer * W b$ 这里的W是权重矩阵,b是偏置项,它们是线性变换的参数,通过训练数据学习得到。输出向量的维度…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)
旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据!该数据集源自2025年4月发表于《地理学报》的论文成果…...
