[{"data":1,"prerenderedAt":1966},["ShallowReactive",2],{"post:\u002Fposts\u002Fswdesign\u002Fsw-design-notes-18":3,"posts:ordered":1666},{"id":4,"title":5,"body":6,"category":1647,"cover":1648,"date":1649,"description":1650,"extension":1651,"featured":1652,"meta":1653,"navigation":1654,"path":1655,"seo":1656,"series":1657,"seriesOrder":1658,"stem":1659,"tags":1660,"__hash__":1665},"posts\u002Fposts\u002FswDesign\u002Fsw-design-notes-18.md","软件分析与设计笔记 18：数据库分析：实体、关系与数据字典",{"type":7,"value":8,"toc":1617},"minimark",[9,16,25,30,33,67,78,84,95,102,106,111,118,186,192,203,209,213,216,219,269,276,280,284,298,375,385,391,395,402,405,461,467,471,485,502,512,516,520,523,538,544,551,559,565,569,576,609,612,660,677,681,692,695,803,816,820,824,830,876,880,883,890,1036,1039,1050,1054,1058,1061,1101,1104,1172,1179,1185,1189,1199,1203,1209,1212,1218,1221,1250,1253,1327,1330,1613],[10,11,12],"blockquote",{},[13,14,15],"p",{},"这是我在学习软件分析与设计时整理的系列笔记。概念部分是我按自己的理解写的，示例与图示都是自己编、自己画的。理解有偏差的地方欢迎指出。",[10,17,18],{},[13,19,20,24],{},[21,22,23],"strong",{},"贯穿案例","：片区家电维修派单系统。09 篇抽出了领域模型，这一篇把它落成 ERD 与数据字典。",[26,27,29],"h2",{"id":28},"_0-直接建表会出什么问题","0 直接建表会出什么问题",[13,31,32],{},"需求刚谈完，最自然的下一个动作就是打开数据库工具开始建表。但这个顺序会带来三类麻烦：",[34,35,36,55,61],"ul",{},[37,38,39,42,43,47,48,47,51,54],"li",{},[21,40,41],{},"字段名各写各的。"," 同一个人名，有的表叫 ",[44,45,46],"code",{},"name","，有的叫 ",[44,49,50],{},"custName",[44,52,53],{},"c_name","；",[37,56,57,60],{},[21,58,59],{},"关系靠猜。"," 「一个客户能有多台机器」没人问过，开发按自己的理解建了外键；",[37,62,63,66],{},[21,64,65],{},"类型和长度随手定。"," 电话字段给了 10 位，结果来了个带区号的号码。",[13,68,69,70,73,74,77],{},"放到本例里，最典型的「猜错」是这条：",[21,71,72],{},"「一个工程师能同时属于多个片区吗？」"," 如果开发猜「不能」，就只在工程师表上加一个 ",[44,75,76],{},"district_id","；等到旺季要跨片区支援，才发现要加一张关联表 —— 那时候已经有几千条数据要迁移。",[13,79,80,81],{},"数据库分析（database analysis）就是把这三件事在动手之前先确定下来：",[21,82,83],{},"有哪些实体、它们之间是什么关系、每个字段叫什么、什么类型、什么业务规则。",[13,85,86,87,90,91,94],{},"顺带明确一下",[21,88,89],{},"数据库管理系统（DBMS）"," 的位置：它位于应用和数据库之间，负责数据的检索与更新（关系型的那一类通常缩写为 ",[21,92,93],{},"RDBMS","）。应用不直接碰文件，而是通过它来读写数据。",[13,96,97],{},[98,99],"img",{"alt":100,"src":101},"实体关系图的构成与数据字典：上方是客户、工单、配件消耗、库存项四个实体以及它们之间的一对多与多对一关系标注，菱形标出关系名；下方是数据字典的四列结构，即属性名、类型、长度、定义与业务规则，并给出四行示例，其中第四列写得最详细","\u002Fimages\u002Fsw-design-notes-18-erd-dictionary.svg",[26,103,105],{"id":104},"_1-数据模型与实体关系图","1 数据模型与实体关系图",[107,108,110],"h3",{"id":109},"_11-三个元素与判断要点","1.1 三个元素与判断要点",[13,112,113,114,117],{},"数据模型（data model）在这个阶段就是用",[21,115,116],{},"实体关系图（entity relationship diagram, ERD）"," 表达的。它由三样东西组成：",[119,120,121,140],"table",{},[122,123,124],"thead",{},[125,126,127,131,134,137],"tr",{},[128,129,130],"th",{},"元素",[128,132,133],{},"含义",[128,135,136],{},"判断要点",[128,138,139],{},"本例",[141,142,143,158,172],"tbody",{},[125,144,145,149,152,155],{},[146,147,148],"td",{},"实体（entity）",[146,150,151],{},"业务中可区分的对象",[146,153,154],{},"能被唯一标识，且有多个实例",[146,156,157],{},"客户、工单、配件消耗、库存项",[125,159,160,163,166,169],{},[146,161,162],{},"关系（relationship）",[146,164,165],{},"实体之间的业务关联",[146,167,168],{},"关系本身描述的是业务动作或约束",[146,170,171],{},"「报修」「包含」「引用」",[125,173,174,177,180,183],{},[146,175,176],{},"属性（attribute）",[146,178,179],{},"实体上需要记录的信息",[146,181,182],{},"来自需求，不是为了方便而加",[146,184,185],{},"工单号、状态、数量",[13,187,188,191],{},[21,189,190],{},"「关系」那一行值得多说一句","。关系不是「有个箭头」，它本身有业务含义，所以要有名字：",[34,193,194,197,200],{},[37,195,196],{},"客户与工单之间的菱形写「报修」—— 它代表一个业务动作；",[37,198,199],{},"工单与配件消耗之间的菱形写「包含」—— 它代表整体与部分；",[37,201,202],{},"配件消耗与库存项之间的菱形写「引用」—— 它代表一种较弱的关联。",[13,204,205,208],{},[21,206,207],{},"菱形上写不写名字，是 ERD 合格与否的一个分界","。不写名字的图，只是几个框加几根线，读的人要自己猜这层关系是什么。",[107,210,212],{"id":211},"_12-实体和一般名词的分界线","1.2 实体和一般名词的分界线",[13,214,215],{},"「能被唯一标识」这个条件是实体和一般名词的分界线。像「红色」这种属性值，本身不能单独成为一个实体。",[13,217,218],{},"本例里有两个容易误判的名词：",[119,220,221,234],{},[122,222,223],{},[125,224,225,228,231],{},[128,226,227],{},"名词",[128,229,230],{},"是不是实体",[128,232,233],{},"理由",[141,235,236,247,258],{},[125,237,238,241,244],{},[146,239,240],{},"工单状态（待派\u002F进行中\u002F待件挂起）",[146,242,243],{},"❌ 不是",[146,245,246],{},"它没有自己的身份，是工单的一个属性取值",[125,248,249,252,255],{},[146,250,251],{},"配件分类（空调配件\u002F冰箱配件）",[146,253,254],{},"⚠️ 看情况",[146,256,257],{},"如果只是固定几种，用码表；如果业务要维护分类树，才是实体",[125,259,260,263,266],{},[146,261,262],{},"片区",[146,264,265],{},"✅ 是",[146,267,268],{},"有自己的编号与负责人，且将来会有多个",[13,270,271,272,275],{},"第二个判断很典型：",[21,273,274],{},"码表（code table）与实体的分界，在于「业务要不要维护它」","。固定不变的几个值用枚举或码表；会增删改、还要挂额外的属性（比如分类下的保修政策），才建实体。",[26,277,279],{"id":278},"_2-基数必须由业务方确认","2 基数必须由业务方确认",[107,281,283],{"id":282},"_21-一对一一对多多对多","2.1 一对一、一对多、多对多",[13,285,286,289,290,293,294,297],{},[21,287,288],{},"基数（cardinality）"," 是关系两端各自可以有多少个实例。图上写的是 ",[44,291,292],{},"1"," 和 ",[44,295,296],{},"*","，背后对应的是业务规则：",[119,299,300,313],{},[122,301,302],{},[125,303,304,307,310],{},[128,305,306],{},"关系",[128,308,309],{},"基数",[128,311,312],{},"背后的业务规则",[141,314,315,330,344,358],{},[125,316,317,320,327],{},[146,318,319],{},"客户 — 工单",[146,321,322,324,325],{},[44,323,292],{}," : ",[44,326,296],{},[146,328,329],{},"一个客户可以有多张工单，一张工单只属于一个客户",[125,331,332,335,341],{},[146,333,334],{},"工单 — 配件消耗",[146,336,337,324,339],{},[44,338,292],{},[44,340,296],{},[146,342,343],{},"一张工单可以有多条配件消耗（至少 0 条）",[125,345,346,349,355],{},[146,347,348],{},"配件消耗 — 库存项",[146,350,351,324,353],{},[44,352,296],{},[44,354,292],{},[146,356,357],{},"每条消耗只对应一个配件型号，一个型号可被多条消耗引用",[125,359,360,363,369],{},[146,361,362],{},"工程师 — 片区",[146,364,365,324,367],{},[44,366,296],{},[44,368,296],{},[146,370,371,372],{},"旺季可跨片区支援 → ",[21,373,374],{},"多对多",[13,376,377,378,380,381,384],{},"第四行是",[21,379,374],{},"。它在关系模型里没法直接落地，要拆出一个中间实体（例如 ",[44,382,383],{},"ENGINEER_DISTRICT","），这一点在设计阶段处理。",[13,386,387,390],{},[21,388,389],{},"这也是为什么基数必须问清：多对多的答案会让表多出一张","。如果开发猜成一对多，那张关联表根本不会被建出来。",[107,392,394],{"id":393},"_22-基数不要自己假设","2.2 基数不要自己假设",[13,396,397,398,401],{},"这里有条纪律值得单独强调：",[21,399,400],{},"基数不要自己假设，一定要去问。","「一个工单能不能有多台机器」「一个客户能不能有多个联系人」——这类问题只有业务方能回答，猜错的话，改起来要动数据、动代码、动报表。",[13,403,404],{},"本例里三个必须问的问题，以及问出来之后的影响：",[119,406,407,420],{},[122,408,409],{},[125,410,411,414,417],{},[128,412,413],{},"要问的问题",[128,415,416],{},"如果答「是」",[128,418,419],{},"如果答「否」",[141,421,422,437,450],{},[125,423,424,427,434],{},[146,425,426],{},"一张工单能涉及多台机器吗？",[146,428,429,430,433],{},"需要 ",[44,431,432],{},"APPLIANCE"," 实体",[146,435,436],{},"机器信息直接写在工单上",[125,438,439,442,447],{},[146,440,441],{},"一个客户能有多个联系电话吗？",[146,443,429,444,433],{},[44,445,446],{},"CONTACT",[146,448,449],{},"客户表上两个字段就够",[125,451,452,455,458],{},[146,453,454],{},"一个工程师能属多个片区吗？",[146,456,457],{},"需要关联表",[146,459,460],{},"工程师表上一个外键",[13,462,463,466],{},[21,464,465],{},"这三个问题的答案，直接决定表数量是 5 张还是 8 张","。问一句的成本是五分钟，猜错的成本是几天的迁移。",[107,468,470],{"id":469},"_23-还要确认可选性","2.3 还要确认可选性",[13,472,473,474,477,478,480,481,484],{},"另外还有一端需要确认：",[21,475,476],{},"关系的可选性（optionality）"," —— 这一端是必须存在（",[44,479,292],{},"），还是可以没有（",[44,482,483],{},"0..1","）。这决定了字段是否允许为空。",[13,486,487,488,491,492,495,496,498,499,501],{},"本例里一个具体的可选性问题：",[21,489,490],{},"一张工单必须已经派了工程师吗？"," 答案是不一定 —— 刚创建的工单还没有工程师。所以 ",[44,493,494],{},"work_order.engineer_id"," 必须允许为 NULL，基数是 ",[44,497,483],{}," 而不是 ",[44,500,292],{},"。",[13,503,504,511],{},[21,505,506,507,510],{},"如果这条没问清，写成 ",[44,508,509],{},"NOT NULL","，那「创建工单」这个动作就根本插不进去数据"," —— 因为那一刻还没有工程师。",[26,513,515],{"id":514},"_3-属性分析与数据命名","3 属性分析与数据命名",[107,517,519],{"id":518},"_31-属性归属","3.1 属性归属",[13,521,522],{},"属性分析（attribute analysis）要回答的是：每个实体上到底有哪些属性、它们属于谁。",[13,524,525,526,529,530,533,534,537],{},"这里的第一道坎是",[21,527,528],{},"属性归属","：",[44,531,532],{},"customer_name"," 该放在工单上，还是放在客户上？判断标准是",[21,535,536],{},"依赖关系"," —— 一个客户的姓名，跟他报修几次无关，所以它属于客户。放在工单上就会重复存储（这正是笔记 19 要解决的问题）。",[13,539,540,541],{},"本例里有个更微妙的归属问题：",[21,542,543],{},"客户地址该放哪？",[13,545,546,547,550],{},"答案是：",[21,548,549],{},"分两处","。客户表上存「常用地址」，工单上存「本次上门地址」。理由是：",[34,552,553,556],{},[37,554,555],{},"客户可能搬家，改客户表地址不该改历史工单的地址；",[37,557,558],{},"同一客户的不同工单可能在不同地址（比如给父母家报修）。",[13,560,561,564],{},[21,562,563],{},"「看起来是同一个字段，其实有两份不同含义」","——这类判断只有把业务走一遍才能发现。而如果只存一份（都放客户表），历史工单的地址就会跟着客户搬家而变，那是数据事故。",[107,566,568],{"id":567},"_32-命名标准","3.2 命名标准",[13,570,571,572,575],{},"第二道坎是命名。",[21,573,574],{},"数据命名标准（data naming standard）"," 通常包含这些规则：",[34,577,578,589,599,602],{},[37,579,580,581,584,585,588],{},"实体名用单数名词（",[44,582,583],{},"WORK_ORDER","，不是 ",[44,586,587],{},"WORK_ORDERS","）；",[37,590,591,592,594,595,598],{},"属性名带上实体前缀（",[44,593,532],{},"、",[44,596,597],{},"order_date","），避免在跨表查询时产生歧义；",[37,600,601],{},"统一用下划线分隔，不用驼峰或空格混用；",[37,603,604,605,608],{},"名称里避免出现项目缩写（半年后没人记得 ",[44,606,607],{},"tmp_flg"," 是什么）。",[13,610,611],{},"本例里遵守这套规则的字段清单：",[613,614,619],"pre",{"className":615,"code":616,"language":617,"meta":618,"style":618},"language-sql shiki shiki-themes github-light github-dark","work_order(order_no, customer_id, engineer_id, appliance_type,\n           fault_desc, status, created_at, closed_at)\npart_usage(work_order_id, part_no, qty)\nstock_item(part_no, name, on_hand_qty, safe_qty, district_id)\n","sql","",[44,620,621,630,643,649],{"__ignoreMap":618},[622,623,626],"span",{"class":624,"line":625},"line",1,[622,627,629],{"class":628},"sVt8B","work_order(order_no, customer_id, engineer_id, appliance_type,\n",[622,631,633,636,640],{"class":624,"line":632},2,[622,634,635],{"class":628},"           fault_desc, ",[622,637,639],{"class":638},"szBVR","status",[622,641,642],{"class":628},", created_at, closed_at)\n",[622,644,646],{"class":624,"line":645},3,[622,647,648],{"class":628},"part_usage(work_order_id, part_no, qty)\n",[622,650,652,655,657],{"class":624,"line":651},4,[622,653,654],{"class":628},"stock_item(part_no, ",[622,656,46],{"class":638},[622,658,659],{"class":628},", on_hand_qty, safe_qty, district_id)\n",[13,661,662,663,666,667,293,670,501,673,676],{},"注意 ",[44,664,665],{},"part_usage"," 里两个外键的命名：",[44,668,669],{},"work_order_id",[44,671,672],{},"part_no",[21,674,675],{},"前缀说明它指向谁"," —— 这正是命名标准的价值：开发看字段名就知道该 join 哪张表。",[107,678,680],{"id":679},"_33-类别关键字","3.3 类别关键字",[13,682,683,684,687,688,691],{},"还有一类元素叫",[21,685,686],{},"类别关键字（class keyword）","：用来区分同一属性的不同类别，例如状态字段 ",[44,689,690],{},"order_status"," 的取值集合。它的作用是把「这个字段能取哪些值」集中记录在一处，而不是散落在代码里。",[13,693,694],{},"本例的工单状态取值必须一次定清楚：",[119,696,697,709],{},[122,698,699],{},[125,700,701,704,706],{},[128,702,703],{},"值",[128,705,133],{},[128,707,708],{},"能转到",[141,710,711,729,748,765,780,792],{},[125,712,713,718,721],{},[146,714,715],{},[44,716,717],{},"CREATED",[146,719,720],{},"已创建，未派单",[146,722,723,594,726],{},[44,724,725],{},"ASSIGNED",[44,727,728],{},"CANCELLED",[125,730,731,735,738],{},[146,732,733],{},[44,734,725],{},[146,736,737],{},"已派单，未上门",[146,739,740,594,743,745,746],{},[44,741,742],{},"IN_PROGRESS",[44,744,725],{},"（改派）、",[44,747,728],{},[125,749,750,754,757],{},[146,751,752],{},[44,753,742],{},[146,755,756],{},"进行中",[146,758,759,594,762],{},[44,760,761],{},"PENDING_PARTS",[44,763,764],{},"COMPLETED",[125,766,767,771,774],{},[146,768,769],{},[44,770,761],{},[146,772,773],{},"待件挂起",[146,775,776,594,778],{},[44,777,742],{},[44,779,728],{},[125,781,782,786,789],{},[146,783,784],{},[44,785,764],{},[146,787,788],{},"已完成",[146,790,791],{},"——",[125,793,794,798,801],{},[146,795,796],{},[44,797,728],{},[146,799,800],{},"已作废",[146,802,791],{},[13,804,805,808,809,812,813,815],{},[21,806,807],{},"这张表不只是「有哪些值」，还包括「能从哪转到哪」","。状态机写在这个阶段，后面 22 篇那些对象的行为才有依据 —— 比如 ",[44,810,811],{},"WorkOrder.close()"," 只在 ",[44,814,742],{}," 下才允许。",[26,817,819],{"id":818},"_4-数据字典","4 数据字典",[107,821,823],{"id":822},"_41-四列结构","4.1 四列结构",[13,825,826,827,529],{},"把上面这些汇总成一张表，就是",[21,828,829],{},"数据字典（data dictionary）",[119,831,832,842],{},[122,833,834],{},[125,835,836,839],{},[128,837,838],{},"列",[128,840,841],{},"内容",[141,843,844,852,860,868],{},[125,845,846,849],{},[146,847,848],{},"ATTRIBUTE NAME",[146,850,851],{},"属性名，按命名标准",[125,853,854,857],{},[146,855,856],{},"TYPE",[146,858,859],{},"数据类型（data type）",[125,861,862,865],{},[146,863,864],{},"LEN",[146,866,867],{},"长度（length）",[125,869,870,873],{},[146,871,872],{},"DEFINITION AND BUSINESS RULES",[146,874,875],{},"定义与业务规则（business rule）",[107,877,879],{"id":878},"_42-最值钱也最常空着的一列","4.2 最值钱也最常空着的一列",[13,881,882],{},"第四列是整张字典里最值钱的一列，也是最常被空着的一列。它要写的是「这个字段在业务上意味着什么、有什么约束」。",[13,884,885,886,889],{},"本例填出来是这样（",[44,887,888],{},"work_order"," 表）：",[119,891,892,904],{},[122,893,894],{},[125,895,896,898,900,902],{},[128,897,848],{},[128,899,856],{},[128,901,864],{},[128,903,872],{},[141,905,906,926,941,959,973,988,1003,1018],{},[125,907,908,913,916,919],{},[146,909,910],{},[44,911,912],{},"order_no",[146,914,915],{},"VARCHAR",[146,917,918],{},"20",[146,920,921,922,925],{},"工单号，格式 ",[44,923,924],{},"WO-yyyyMMdd-nnn","；系统生成，不可修改",[125,927,928,933,935,938],{},[146,929,930],{},[44,931,932],{},"customer_id",[146,934,915],{},[146,936,937],{},"10",[146,939,940],{},"报修客户，必须存在于 customer 表",[125,942,943,948,950,952],{},[146,944,945],{},[44,946,947],{},"engineer_id",[146,949,915],{},[146,951,937],{},[146,953,954,955,958],{},"派单工程师；",[21,956,957],{},"派单前为空","；必须是本片区或在支援名单内",[125,960,961,966,968,970],{},[146,962,963],{},[44,964,965],{},"appliance_type",[146,967,915],{},[146,969,918],{},[146,971,972],{},"家电类型，取值见码表；必填",[125,974,975,980,982,985],{},[146,976,977],{},[44,978,979],{},"fault_desc",[146,981,915],{},[146,983,984],{},"500",[146,986,987],{},"客户描述的故障；可空；不对外展示给工程师以外的人",[125,989,990,994,996,998],{},[146,991,992],{},[44,993,639],{},[146,995,915],{},[146,997,918],{},[146,999,1000,1001],{},"工单状态，取值见 §3.3 状态机；默认 ",[44,1002,717],{},[125,1004,1005,1010,1013,1015],{},[146,1006,1007],{},[44,1008,1009],{},"created_at",[146,1011,1012],{},"DATETIME",[146,1014,791],{},[146,1016,1017],{},"创建时间；系统生成；不得晚于当前时间",[125,1019,1020,1025,1027,1029],{},[146,1021,1022],{},[44,1023,1024],{},"closed_at",[146,1026,1012],{},[146,1028,791],{},[146,1030,1031,1032,1035],{},"完成时间；",[44,1033,1034],{},"status = COMPLETED"," 时必填",[13,1037,1038],{},"这些规则如果只留在需求文档的正文里，开发很可能漏掉；写进数据字典，它就和字段绑在一起了。",[13,1040,1041,1046,1047],{},[21,1042,662,1043,1045],{},[44,1044,947],{}," 那一行的「派单前为空」"," —— 这就是 §2.3 讨论的可选性，落到字典里变成一句话。",[21,1048,1049],{},"这一步是把业务规则翻译成技术约束的关键位置。",[26,1051,1053],{"id":1052},"_5-键key","5 键（key）",[107,1055,1057],{"id":1056},"_51-五种键","5.1 五种键",[13,1059,1060],{},"标识一个实体实例用的信息，统称标识符（identifier），落到表上就是各种键：",[34,1062,1063,1069,1077,1089,1095],{},[37,1064,1065,1068],{},[21,1066,1067],{},"主键（primary key）"," —— 唯一标识一行，不允许重复、不允许为空；",[37,1070,1071,1074,1075,54],{},[21,1072,1073],{},"简单键（simple key）"," —— 由单个属性构成，例如 ",[44,1076,912],{},[37,1078,1079,1082,1083,1085,1086,54],{},[21,1080,1081],{},"复合键（composite key）"," —— 由多个属性共同构成，例如 ",[44,1084,665],{}," 的 ",[44,1087,1088],{},"work_order_id + part_no",[37,1090,1091,1094],{},[21,1092,1093],{},"人工键（artificial key）"," —— 业务本身没有合适的唯一标识时，人为加一个编号（例如流水号）；",[37,1096,1097,1100],{},[21,1098,1099],{},"外键（foreign key）"," —— 指向另一个表主键的字段，用来表达关系。",[13,1102,1103],{},"本例里三种键都有，值得逐个看：",[119,1105,1106,1121],{},[122,1107,1108],{},[125,1109,1110,1113,1116,1119],{},[128,1111,1112],{},"表",[128,1114,1115],{},"主键",[128,1117,1118],{},"类型",[128,1120,233],{},[141,1122,1123,1139,1156],{},[125,1124,1125,1129,1133,1136],{},[146,1126,1127],{},[44,1128,888],{},[146,1130,1131],{},[44,1132,912],{},[146,1134,1135],{},"人工键",[146,1137,1138],{},"业务上没有天然唯一标识（客户+时间也可能重复）",[125,1140,1141,1146,1150,1153],{},[146,1142,1143],{},[44,1144,1145],{},"stock_item",[146,1147,1148],{},[44,1149,672],{},[146,1151,1152],{},"简单键",[146,1154,1155],{},"配件编号本身就是业务标识",[125,1157,1158,1162,1166,1169],{},[146,1159,1160],{},[44,1161,665],{},[146,1163,1164],{},[44,1165,1088],{},[146,1167,1168],{},"复合键",[146,1170,1171],{},"一张工单里同一配件只记一行（数量累加）",[13,1173,1174,1175,1178],{},"第三行那个复合键有个隐含的业务规则：",[21,1176,1177],{},"同一张工单里，同一个配件只出现一次，数量累加","。如果业务允许「同一个配件分两次申请」（比如先申请 2 个，后来再申请 1 个），那就要再加一个序号，主键变成三列。",[13,1180,1181,1184],{},[21,1182,1183],{},"这个判断又要回去问业务方"," —— 这就是数据建模里绕不开的一环：每一个技术决定背后都有一条业务规则。",[107,1186,1188],{"id":1187},"_52-实体与实体实例","5.2 实体与实体实例",[13,1190,1191,1192,1195,1196,501],{},"最后补充一个术语：",[21,1193,1194],{},"实体实例（entity occurrence）"," —— 实体是「客户」这个概念，而「张先生这个客户」是一个实体实例。主键要保证的正是",[21,1197,1198],{},"每个实体实例都能被唯一标识",[107,1200,1202],{"id":1201},"_53-一个容易忽略的键问题业务标识会变吗","5.3 一个容易忽略的键问题：业务标识会变吗",[13,1204,1205,1206],{},"本例里有个值得停下来想的点：",[21,1207,1208],{},"客户用手机号还是客户编号做主键？",[13,1210,1211],{},"如果用手机号：客户换号，所有关联的工单都要改外键 —— 而手机号是会换的；\n如果用人造编号：换号只改一个字段，工单不受影响。",[13,1213,1214,1217],{},[21,1215,1216],{},"结论是人造编号","。这条判断的依据是「标识会不会变」，而它同样是个业务问题（客户换号有多常见）。",[26,1219,1220],{"id":1220},"本节自测",[1222,1223,1224,1227,1232,1235,1238,1241,1244,1247],"ol",{},[37,1225,1226],{},"为什么「基数」不能由开发自己决定？猜错会付出什么代价？",[37,1228,1229,1231],{},[44,1230,532],{}," 应该放在客户表还是工单表？判断依据是什么？",[37,1233,1234],{},"数据字典的第四列写的是什么？为什么它不能被省略？",[37,1236,1237],{},"简单键和复合键的区别是什么？各举本例中的一个例子。",[37,1239,1240],{},"什么情况下需要引入人工键？",[37,1242,1243],{},"本例中客户地址为什么要存两份（客户表 + 工单表）？",[37,1245,1246],{},"本例为什么不用手机号做客户表的主键？",[37,1248,1249],{},"「工单状态」是实体吗？码表和实体的分界在哪？",[107,1251,1252],{"id":1252},"参考答案",[1222,1254,1255,1262,1271,1278,1294,1307,1310,1317],{},[37,1256,1257,1258,1261],{},"因为基数背后是",[21,1259,1260],{},"业务规则","，只有业务方能回答。猜错要动数据、动代码、动报表。本例最典型：「一个工程师能属多个片区吗」—— 若猜「不能」，旺季跨片区支援时才发现要加关联表，那时已有几千条数据要迁移（§2.2）。",[37,1263,1264,1267,1268,1270],{},[21,1265,1266],{},"放客户表","。判断依据是",[21,1269,536],{}," —— 客户姓名与他报修几次无关。放工单表会重复存储（§3.1）。",[37,1272,1273,1274,1277],{},"写的是",[21,1275,1276],{},"定义与业务规则","（这个字段业务上意味着什么、有什么约束）。不能省是因为这些规则若只留在需求正文里，开发很容易漏；写进字典后规则就和字段绑在一起了（§4.2）。",[37,1279,1280,1282,1283,588,1286,1288,1289,1085,1291,1293],{},[21,1281,1152],{},"由单个属性构成（本例 ",[44,1284,1285],{},"stock_item.part_no",[21,1287,1168],{},"由多个属性共同构成（本例 ",[44,1290,665],{},[44,1292,1088],{},"）（§5.1）。",[37,1295,1296,1297,1300,1301,1303,1304,1306],{},"当",[21,1298,1299],{},"业务本身没有合适的天然唯一标识","时。本例 ",[44,1302,888],{}," 用人工键 ",[44,1305,912],{},"，因为「客户 + 时间」也可能重复（§5.1）。",[37,1308,1309],{},"因为两者含义不同：客户表存「常用地址」，工单表存「本次上门地址」。客户搬家不该改变历史工单的地址；同一客户的不同工单也可能在不同地址（给父母家报修）（§3.1）。",[37,1311,1312,1313,1316],{},"因为",[21,1314,1315],{},"手机号会变","。用手机号做主键的话，客户换号时所有关联工单的外键都要改；用人造编号只需改一个字段（§5.3）。",[37,1318,1319,1322,1323,1326],{},[21,1320,1321],{},"不是实体","，它是工单的一个属性取值（没有自己的身份）。",[21,1324,1325],{},"码表与实体的分界在于「业务要不要维护它」","：固定几个值用码表；会增删改、还要挂额外属性的才建实体（§1.2）。",[26,1328,1329],{"id":1329},"术语表",[119,1331,1332,1345],{},[122,1333,1334],{},[125,1335,1336,1339,1342],{},[128,1337,1338],{},"术语",[128,1340,1341],{},"中文",[128,1343,1344],{},"一句话解释",[141,1346,1347,1358,1369,1379,1390,1401,1411,1422,1432,1442,1453,1464,1475,1486,1497,1508,1519,1530,1541,1551,1562,1572,1582,1592,1602],{},[125,1348,1349,1352,1355],{},[146,1350,1351],{},"database",[146,1353,1354],{},"数据库",[146,1356,1357],{},"结构化保存数据的集合",[125,1359,1360,1363,1366],{},[146,1361,1362],{},"DBMS",[146,1364,1365],{},"数据库管理系统",[146,1367,1368],{},"位于应用与数据库之间、负责读写数据的软件",[125,1370,1371,1373,1376],{},[146,1372,93],{},[146,1374,1375],{},"关系型数据库管理系统",[146,1377,1378],{},"以表与关系组织数据的 DBMS",[125,1380,1381,1384,1387],{},[146,1382,1383],{},"data model",[146,1385,1386],{},"数据模型",[146,1388,1389],{},"描述数据结构的模型",[125,1391,1392,1395,1398],{},[146,1393,1394],{},"entity relationship diagram（ERD）",[146,1396,1397],{},"实体关系图",[146,1399,1400],{},"画出实体、关系与基数的图",[125,1402,1403,1406,1409],{},[146,1404,1405],{},"entity",[146,1407,1408],{},"实体",[146,1410,151],{},[125,1412,1413,1416,1419],{},[146,1414,1415],{},"entity occurrence",[146,1417,1418],{},"实体实例",[146,1420,1421],{},"实体的一条具体记录",[125,1423,1424,1427,1429],{},[146,1425,1426],{},"relationship",[146,1428,306],{},[146,1430,1431],{},"两个实体之间的业务关联",[125,1433,1434,1437,1439],{},[146,1435,1436],{},"cardinality",[146,1438,309],{},[146,1440,1441],{},"关系两端各自可以有多少个实例",[125,1443,1444,1447,1450],{},[146,1445,1446],{},"optionality",[146,1448,1449],{},"可选性",[146,1451,1452],{},"关系的一端是否必须存在",[125,1454,1455,1458,1461],{},[146,1456,1457],{},"attribute",[146,1459,1460],{},"属性",[146,1462,1463],{},"实体上需要记录的信息项",[125,1465,1466,1469,1472],{},[146,1467,1468],{},"attribute analysis",[146,1470,1471],{},"属性分析",[146,1473,1474],{},"确定属性有哪些、属于哪个实体",[125,1476,1477,1480,1483],{},[146,1478,1479],{},"data naming standard",[146,1481,1482],{},"数据命名标准",[146,1484,1485],{},"全项目统一的字段命名规则",[125,1487,1488,1491,1494],{},[146,1489,1490],{},"class keyword",[146,1492,1493],{},"类别关键字",[146,1495,1496],{},"用来区分属性取值类别的标记",[125,1498,1499,1502,1505],{},[146,1500,1501],{},"code table",[146,1503,1504],{},"码表",[146,1506,1507],{},"存放固定取值集合的辅助表",[125,1509,1510,1513,1516],{},[146,1511,1512],{},"data dictionary",[146,1514,1515],{},"数据字典",[146,1517,1518],{},"逐字段记录名称、类型、长度与业务规则的清单",[125,1520,1521,1524,1527],{},[146,1522,1523],{},"data type",[146,1525,1526],{},"数据类型",[146,1528,1529],{},"字段存储的数据种类",[125,1531,1532,1535,1538],{},[146,1533,1534],{},"length",[146,1536,1537],{},"长度",[146,1539,1540],{},"字段允许的最大长度",[125,1542,1543,1546,1548],{},[146,1544,1545],{},"business rule",[146,1547,1260],{},[146,1549,1550],{},"字段在业务上必须满足的约束",[125,1552,1553,1556,1559],{},[146,1554,1555],{},"identifier",[146,1557,1558],{},"标识符",[146,1560,1561],{},"能唯一区分实体实例的信息",[125,1563,1564,1567,1569],{},[146,1565,1566],{},"primary key",[146,1568,1115],{},[146,1570,1571],{},"唯一标识一行的字段组合",[125,1573,1574,1577,1579],{},[146,1575,1576],{},"simple key",[146,1578,1152],{},[146,1580,1581],{},"由单个属性构成的主键",[125,1583,1584,1587,1589],{},[146,1585,1586],{},"composite key",[146,1588,1168],{},[146,1590,1591],{},"由多个属性共同构成的主键",[125,1593,1594,1597,1599],{},[146,1595,1596],{},"artificial key",[146,1598,1135],{},[146,1600,1601],{},"业务无天然标识时人为引入的编号",[125,1603,1604,1607,1610],{},[146,1605,1606],{},"foreign key",[146,1608,1609],{},"外键",[146,1611,1612],{},"指向另一张表主键的字段",[1614,1615,1616],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":618,"searchDepth":645,"depth":645,"links":1618},[1619,1620,1624,1629,1634,1638,1643,1646],{"id":28,"depth":632,"text":29},{"id":104,"depth":632,"text":105,"children":1621},[1622,1623],{"id":109,"depth":645,"text":110},{"id":211,"depth":645,"text":212},{"id":278,"depth":632,"text":279,"children":1625},[1626,1627,1628],{"id":282,"depth":645,"text":283},{"id":393,"depth":645,"text":394},{"id":469,"depth":645,"text":470},{"id":514,"depth":632,"text":515,"children":1630},[1631,1632,1633],{"id":518,"depth":645,"text":519},{"id":567,"depth":645,"text":568},{"id":679,"depth":645,"text":680},{"id":818,"depth":632,"text":819,"children":1635},[1636,1637],{"id":822,"depth":645,"text":823},{"id":878,"depth":645,"text":879},{"id":1052,"depth":632,"text":1053,"children":1639},[1640,1641,1642],{"id":1056,"depth":645,"text":1057},{"id":1187,"depth":645,"text":1188},{"id":1201,"depth":645,"text":1202},{"id":1220,"depth":632,"text":1220,"children":1644},[1645],{"id":1252,"depth":645,"text":1252},{"id":1329,"depth":632,"text":1329},"学习",null,"2026-11-09","为什么不能拿到需求就动手建表：实体与关系怎么判定、基数为什么必须由业务方确认、数据命名要遵守什么规则，以及数据字典四列里最容易被空着的那一列。","md",false,{},true,"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-18",{"title":5,"description":1650},"软件分析与设计笔记",18,"posts\u002FswDesign\u002Fsw-design-notes-18",[1661,1662,1663,1664],"数据库设计","数据建模","ERD","学习笔记","UsfWy-B511GZ5IHZ9esqygrj5ysZ07yOb8mKIVSwsfQ",[1667,1673,1679,1685,1691,1697,1703,1709,1715,1721,1727,1728,1734,1740,1746,1752,1758,1764,1770,1776,1782,1788,1794,1800,1806,1811,1816,1821,1826,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1882,1886,1892,1896,1900,1904,1908,1912,1916,1920,1925,1929,1933,1938,1943,1949,1953,1958,1962],{"path":1668,"title":1669,"description":1670,"date":1671,"series":1657,"seriesOrder":1672},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-28","软件分析与设计笔记 28：draw.io 作图手册：五张图的符号位置、落笔顺序与自检","不模拟软件界面，只讲清三件事：每个 UML 符号在 draw.io 里搜什么词、一张图该按什么顺序落笔、画完拿什么标准自查。覆盖用例图、活动图、顺序图、类图、界面草图五种图纸，末尾附「最容易画反的十二笔」总检表。","2026-11-19",28,{"path":1674,"title":1675,"description":1676,"date":1677,"series":1657,"seriesOrder":1678},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-27","软件分析与设计笔记 27：Paper 1 应试：从读案例到交卷的一次完整演算","拿一份完整的案例，从头到尾走一遍：怎么读案例列角色、怎么在四个小问之间分配时间、每一张图从哪一句话推出来、两张表怎么填、交卷前拿什么清单自查。附 C25 真题与样卷的题型分值对照。","2026-11-18",27,{"path":1680,"title":1681,"description":1682,"date":1683,"series":1657,"seriesOrder":1684},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-26","软件分析与设计笔记 26：总结：把整条链路串起来","系列收尾：从需求到代码的完整链路回顾、每一环的产出与验收方式、六条贯穿全系列的原则，以及一份可以直接拿去做项目自查的清单。","2026-11-17",26,{"path":1686,"title":1687,"description":1688,"date":1689,"series":1657,"seriesOrder":1690},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-25","软件分析与设计笔记 25：代码重构：坏味道与手法","重构不改变外部行为、只改善内部结构：四种常见坏味道与对应手法、为什么动手之前必须先有测试、什么时候该重构，以及重构和前面那些设计模型是什么关系。","2026-11-16",25,{"path":1692,"title":1693,"description":1694,"date":1695,"series":1657,"seriesOrder":1696},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-24","软件分析与设计笔记 24：设计原则：内聚、耦合与多态","好设计怎么衡量：内聚与耦合分别在说什么、一次从「差设计」到「多态重设计」的完整对照、抽象类与接口的选择依据，以及复用为什么会从优势变成风险。","2026-11-15",24,{"path":1698,"title":1699,"description":1700,"date":1701,"series":1657,"seriesOrder":1702},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-23","软件分析与设计笔记 23：设计建模：从对象模型到能实现的方案","分析模型能用但还不能直接开工：对象模型怎么映射到关系模型、继承映射的三种方案怎么选、数据访问层与 Data Broker 解决什么问题，以及三层架构与 MVC 的分工。","2026-11-14",23,{"path":1704,"title":1705,"description":1706,"date":1707,"series":1657,"seriesOrder":1708},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-22","软件分析与设计笔记 22：分析建模：边界、实体与控制对象","不经过分析直接写代码会踩什么坑、三种分析对象各管一段、怎么用顺序图完成一次用例实现，以及从顺序图导出类的属性、操作与关系的方法。","2026-11-13",22,{"path":1710,"title":1711,"description":1712,"date":1713,"series":1657,"seriesOrder":1714},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-21","软件分析与设计笔记 21：UML：类图与顺序图","为什么需要一种共同的画法：类符号三格怎么画、五种关系的符号与设计含义、每种关系落到 Java 是什么写法，顺序图的三要素怎么摆；以及这三种最容易找不到的符号在 draw.io 里各搜什么词。","2026-11-12",21,{"path":1716,"title":1717,"description":1718,"date":1719,"series":1657,"seriesOrder":1720},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-20","软件分析与设计笔记 20：面向对象概念：对象、关系与多态","面向对象不是「用某个语言写代码」：对象与类的区别、封装真正在保护什么、五种关系（依赖、关联、聚合、组合、继承）各自何时用，以及多态为什么能减少条件判断。","2026-11-11",20,{"path":1722,"title":1723,"description":1724,"date":1725,"series":1657,"seriesOrder":1726},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-19","软件分析与设计笔记 19：规范化与逻辑数据模型","一次改地址引发的数据不一致，讲清规范化的真正动机；UNF 到 3NF 每一步在解决什么问题，派生字段为什么不能存，优化为什么和规范化方向相反，以及逻辑数据模型在 3NF 之上补了什么。","2026-11-10",19,{"path":1655,"title":5,"description":1650,"date":1649,"series":1657,"seriesOrder":1658},{"path":1729,"title":1730,"description":1731,"date":1732,"series":1657,"seriesOrder":1733},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-17","软件分析与设计笔记 17：用生成式 AI 做设计与开发：能用的场景与要防的坑","生成式 AI 在工作流里真正站得住的三个场景 —— 造示例数据、解释陌生代码、起草初稿；它为什么会复现社会偏见、为什么会编得像真的，以及哪些内容绝不能直接采用。","2026-11-08",17,{"path":1735,"title":1736,"description":1737,"date":1738,"series":1657,"seriesOrder":1739},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-16","软件分析与设计笔记 16：从 persona 到 URS：一份交付物的完整骨架","把前面十几篇串成一份能交出去的东西：用户需求规格说明书的九个部分、每一章来自哪个环节、由谁确认，以及面向决策者的提案该讲清哪三件事。","2026-11-07",16,{"path":1741,"title":1742,"description":1743,"date":1744,"series":1657,"seriesOrder":1745},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-15","软件分析与设计笔记 15：非功能需求：把「快、稳、安全」变成数字","为什么「系统要快」不是一条需求：非功能需求怎么量化，决定性能的用户量、交易量、数据量三张表怎么估，安全需求要写清什么，以及可靠性的「不可用」该怎么定义。","2026-11-06",15,{"path":1747,"title":1748,"description":1749,"date":1750,"series":1657,"seriesOrder":1751},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-14","软件分析与设计笔记 14：软件测试：层级、方法与等价类划分","「我们测过了」这句话问题在哪：为什么静态评审和动态测试缺一不可，五个测试层级各自验证什么，白盒与黑盒的差别，以及用等价类划分和边界值把无限输入变成有限用例的方法。","2026-11-05",14,{"path":1753,"title":1754,"description":1755,"date":1756,"series":1657,"seriesOrder":1757},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-13","软件分析与设计笔记 13：需求获取：五种调查方法与一场访谈","需求从哪里来：五种事实调查方法各自适合回答什么、代价是什么，访谈从准备到记录的全过程，问卷设计里最容易犯的错，以及访谈记录必须写下来的那几项信息。","2026-11-04",13,{"path":1759,"title":1760,"description":1761,"date":1762,"series":1657,"seriesOrder":1763},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-12","软件分析与设计笔记 12：前端设计：把流程变成屏幕","界面设计不是「画得好看」：从活动图走到屏幕的三步，CRUD 任务流怎么当检查表，界面必须回答的三个问题，信息架构与五种导航类型，视觉一致性与留白，以及移动端与 Web 端的差别。","2026-11-03",12,{"path":1765,"title":1766,"description":1767,"date":1768,"series":1657,"seriesOrder":1769},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-11","软件分析与设计笔记 11：Agentic AI 与业务流程自动化","AI agent 到底是什么：五项能力与四个核心组件，什么场景该用定时任务、什么该用程序、什么才值得用 agent，两种实现路径的差别，必须提前防住的四类失败模式，以及设计方法从提示词到编排的演进。","2026-11-02",11,{"path":1771,"title":1772,"description":1773,"date":1774,"series":1657,"seriesOrder":1775},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-10","软件分析与设计笔记 10：精炼用例模型：include、extend 与泛化","三种精炼用例关系的判断标准：include 什么时候用、它和 extend 的区别到底在哪、参与者泛化与用例泛化各解决什么问题，以及精炼到什么程度就该停手。","2026-11-01",10,{"path":1777,"title":1778,"description":1779,"date":1780,"series":1657,"seriesOrder":1781},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-09","软件分析与设计笔记 09：功能需求（二）：从流程到业务实体","活动图画完之后，怎么从流程里筛出真正的业务实体：三条判断标准、哪些名词其实不该建实体、属性从哪里来，以及领域模型和后面的数据模型分别负责什么。","2026-10-31",9,{"path":1783,"title":1784,"description":1785,"date":1786,"series":1657,"seriesOrder":1787},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-08","软件分析与设计笔记 08：功能需求（一）：用例模型与活动图","从用户故事到用例的翻译过程：什么才算「好」的参与者，系统边界怎么划，用例描述必须写清哪四件事，活动图怎么把分支、并行与泳道画清楚，以及这两个图在 draw.io 里各用哪个形状、最容易画错哪一笔。","2026-10-30",8,{"path":1789,"title":1790,"description":1791,"date":1792,"series":1657,"seriesOrder":1793},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-07","软件分析与设计笔记 07：用户故事：把需求写成可验收的一句话","用户故事的三段结构，怎么从用户画像和数据地图里把它提取出来，一条故事多大算合适、时序为什么也是需求的一部分，以及验收标准怎么写才算真的可验收。","2026-10-29",7,{"path":1795,"title":1796,"description":1797,"date":1798,"series":1657,"seriesOrder":1799},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-06","软件分析与设计笔记 06：范围界定（数据侧）：用数据校验你的判断","访谈结论和数字对不上时该怎么办：分析的四个层次分别在回答什么问题、为什么不能跳级、数据准备与搜集怎么做成一张应用地图，以及数据最容易在哪几个地方骗你。","2026-10-28",6,{"path":1801,"title":1802,"description":1803,"date":1804,"series":1657,"seriesOrder":1805},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-05","软件分析与设计笔记 05：范围界定（用户侧）：用户画像与客户旅程图","为什么「所有用户」是一个陷阱，项目边界该怎么划，用户画像要写哪五个要素、最容易写成什么废纸，客户旅程图怎么把阶段、想法和情绪画在同一张纸上，以及怎么从这两样东西一步步推到可验证的需求。","2026-10-27",5,{"path":1807,"title":1808,"description":1809,"date":1810,"series":1657,"seriesOrder":651},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-04","软件分析与设计笔记 04：接手陌生案例的第一步：三问与一张表","拿到一个完全陌生的业务案例，怎么从零开始不跑偏：只问 Who、What、Why 三个问题，把「问题—事实—假设—理由」记成一张四列表，说清为什么假设必须写明理由，以及带着问题进访谈为什么比空手去有效得多。","2026-10-26",{"path":1812,"title":1813,"description":1814,"date":1815,"series":1657,"seriesOrder":645},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-03","软件分析与设计笔记 03：Agile 与 Scrum：迭代、角色与产出物","从「三周做完交付，客户说这不是我要的」讲起，说清瀑布式与迭代式的根本差别，Scrum 的三个角色和三种产出物，一个 sprint 里四次固定活动各自在解决什么问题，以及敏捷里最常见的几个误解。","2026-10-25",{"path":1817,"title":1818,"description":1819,"date":1820,"series":1657,"seriesOrder":632},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-02","软件分析与设计笔记 02：设计思维、用户驱动与数据驱动","从「客服要一个能一次看完所有排期的界面」这个翻车案例讲起，说清设计思维到底是什么、用户驱动与数据驱动各自能看见什么又看不见什么、为什么真正的落点是把两者合起来的 data-informed design，以及访谈中怎么问出有用的信息。","2026-10-24",{"path":1822,"title":1823,"description":1824,"date":1825,"series":1657,"seriesOrder":625},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-01","软件分析与设计笔记 01：课程地图与从需求到设计的主干","这一篇不写具体方法，先把整条链路铺开：软件分析与设计到底要解决什么问题，从需求到设计分成哪两段，中间有哪十几个环节，一份完整的交付物里应该有什么，以及后面 27 篇笔记怎么排、新人该按什么顺序读。","2026-10-23",{"path":1827,"title":1828,"description":1829,"date":1830,"series":1831,"seriesOrder":625},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-01","Spring 学习笔记 01：IoC、依赖注入与第一个 Spring Boot 应用","从「手写 new 出来的三层结构」的痛点讲起，说清 IoC 容器、Bean 与依赖注入是怎么回事，再用 Maven 与 Spring Initializr 跑起第一个能返回 JSON 的应用。","2026-10-15","Spring 学习笔记",{"path":1833,"title":1834,"description":1835,"date":1830,"series":1831,"seriesOrder":632},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-02","Spring 学习笔记 02：Spring Data JPA 基础——实体映射与 Repository","用 ORM 把类映射到表：@Entity、@Id、@GeneratedValue 怎么用，CrudRepository 三层接口怎么选，H2 与 MySQL 怎么配，以及 ddl-auto 到底该写哪个值。",{"path":1837,"title":1838,"description":1839,"date":1830,"series":1831,"seriesOrder":645},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-03","Spring 学习笔记 03：JPA 查询——派生方法、JPQL 与分页排序","Repository 自带的方法只够按主键查，这篇讲三种自己造查询的方式：方法名派生、@Query 写 JPQL、原生 SQL，以及 Pageable 分页排序和投影返回。",{"path":1841,"title":1842,"description":1843,"date":1830,"series":1831,"seriesOrder":651},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-04","Spring 学习笔记 04：JPA 关联映射与仓储测试","一对多、多对多怎么映射，谁负责维护外键，FetchType 与 CascadeType 该怎么选，顺带用 @DataJpaTest 与 AssertJ 把仓储层测起来。",{"path":1845,"title":1846,"description":1847,"date":1830,"series":1831,"seriesOrder":1805},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-05","Spring 学习笔记 05：Spring MVC 与 Thymeleaf 入门","一次页面请求在 Spring MVC 里走了哪几步，@Controller 怎么把数据交给模板，@PathVariable 与 @RequestParam 怎么取参数，以及控制器为什么必须是无状态的。",{"path":1849,"title":1850,"description":1851,"date":1830,"series":1831,"seriesOrder":1799},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-06","Spring 学习笔记 06：表单绑定与服务端校验","把表单字段绑到对象上，用 Bean Validation 注解做服务端校验，把错误信息显示回用户刚填的那一页，再补上自定义校验注解与全局异常处理。",{"path":1853,"title":1854,"description":1855,"date":1830,"series":1831,"seriesOrder":1793},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-07","Spring 学习笔记 07：Thymeleaf 片段复用与国际化","用 th:fragment 与 th:insert\u002Fth:replace 把导航栏、页脚抽成片段，页面套用统一布局，再用 messages.properties 与 LocaleResolver 做中英文切换。",{"path":1857,"title":1858,"description":1859,"date":1830,"series":1831,"seriesOrder":1787},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-08","Spring 学习笔记 08：HTTP Session 与会话状态管理","HTTP 无状态带来的问题，Cookie 与 Session 怎么分工，Spring 里读写会话的几种方式，以及会话固定、超时、多实例共享这些必须想清楚的事。",{"path":1861,"title":1862,"description":1863,"date":1830,"series":1831,"seriesOrder":1781},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-09","Spring 学习笔记 09：拦截器 HandlerInterceptor","登录检查、权限判断、请求耗时统计这类横切逻辑该放哪：HandlerInterceptor 的三个回调各在什么时机执行，怎么注册与排除路径，以及它和过滤器的分工。",{"path":1865,"title":1866,"description":1867,"date":1830,"series":1831,"seriesOrder":1775},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-10","Spring 学习笔记 10：Service 层与事务","一组数据库操作要么全成功要么全失败，靠 @Transactional 怎么做到；传播行为、隔离级别、回滚规则怎么选，以及自调用失效、乐观锁悲观锁这些实战里的坑。",{"path":1869,"title":1870,"description":1871,"date":1830,"series":1831,"seriesOrder":1769},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-11","Spring 学习笔记 11：REST API 基础——把 Service 暴露成接口","同一套 Service 怎么同时服务页面和客户端：@RestController 的路由与方法语义、状态码怎么给、DTO 怎么定，以及 MockMvc 端到端和切片测试的两种写法与各自的坑。",{"path":1873,"title":1874,"description":1875,"date":1830,"series":1831,"seriesOrder":1763},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-12","Spring 学习笔记 12：REST 进阶——版本、更新、跨域与文档化","接口能跑通之后要处理的几件事：URL 与响应结构怎么定、版本怎么演进、更新怎么做到可安全重试、CORS 怎么配、HATEOAS 与 Spring Data REST 该不该用。",{"path":1877,"title":1878,"description":1879,"date":1880,"series":1881,"seriesOrder":651},"\u002Fposts\u002Fjava\u002Fjava-functionalprogramming-notes","Java 函数式编程笔记：Lambda、函数式接口与 Streams","学习 Java 函数式编程时的个人笔记：Lambda 表达式的五步简写与方法引用、常见函数式接口、Stream 流水线与惰性求值，以及创建流、中间操作、终止操作与收集器的用法。示例代码均为自写，运行结果来自本地实测。","2026-10-13","Java 学习笔记",{"path":1883,"title":1884,"description":1885,"date":1880,"series":1881,"seriesOrder":645},"\u002Fposts\u002Fjava\u002Fjava-generics-notes","Java 泛型笔记：泛型方法、有界类型与泛型类","学习 Java 泛型时的个人笔记：从重复的重载方法与多态尝试讲起，再到泛型方法与类型推断、有界泛型类型、Comparable 与 Number 边界，以及泛型类在集合中的用法。示例代码均为自写，运行结果来自本地实测。",{"path":1887,"title":1888,"description":1889,"date":1890,"series":1891,"seriesOrder":651},"\u002Fposts\u002Fmysql\u002Fmysql-learning-04","MySQL 学习笔记 04：数据操纵（增删改）","MySQL 学习笔记第四篇：INSERT 插入（含批量插入与 INSERT ... SELECT）、UPDATE 更新、DELETE 删除，以及各自的坑。","2026-10-10","SQL 学习笔记",{"path":1893,"title":1894,"description":1895,"date":1890,"series":1891,"seriesOrder":1805},"\u002Fposts\u002Fmysql\u002Fmysql-learning-05","MySQL 学习笔记 05：DDL 概览","MySQL 学习笔记第五篇：数据定义语言概览——Schema、Table、View、Index、Constraints 这几类对象分别对应哪些 CREATE \u002F ALTER \u002F DROP 命令。",{"path":1897,"title":1898,"description":1899,"date":1890,"series":1891,"seriesOrder":1799},"\u002Fposts\u002Fmysql\u002Fmysql-learning-06","MySQL 学习笔记 06：CREATE TABLE 与数据类型","MySQL 学习笔记第六篇：CREATE TABLE 的完整语法、常用数据类型的选型建议，以及外键的两种定义方式。",{"path":1901,"title":1902,"description":1903,"date":1890,"series":1891,"seriesOrder":1793},"\u002Fposts\u002Fmysql\u002Fmysql-learning-07","MySQL 学习笔记 07：索引","MySQL 学习笔记第七篇：索引的作用与 B+ 树结构、单列与复合索引的创建、索引的代价与适用场景。",{"path":1905,"title":1906,"description":1907,"date":1890,"series":1891,"seriesOrder":1787},"\u002Fposts\u002Fmysql\u002Fmysql-learning-08","MySQL 学习笔记 08：DROP 与 ALTER","MySQL 学习笔记第八篇：DROP 删除数据库对象、ALTER 修改表结构，以及常见的坑。",{"path":1909,"title":1910,"description":1911,"date":1890,"series":1891,"seriesOrder":1781},"\u002Fposts\u002Fmysql\u002Fmysql-learning-09","MySQL 学习笔记 09：约束与数据完整性","MySQL 学习笔记第九篇：SQL 约束——NOT NULL、CHECK、PRIMARY KEY、FOREIGN KEY 等如何保证数据完整性，以及它们的对比。",{"path":1913,"title":1914,"description":1915,"date":1890,"series":1891,"seriesOrder":1775},"\u002Fposts\u002Fmysql\u002Fmysql-learning-10","MySQL 学习笔记 10：视图","MySQL 学习笔记第十篇：视图（Views）的定义与特性、和表的对比、创建与使用，以及可更新视图的限制。",{"path":1917,"title":1918,"description":1919,"date":1890,"series":1891,"seriesOrder":1769},"\u002Fposts\u002Fmysql\u002Fmysql-learning-11","MySQL 学习笔记 11：存储过程","MySQL 学习笔记第十一篇：存储过程（Stored Procedures）的概念、创建与调用、带参数的写法，以及优缺点。",{"path":1921,"title":1922,"description":1923,"date":1924,"series":1891,"seriesOrder":625},"\u002Fposts\u002Fmysql\u002Fmysql-learning-01","MySQL 学习笔记 01：MySQL 基础知识","MySQL 学习笔记第一篇：MySQL Server 与 Workbench 的安装，数据库与 DBMS 的基本概念，关系模型、ACID 事务、常见数据类型，以及后续的学习路线。","2026-10-09",{"path":1926,"title":1927,"description":1928,"date":1924,"series":1891,"seriesOrder":632},"\u002Fposts\u002Fmysql\u002Fmysql-learning-02","MySQL 学习笔记 02：SELECT 查询与过滤","MySQL 学习笔记第二篇：SELECT 的基本语法与逻辑执行顺序，WHERE 过滤（比较、LIKE、IN、BETWEEN、NULL）、各种 JOIN、UNION 与子查询。",{"path":1930,"title":1931,"description":1932,"date":1924,"series":1891,"seriesOrder":645},"\u002Fposts\u002Fmysql\u002Fmysql-learning-03","MySQL 学习笔记 03：聚合、分组与输出控制","MySQL 学习笔记第三篇：COUNT\u002FSUM\u002FAVG 等聚合函数、GROUP BY 分组、HAVING 过滤、ORDER BY 与 LIMIT，以及一条 SELECT 的完整逻辑执行顺序。",{"path":1934,"title":1935,"description":1936,"date":1937,"series":1881,"seriesOrder":632},"\u002Fposts\u002Fjava\u002Fjava-oop-notes","Java 面向对象笔记：类、继承、多态与异常","学习 Java 面向对象编程时的个人笔记：类与对象、封装与访问修饰符、继承与多态、集合、抽象类与接口、异常处理。示例代码均为自写，运行结果来自本地实测。","2026-10-06",{"path":1939,"title":1940,"description":1941,"date":1942,"series":1881,"seriesOrder":625},"\u002Fposts\u002Fjava\u002Fjava-basics-notes","Java 基础语法笔记：从环境安装到方法与数组","学习 Java 基础语法时的个人笔记：JDK 安装、输入输出、变量与运算符、条件与循环、字符串、数组与方法。示例代码均为自写，运行结果来自本地实测。","2026-10-05",{"path":1944,"title":1945,"description":1946,"date":1947,"series":1948,"seriesOrder":625},"\u002Fposts\u002Fgit\u002Fgit-install-and-basics","Git 安装与基本使用","从装好 Git 到跑通日常提交流程，把最常用的几条命令和它们背后的三个区域讲清楚。","2026-10-04","Git 入门",{"path":1950,"title":1951,"description":1952,"date":1947,"series":1948,"seriesOrder":632},"\u002Fposts\u002Fgit\u002Flocal-remote-repository","本地远程仓库的部署","用自己的机器当 Git 服务器：建一个裸仓库、走 SSH 连上去，把本地提交推上去。",{"path":1954,"title":1955,"description":1956,"date":1947,"series":1957,"seriesOrder":625},"\u002Fposts\u002Fguide\u002Fguide-file-layout","文件都放在哪","文章、图片、封面分别放在仓库的什么位置，哪些目录可以放手改、哪些碰了会出问题。","博客使用指南",{"path":1959,"title":1960,"description":1961,"date":1947,"series":1957,"seriesOrder":632},"\u002Fposts\u002Fguide\u002Fguide-frontmatter","frontmatter 每个字段是什么意思","文章开头那段被横线包住的字段，逐个说明作用、默认行为和写错的后果。",{"path":1963,"title":1964,"description":1965,"date":1947,"series":1957,"seriesOrder":645},"\u002Fposts\u002Fguide\u002Fguide-markdown","正文怎么写 —— Markdown 与排版效果","标题、链接、图片、代码、表格分别怎么写，以及本站对它们做了哪些自定义处理。",1793268185356]