【感恩支持】一票一星皆厚意,一行一履总关情——Java与PostgreSQL正在Commit这份支持,永不Rollback

【感恩支持】一票一星皆厚意,一行一履总关情——Java与PostgreSQL正在Commit这份支持,永不Rollback

目录

前言

1、回味硝烟

2、感恩助力

3、铭记助力

一、该怎么存储呢

1、PG数据库的设计与实现

2、JAVA后台设计与实现

二、JAVA数据存储实践

1、持久化所有助力

2、助力数据查询

三、助力可视化

1、助力名单展示

2、时空引力分析

四、总结


前言

1、回味硝烟

        当时间拨回2026年1月24日零点,2025ZEEKLOG博客之星评选网络投票的硝烟终于在零点投票通道关闭的那一刻缓缓散去。回望这场持续了7天的技术圈"全民公投",恍若一场漫长而激烈的数据库高并发事务——每一位参赛者的票数曲线在屏幕上疯狂跳动,每一次刷新都可能意味着排名的重新排序。这是一场没有硝烟的战争,却充满了计算与决策的张力:有人选择在高并发的高峰期持续拉票,如同优化SQL查询般精准投放;有人在最后时刻发起"秒杀式"冲刺,像极了处理分布式事务时的最后提交。网络投票环节的残酷在于它的实时性,榜单每小时都在变化,那种看着自己的票数被一点点追赶、反超,又在黎明前凭借最后几票重新夺回席位的惊心动魄,堪比生产环境中处理千万级TPS时的紧张感。技术人常说"数据是冰冷的",但当那些数字背后是一张张熟悉或陌生的面孔,是一次次点击"投他一票"时的真诚选择,冰冷的计数器便有了温度,成为了这个冬天最暖心的记忆索引。

网络投票助力图(已截取),详细表单见下文

2、感恩助力

        然而比起榜单上的数字博弈,更让我焦灼的是那一段段不得不发出的拉票信息。作为一个习惯于躲在IDE后面、用代码与世界对话的开发者,突然要把自己从"内容输出者"切换成"推销员"模式,那种心理抗拒就像是在 production 环境直接执行 UPDATE 语句而没有 WHERE 条件——每一步都充满风险与不安。我记得那些在技术群里编辑了又删除、删除了又编辑的消息,害怕打扰到他人,更担心消费了平时积累的技术信誉;记得深夜私聊前辈时字斟句酌的措辞,那种忐忑不亚于提交一个涉及核心表结构变更的 DDL。但正是这些让我脚趾抠地的"社交冒险",换来了令我心头震颤的回应:有潜水多年的老同事、老同事突然冒泡甩来截图,有从未谋面的读者特意注册账号投出第一票,有那些特意前来支持时空智能的朋友,有熟悉的朋友坚定对我说“加油,我支持你往前冲”,还有技术大佬在朋友圈的简短转发配文"支持认真写技术的人"。当看到你们都默默定好闹钟,默契的按时为我每日助力。我知道每一票都不是孤立的数据行,它们携带着关系的温度——是平日的技术分享终于被看见的认可,是"虽不认识但很感动"的陌生人善意,更是老朋友"不管你写得好不好我都挺你"的无条件支持。这些瞬间让我明白,所谓拉票的焦灼,本质上是担心自己的价值配不上他人的善意;而当善意涌来时,所有的焦虑都化作了持久的感动,被写入了我内心那个名为"感恩"的内存表中。

3、铭记助力

        所以今天,当我坐下来打开电脑,带着感恩的心情决定不仅要用心记住这些支持,更要用技术人最擅长的方式——将它们持久化。我启动了一个 Spring Boot 项目,用 Java 编写数据访问层,连接 PostgreSQL 数据库,为每一位投票的朋友建立了一张表。这里没有复杂的业务逻辑,只有最简单的 CRUD——Create 你们的名字,Read 你们的留言,但绝不 Update 这份情感的真实度,更永远不准 Delete 这些记录。当 commit() 方法被调用,当 WAL(Write-Ahead Log)将数据安全地写入磁盘,我知道这不仅仅是技术上的事务提交(Transaction Commit),更是我对你们的情感 Commitment。在技术语境中,Rollback 是事务失败的退路,是数据一致性的保障;但在我们之间,这份支持一旦落库,便是永不 Rollback的永久承诺。无论未来的博客之路是遇到 IO 阻塞还是内存溢出,这张表都会提醒我:2025年的那个冬天,有一群人为我投下了信任票,而我用 Java 对象的固执和 PostgreSQL 的严谨,将这份厚意永远保存在了时光的存储引擎里。谢谢你们,让我明白技术不仅是冷冰冰的代码,更是连接温暖的可能;这份 Commit,我已写入人生数据库,永不回滚。

一、该怎么存储呢

        该怎么存储来自各位朋友的助力呢?作为一个技术人,我想从以下两个方面来进行设计吧。首先是围绕PG数据库,其次是围绕Java的后台程序设计和实现。

1、PG数据库的设计与实现

        为了能实现将每个用户的助力都能进行保存,我将对投票期间的个人助力信息截图,这个界面由ZEEKLOG博客之星评选官方给出。受限与屏幕展示,这里仅展示前面的一些助力朋友。

        将以上的界面信息拆解成PG数据库的物理设计,我们可以得到以下的这些表(仅供参考,大家可以根据自己的需要设计表结构均可):

        这里再将上述两张表的物理表结构的管理SQL给出,如果大家有需要,也可以下载使用,一起记录自己的专属助力动力来源,SQL如下:

/*==============================================================*/ /* Table: biz_ZEEKLOG_candidates_info */ /*==============================================================*/ create table biz_ZEEKLOG_candidates_info ( pk_id INT8 not null, candidate_blog_profile_url VARCHAR(500) not null, candidate_avatar VARCHAR(500) null default '', candidate_nickname VARCHAR(100) not null, vote_no VARCHAR(20) not null, rank INT4 not null, vote_count INT4 not null, previous_vote_count_diff INT4 not null, candidate_article_count INT4 not null, candidate_blog_level INT4 not null, activity_year INT4 not null, vote_enable BOOL null default TRUE, created_at TIMESTAMP null, updated_at TIMESTAMP null, constraint PK_BIZ_ZEEKLOG_CANDIDATES_INFO primary key (pk_id) ); comment on table biz_ZEEKLOG_candidates_info is '候选人信息表:存储候选人的基本信息和投票数据'; comment on column biz_ZEEKLOG_candidates_info.pk_id is 'pk_id'; comment on column biz_ZEEKLOG_candidates_info.candidate_blog_profile_url is '候选人博客主页URL地址'; comment on column biz_ZEEKLOG_candidates_info.candidate_avatar is '候选人头像图片URL'; comment on column biz_ZEEKLOG_candidates_info.candidate_nickname is '候选人昵称'; comment on column biz_ZEEKLOG_candidates_info.vote_no is '投票编号,候选人唯一标识'; comment on column biz_ZEEKLOG_candidates_info.rank is '当前排名'; comment on column biz_ZEEKLOG_candidates_info.vote_count is '获得的总票数'; comment on column biz_ZEEKLOG_candidates_info.previous_vote_count_diff is '与前一名票数的差异值'; comment on column biz_ZEEKLOG_candidates_info.candidate_article_count is '候选人发布的文章数量'; comment on column biz_ZEEKLOG_candidates_info.candidate_blog_level is '候选人博客等级'; comment on column biz_ZEEKLOG_candidates_info.activity_year is '活动年份(如2023, 2024)'; comment on column biz_ZEEKLOG_candidates_info.vote_enable is '投票功能是否启用'; comment on column biz_ZEEKLOG_candidates_info.created_at is '记录创建时间'; comment on column biz_ZEEKLOG_candidates_info.updated_at is '记录最后更新时间'; /*==============================================================*/ /* Index: idx_biz_ZEEKLOG_candidates_info_vo */ /*==============================================================*/ create index idx_biz_ZEEKLOG_candidates_info_vo on biz_ZEEKLOG_candidates_info ( vote_no ); /*==============================================================*/ /* Index: idx_biz_ZEEKLOG_candidates_info_ac */ /*==============================================================*/ create index idx_biz_ZEEKLOG_candidates_info_ac on biz_ZEEKLOG_candidates_info ( activity_year ); /*==============================================================*/ /* Table: biz_ZEEKLOG_contributors */ /*==============================================================*/ create table biz_ZEEKLOG_contributors ( pk_id INT8 not null, candidate_id INT8 null, activity_year INT4 not null, user_name VARCHAR(100) not null, nick_name VARCHAR(200) not null, rank INT4 not null, blog_level INT4 not null, code_age INT4 not null, vote_count INT4 not null, province_name VARCHAR(50) null default '', province_code VARCHAR(30) null default '', created_at TIMESTAMP null, constraint PK_BIZ_ZEEKLOG_CONTRIBUTORS primary key (pk_id) ); comment on table biz_ZEEKLOG_contributors is '贡献者排名表:存储为候选人投票的贡献者信息'; comment on column biz_ZEEKLOG_contributors.pk_id is '主键'; comment on column biz_ZEEKLOG_contributors.candidate_id is '候选人id'; comment on column biz_ZEEKLOG_contributors.activity_year is '投票年份'; comment on column biz_ZEEKLOG_contributors.user_name is '贡献者用户名'; comment on column biz_ZEEKLOG_contributors.nick_name is '贡献者昵称/显示名称'; comment on column biz_ZEEKLOG_contributors.rank is '贡献者排名'; comment on column biz_ZEEKLOG_contributors.blog_level is '贡献者博客等级'; comment on column biz_ZEEKLOG_contributors.code_age is '码经验年限(编程年龄)'; comment on column biz_ZEEKLOG_contributors.vote_count is '该贡献者投票数量'; comment on column biz_ZEEKLOG_contributors.province_name is '所在省份'; comment on column biz_ZEEKLOG_contributors.province_code is '省份code'; comment on column biz_ZEEKLOG_contributors.created_at is '记录创建时间'; /*==============================================================*/ /* Index: idx_biz_ZEEKLOG_contributors_activ */ /*==============================================================*/ create index idx_biz_ZEEKLOG_contributors_activ on biz_ZEEKLOG_contributors ( activity_year ); /*==============================================================*/ /* Index: idx_activity_year_candidate_id */ /*==============================================================*/ create index idx_activity_year_candidate_id on biz_ZEEKLOG_contributors ( candidate_id );

2、JAVA后台设计与实现

        这里使用MybatisPlus来进行数据的存储和管理查询,因此最首要的就是要定义两个对应的数据实体,首先是存储ZEEKLOG的候选人信息实体,核心代码如下:

package com.yelang.project.extend.culturaltour.domain; import java.io.Serializable; import java.util.Date; import java.util.List; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import com.google.gson.annotations.SerializedName; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; /** * - 用于保存ZEEKLOG的候选人信息表:存储候选人的基本信息和投票数据 * @author 夜郎king * */ @TableName(value = "biz_ZEEKLOG_candidates_info") @NoArgsConstructor @AllArgsConstructor @Setter @Getter @ToString public class ZEEKLOGCandidatesInfo implements Serializable { private static final long serialVersionUID = 8767893226186196875L; @TableId(value = "pk_id") private Long pkId;// 主键 //此处ZEEKLOG的技术同学拼写错误 @TableField(value = "candidate_blog_profile_url") @SerializedName("candidateBolgProfileUrl") private String candidateBlogProfileUrl;// 候选人博客主页URL地址 @TableField(value = "candidate_avatar") private String candidateAvatar;// 候选人头像图片URL @TableField(value = "candidate_nickname") private String candidateNickname;// 候选人昵称 @TableField(value = "vote_no") private String voteNo;// 投票编号,候选人唯一标识 @TableField(value = "rank") private Integer rank;// 当前排名 @TableField(value = "vote_count") private Integer voteCount;// 获得的总票数 @TableField(value = "previous_vote_count_diff") private Integer previousVoteCountDiff;// 与前一名票数的差异值 @TableField(value = "candidate_article_count") private Integer candidateArticleCount;// 候选人发布的文章数量 @TableField(value = "candidate_blog_level") private Integer candidateBlogLevel;// 候选人博客等级 @TableField(value = "activity_year") private Integer activityYear;// 活动年份(如2023, 2024) @TableField(value = "vote_enable") private Boolean voteEnable;// 投票功能是否启用 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField(value = "created_at") private Date createdAt;// 创建时间 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField(value = "updated_at") private Date updatedAt;// 更新时间 @TableField(exist = false) private List<ZEEKLOGContributors> contributeRankList; }

        除了存储候选人员信息以外,更多的就是存储外部的支持信息,对应的ZEEKLOG的贡献者排名信息对象核心代码如下:

package com.yelang.project.extend.culturaltour.domain; import java.io.Serializable; import java.util.Date; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; /** * - 用于保存ZEEKLOG的贡献者排名表:存储为候选人投票的贡献者信息 * @author 夜郎king * */ @TableName(value = "biz_ZEEKLOG_contributors") @NoArgsConstructor @AllArgsConstructor @Setter @Getter @ToString public class ZEEKLOGContributors implements Serializable { private static final long serialVersionUID = -1216167452283435753L; @TableId(value = "pk_id") private Long pkId;// 主键 @TableField(value = "candidate_id") private Long candidateId;// 候选人id @TableField(value = "activity_year") private Integer activityYear;// 用户昵称 @TableField(value = "user_name") private String userName;// 贡献者用户名 @TableField(value = "nick_name") private String nickName;// 贡献者昵称/显示名称 @TableField(value = "rank") private Integer rank;// 贡献者排名 @TableField(value = "blog_level") private Integer blogLevel;// 贡献者博客等级 @TableField(value = "code_age") private Integer codeAge;// 码经验年限(编程年龄) @TableField(value = "vote_count") private Integer voteCount;// 该贡献者投票数量 @TableField(value = "province_name") private String provinceName;// 所在省份 @TableField(value = "province_code") private String provinceCode;// 省份code @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField(value = "created_at") private Date createdAt;// 创建时间 }

二、JAVA数据存储实践

        当持久化的物理表已经设计好,对应的管理对象也实现好。那么接下来就是将所有的助力支持都持久化到PG数据库中。因此接下来就是介绍如何进行助力信息的持久化和数据查询。

1、持久化所有助力

        首先我们先搜集官方的信息数据,从提供的接口中可以获取以下信息:

        我们将数据写入到json或者txt中,然后需要使用Java的API来进行文本的读取。然后将json格式的文本反序列化成对应的对象。然后再调用相应的持久化层进行数据持久化。篇幅有限,用于数据存储的核心代码如下:

package com.yelang.project.blogstar; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Date; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import com.google.gson.Gson; import com.yelang.common.utils.DateUtils; import com.yelang.common.utils.StringUtils; import com.yelang.project.extend.culturaltour.domain.ZEEKLOGCandidatesDTO; import com.yelang.project.extend.culturaltour.domain.ZEEKLOGCandidatesInfo; import com.yelang.project.extend.culturaltour.domain.ZEEKLOGContributors; import com.yelang.project.extend.culturaltour.service.IZEEKLOGCandidatesInfoService; import com.yelang.project.extend.culturaltour.service.IZEEKLOGContributorsService; @SpringBootTest @RunWith(SpringRunner.class) public class ZEEKLOGBlogStar2025ToDB { @Autowired private IZEEKLOGCandidatesInfoService ZEEKLOGCandidatesInfoService; @Autowired private IZEEKLOGContributorsService ZEEKLOGContributorsService; /** * 使用try-with-resources(JDK 1.7+) */ public static String readFileToString(String filePath) { StringBuilder content = new StringBuilder(); try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { String line; while ((line = br.readLine()) != null) { content.append(line).append("\n"); } } catch (IOException e) { System.err.println("读取文件失败: " + e.getMessage()); return ""; } return content.toString(); } @Test public void readFromTxt() { String filePath = "F:/2025ZEEKLOG博客之星/投票信息/17、夜郎king.txt"; //step1、从文本中读取json字符串 String jsonStr = readFileToString(filePath); //System.out.println(jsonStr); Gson gson = new Gson(); if(StringUtils.isNotEmpty(jsonStr)) { ZEEKLOGCandidatesDTO ZEEKLOGCandidatesDto = gson.fromJson(jsonStr, ZEEKLOGCandidatesDTO.class); //step2、保存处理候选人信息 Date now = DateUtils.getNowDate(); int year = 2025; ZEEKLOGCandidatesInfo candidatesInfo = ZEEKLOGCandidatesDto.getData(); candidatesInfo.setActivityYear(year); candidatesInfo.setCreatedAt(now); candidatesInfo.setUpdatedAt(now); ZEEKLOGCandidatesInfoService.save(candidatesInfo); //step3、处理贡献人信息列表 Long candidatesInfoPk = candidatesInfo.getPkId(); List<ZEEKLOGContributors> dataList = candidatesInfo.getContributeRankList(); for (ZEEKLOGContributors ZEEKLOGContributors : dataList) { ZEEKLOGContributors.setCandidateId(candidatesInfoPk); ZEEKLOGContributors.setCreatedAt(now); ZEEKLOGContributors.setActivityYear(year); } ZEEKLOGContributorsService.saveBatch(dataList, 300); System.out.println("处理完成..."); } } }

2、助力数据查询

        在IDE中运行以上程序就可以实现每一份助力力量的存储。下面我们来验证一下在数据库中是否已经完成数据的录入存储,查询SQL如下:

select * from biz_ZEEKLOG_contributors;

        查询结果如下,如果看到以上信息说明数据保存成功:

三、助力可视化

        在将所有的助力信息永久持久化之后,让我们一起来看看在这次博客之星的投票环节中,都是那些小伙伴在背后支持夜郎king?那这些小伙伴又分别来自那些省份呢(这又回到我热爱的GIS时空专题了,嘻嘻)?

1、助力名单展示

        下面是完整详细的网络投票支持名单,真诚的感激名单中的每一位。也感激很多朋友发来消息,没有投票权的朋友(虽然没有直接投票,但心意是真心的领悟到了)。下面将展示我的力量源泉,感恩你们的助力:

2、时空引力分析

        作为一名时空智能的技术开发人员,特别想看看这些支持的力量都来自于那些省份呢?关于省份信息,数据信息来源从每个人的博客地址首页中直接采集的信息,比如:

        为了能方便的进行省份信息分布情况查询,我特意按照省份进行分组后进行数据展示,查询sql如下:

select count(1),province_name from biz_ZEEKLOG_contributors t group by t.province_name order by count(1) desc; 

        为了更直观的展示信息,我们将这些助力的省份来源做成了引力图,具体如下图所示:

        作为来自湖南的开发人员,收到了来自湖南的最多支持。除此之外,广东省和江苏省也是较大的助力来源。其次是浙江、上海、湖北、河北及河南等省份。互联网很神奇,拉进了我们的距离,虽然隔着时空万里,但我们却如此相近。

四、总结

        以上就是本文的主要内容,本文是夜郎king专程对2025博客之星网络评选的助力朋友的专程感谢,除了这些直接的助力,还有很多没直接投票,但也关心支持夜郎king的朋友,相信有了你们的支持,在接下来的日子中,我会更加坚定和努力。这场评选落幕了,但我选择用代码为记忆存档。以Java为笔、PostgreSQL为卷,将五百多份助力郑重写入数据表——每一次查询都是对善意的重读,每一行记录都是时光的拓片。当看到可视化图表上星罗棋布的支持点亮成一片星河,我深知这不仅是技术实现,更是一次情感的持久化Commit。这份温暖已落盘、已归档、已刻入持久存储,成为我永不Rollback的前行动力。谢谢你们,让冰冷的代码有了温度,让每一个事务提交都成了感恩的见证。未来长路,携此星光,继续奔赴。

Read more

【数据结构】彻底搞懂二叉树:四种遍历逻辑、经典OJ题与递归性能全解析

【数据结构】彻底搞懂二叉树:四种遍历逻辑、经典OJ题与递归性能全解析

🏠 个人主页:EXtreme35 📚 个人专栏: 专栏名称专栏主题简述《C语言》C语言基础、语法解析与实战应用《数据结构》线性表、树、图等核心数据结构详解《题解思维》算法思路、解题技巧与高效编程实践 目录 * 二叉树全栈进阶指南:从内存布局到递归本质的深度复盘 * 一、二叉树的底层逻辑与核心概念 * 1.1 核心定义与特点 * 1.2 二叉树的五种基本形态 * 1.3 特殊二叉树 * 1.4 二叉树的五条性质 * 1.5 存储结构 * 二、遍历的递归之美 * 2.1 前序遍历 * 2.2 中序遍历 (In-order Traversal) * 2.3 后序遍历 (Post-order Traversal) * 2.

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 path_to_regexp 揭秘路由匹配与参数提取的核心算法(路由管道工程师)

Flutter for OpenHarmony: Flutter 三方库 path_to_regexp 揭秘路由匹配与参数提取的核心算法(路由管道工程师)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在进行 OpenHarmony 的应用架构设计时,我们经常需要处理“动态路由”。 * 页面路径模式:/profile/:userId * 实际跳转路径:/profile/9527 如何在众多的路由规则中,快速匹配到正确的页面,并精准提取出其中的动态参数 userId = 9527?这背后的核心驱动力,正是 path_to_regexp。它是 go_router、auto_route 等几乎所有顶级路由框架共享的底层逻辑库。 一、路由解析链路模型 该库将人类易读的路径模式,转化为机器可高效执行的正规表达式。 路径模式 ('/user/:id') path_to_regexp 编译器 高性能 RegExp (正则) 路径匹配

By Ne0inhk
算法王冠上的明珠——动态规划之路径问题(第一篇)

算法王冠上的明珠——动态规划之路径问题(第一篇)

目录 1. 什么叫路径类动态规划 一、核心定义(通俗理解) 二、核心特征(识别这类问题的关键) 2. 动态规划步骤 状态表示 状态转移方程 初始化 填表顺序 返回值 3. 例题讲解 3.1 LeetCode62. 不同路径 3.2 LeetCode63. 不同路径 II 3.3 LeetCodeLCR 166. 珠宝的最高价值 今天我们来聊一聊动态规划的路径类问题。 1. 什么叫路径类动态规划 路径类动态规划是 动态规划的一个重要分支,核心解决 “从起点到终点的路径相关问题”—— 比如 “路径总数”“最短路径长度”“路径上的最大 / 最小和” 等,其本质是通过 “状态递推” 避免重复计算,高效求解多阶段决策的路径问题。 一、

By Ne0inhk
C语言指针与数组的深度关联及实战应用

C语言指针与数组的深度关联及实战应用

C语言指针与数组的深度关联及实战应用 💡 学习目标:掌握指针与数组的内在联系,熟练运用指针操作数组元素,解决实际开发中的数组遍历、数据交换等问题;学习重点:数组名的本质、指针算术运算操作数组、指针数组与数组指针的区别及应用。 38.1 数组名与指针的关系 在C语言中,数组和指针有着密不可分的联系。很多初学者会混淆数组名和指针变量的概念,其实二者既有关联,又有本质区别。 38.1.1 数组名的本质 💡 数组名在大多数情况下会被编译器隐式转换为指向数组首元素的常量指针。 我们来看一段简单的代码: #include<stdio.h>intmain(){int arr[5]={10,20,30,40,50};printf("数组首元素地址:%p\n", arr);printf("数组首元素地址:%p\n&

By Ne0inhk