[{"data":1,"prerenderedAt":3519},["ShallowReactive",2],{"post:\u002Fposts\u002Fjavaspring\u002Fspring-notes-02":3,"posts:ordered":3242},{"id":4,"title":5,"body":6,"category":3227,"cover":3228,"date":3229,"description":3230,"extension":3231,"featured":3232,"meta":3233,"navigation":435,"path":3234,"seo":3235,"series":3236,"seriesOrder":206,"stem":3237,"tags":3238,"__hash__":3241},"posts\u002Fposts\u002Fjavaspring\u002Fspring-notes-02.md","Spring 学习笔记 02：Spring Data JPA 基础——实体映射与 Repository",{"type":7,"value":8,"toc":3204},"minimark",[9,16,21,33,46,66,70,73,145,151,170,174,179,365,375,379,385,460,471,484,530,537,541,544,605,608,642,652,656,660,1519,1530,1685,1688,1762,1766,1852,1862,1866,1869,1936,1939,1970,1981,1985,1989,1992,2061,2070,2074,2140,2149,2164,2495,2502,2519,2523,2765,2771,2788,2792,2874,2877,2901,2905,2929,2936,2940,3126,3130,3189,3200],[10,11,12],"blockquote",{},[13,14,15],"p",{},"这是一份个人学习笔记：概念部分是我按自己的理解写的，示例代码都是自己敲的，参考的是\nSpring 官方文档与 Jakarta Persistence 规范。理解有偏差的地方欢迎指出。",[17,18,20],"h2",{"id":19},"_0-内存实现撑不了太久","0 内存实现撑不了太久",[13,22,23,24,28,29,32],{},"上一篇的 ",[25,26,27],"code",{},"InMemoryBookRepository"," 用 ",[25,30,31],{},"Map"," 存数据，好处是跑起来什么都不用配，坏处也很明显：",[34,35,36,40,43],"ul",{},[37,38,39],"li",{},"进程一重启，数据全没了；",[37,41,42],{},"想按条件查询，只能自己写循环过滤；",[37,44,45],{},"并发、事务、分页这些迟早要面对的问题，一个都躲不掉。",[13,47,48,49,53,54,57,58,61,62,65],{},"换成真的数据库，绕不开一个矛盾：",[50,51,52],"strong",{},"Java 里是对象和引用，数据库里是表和行","。",[25,55,56],{},"Book"," 对象有\n",[25,59,60],{},"author"," 字段，数据库里可能就是 ",[25,63,64],{},"authors"," 表加一个外键。谁来做这层翻译？",[17,67,69],{"id":68},"_1-ormjpahibernatespring-data-jpa","1 ORM、JPA、Hibernate、Spring Data JPA",[13,71,72],{},"这四个词经常被混着说，先分清它们的位置：",[74,75,76,89],"table",{},[77,78,79],"thead",{},[80,81,82,86],"tr",{},[83,84,85],"th",{},"名词",[83,87,88],{},"是什么",[90,91,92,101,121,133],"tbody",{},[80,93,94,98],{},[95,96,97],"td",{},"ORM",[95,99,100],{},"一种思路：把对象模型和关系模型对应起来，省掉手写 SQL 搬运数据",[80,102,103,106],{},[95,104,105],{},"JPA",[95,107,108,109,112,113,116,117,120],{},"一套",[50,110,111],{},"规范","（Jakarta Persistence），定义了 ",[25,114,115],{},"@Entity","、",[25,118,119],{},"EntityManager","、JPQL 等 API 与语义",[80,122,123,126],{},[95,124,125],{},"Hibernate",[95,127,128,129,132],{},"JPA 的具体",[50,130,131],{},"实现","之一，那些注解最终是它在干活",[80,134,135,138],{},[95,136,137],{},"Spring Data JPA",[95,139,140,141,144],{},"站在 JPA 之上的",[50,142,143],{},"封装","，负责按接口自动生成 Repository 实现",[13,146,147,148,150],{},"所以写代码时：注解来自 JPA 规范，运行时是 Hibernate 在翻译 SQL，而帮你省掉 ",[25,149,119],{},"\n那一堆模板代码的是 Spring Data JPA。",[13,152,153,154,160,161,163,164,166,167,53],{},"顺带解决一个上一篇留下的问题：",[50,155,156,157],{},"实体不能是 ",[25,158,159],{},"record","。JPA 要求实体类有可访问的无参构造器、\n字段可变（要能被代理和填充），",[25,162,159],{}," 的 final 字段两条都不满足。领域模型用 ",[25,165,159],{}," 没问题，\n",[50,168,169],{},"被 JPA 管理的实体要用普通 class",[17,171,173],{"id":172},"_2-引入依赖与配置数据源","2 引入依赖与配置数据源",[175,176,178],"h3",{"id":177},"_21-依赖","2.1 依赖",[180,181,186],"pre",{"className":182,"code":183,"language":184,"meta":185,"style":185},"language-xml shiki shiki-themes github-light github-dark","\u003Cdependency>\n    \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n    \u003CartifactId>spring-boot-starter-data-jpa\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n\u003Cdependency>\n    \u003CgroupId>com.h2database\u003C\u002FgroupId>\n    \u003CartifactId>h2\u003C\u002FartifactId>\n    \u003Cscope>runtime\u003C\u002Fscope>\n\u003C\u002Fdependency>\n\u003Cdependency>\n    \u003CgroupId>com.mysql\u003C\u002FgroupId>\n    \u003CartifactId>mysql-connector-j\u003C\u002FartifactId>\n    \u003Cscope>runtime\u003C\u002Fscope>\n\u003C\u002Fdependency>\n","xml","",[25,187,188,204,220,235,245,254,268,282,297,306,315,329,343,356],{"__ignoreMap":185},[189,190,193,197,201],"span",{"class":191,"line":192},"line",1,[189,194,196],{"class":195},"sVt8B","\u003C",[189,198,200],{"class":199},"s9eBZ","dependency",[189,202,203],{"class":195},">\n",[189,205,207,210,213,216,218],{"class":191,"line":206},2,[189,208,209],{"class":195},"    \u003C",[189,211,212],{"class":199},"groupId",[189,214,215],{"class":195},">org.springframework.boot\u003C\u002F",[189,217,212],{"class":199},[189,219,203],{"class":195},[189,221,223,225,228,231,233],{"class":191,"line":222},3,[189,224,209],{"class":195},[189,226,227],{"class":199},"artifactId",[189,229,230],{"class":195},">spring-boot-starter-data-jpa\u003C\u002F",[189,232,227],{"class":199},[189,234,203],{"class":195},[189,236,238,241,243],{"class":191,"line":237},4,[189,239,240],{"class":195},"\u003C\u002F",[189,242,200],{"class":199},[189,244,203],{"class":195},[189,246,248,250,252],{"class":191,"line":247},5,[189,249,196],{"class":195},[189,251,200],{"class":199},[189,253,203],{"class":195},[189,255,257,259,261,264,266],{"class":191,"line":256},6,[189,258,209],{"class":195},[189,260,212],{"class":199},[189,262,263],{"class":195},">com.h2database\u003C\u002F",[189,265,212],{"class":199},[189,267,203],{"class":195},[189,269,271,273,275,278,280],{"class":191,"line":270},7,[189,272,209],{"class":195},[189,274,227],{"class":199},[189,276,277],{"class":195},">h2\u003C\u002F",[189,279,227],{"class":199},[189,281,203],{"class":195},[189,283,285,287,290,293,295],{"class":191,"line":284},8,[189,286,209],{"class":195},[189,288,289],{"class":199},"scope",[189,291,292],{"class":195},">runtime\u003C\u002F",[189,294,289],{"class":199},[189,296,203],{"class":195},[189,298,300,302,304],{"class":191,"line":299},9,[189,301,240],{"class":195},[189,303,200],{"class":199},[189,305,203],{"class":195},[189,307,309,311,313],{"class":191,"line":308},10,[189,310,196],{"class":195},[189,312,200],{"class":199},[189,314,203],{"class":195},[189,316,318,320,322,325,327],{"class":191,"line":317},11,[189,319,209],{"class":195},[189,321,212],{"class":199},[189,323,324],{"class":195},">com.mysql\u003C\u002F",[189,326,212],{"class":199},[189,328,203],{"class":195},[189,330,332,334,336,339,341],{"class":191,"line":331},12,[189,333,209],{"class":195},[189,335,227],{"class":199},[189,337,338],{"class":195},">mysql-connector-j\u003C\u002F",[189,340,227],{"class":199},[189,342,203],{"class":195},[189,344,346,348,350,352,354],{"class":191,"line":345},13,[189,347,209],{"class":195},[189,349,289],{"class":199},[189,351,292],{"class":195},[189,353,289],{"class":199},[189,355,203],{"class":195},[189,357,359,361,363],{"class":191,"line":358},14,[189,360,240],{"class":195},[189,362,200],{"class":199},[189,364,203],{"class":195},[13,366,367,370,371,374],{},[25,368,369],{},"spring-boot-starter-data-jpa"," 会带进 Spring Data JPA、Hibernate、JDBC 与事务管理，版本仍然\n由 parent 统一管。两个驱动都用 ",[25,372,373],{},"runtime"," 作用域：编译时不需要它们，运行时才加载。",[175,376,378],{"id":377},"_22-h2先跑通再换库","2.2 H2：先跑通，再换库",[13,380,381,382,53],{},"H2 是个内存数据库，进程结束数据就没了——这正是它适合开发和测试的原因：",[50,383,384],{},"不用装数据库、\n不用清数据、每次启动都是干净状态",[180,386,390],{"className":387,"code":388,"language":389,"meta":185,"style":185},"language-properties shiki shiki-themes github-light github-dark","# application.properties\nspring.datasource.url=jdbc:h2:mem:librarydb\nspring.datasource.driver-class-name=org.h2.Driver\nspring.datasource.username=sa\nspring.datasource.password=\n\nspring.jpa.show-sql=true\nspring.jpa.hibernate.ddl-auto=create-drop\nspring.h2.console.enabled=true\n","properties",[25,391,392,398,407,415,423,431,437,445,453],{"__ignoreMap":185},[189,393,394],{"class":191,"line":192},[189,395,397],{"class":396},"sJ8bj","# application.properties\n",[189,399,400,404],{"class":191,"line":206},[189,401,403],{"class":402},"szBVR","spring.datasource.url",[189,405,406],{"class":195},"=jdbc:h2:mem:librarydb\n",[189,408,409,412],{"class":191,"line":222},[189,410,411],{"class":402},"spring.datasource.driver-class-name",[189,413,414],{"class":195},"=org.h2.Driver\n",[189,416,417,420],{"class":191,"line":237},[189,418,419],{"class":402},"spring.datasource.username",[189,421,422],{"class":195},"=sa\n",[189,424,425,428],{"class":191,"line":247},[189,426,427],{"class":402},"spring.datasource.password",[189,429,430],{"class":195},"=\n",[189,432,433],{"class":191,"line":256},[189,434,436],{"emptyLinePlaceholder":435},true,"\n",[189,438,439,442],{"class":191,"line":270},[189,440,441],{"class":402},"spring.jpa.show-sql",[189,443,444],{"class":195},"=true\n",[189,446,447,450],{"class":191,"line":284},[189,448,449],{"class":402},"spring.jpa.hibernate.ddl-auto",[189,451,452],{"class":195},"=create-drop\n",[189,454,455,458],{"class":191,"line":299},[189,456,457],{"class":402},"spring.h2.console.enabled",[189,459,444],{"class":195},[13,461,462,463,466,467,470],{},"关于 H2 的 URL：",[25,464,465],{},"jdbc:h2:mem:\u003C名字>"," 是内存库，",[25,468,469],{},"jdbc:h2:file:.\u002Fdata\u002Flibrarydb"," 会把数据写到\n磁盘文件，两者其余用法一致。",[13,472,473,479,480,483],{},[50,474,475,476,478],{},"注意 ",[25,477,457],{}," 在 Boot 4 里光写属性不够","：H2 控制台的自动配置已经拆成\n独立模块，只有驱动不会启用它，还要加一个依赖（Boot 3 里内置在 ",[25,481,482],{},"spring-boot-autoconfigure","，\n写属性即可）：",[180,485,487],{"className":182,"code":486,"language":184,"meta":185,"style":185},"\u003Cdependency>\n    \u003CgroupId>org.springframework.boot\u003C\u002FgroupId>\n    \u003CartifactId>spring-boot-h2console\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n",[25,488,489,497,509,522],{"__ignoreMap":185},[189,490,491,493,495],{"class":191,"line":192},[189,492,196],{"class":195},[189,494,200],{"class":199},[189,496,203],{"class":195},[189,498,499,501,503,505,507],{"class":191,"line":206},[189,500,209],{"class":195},[189,502,212],{"class":199},[189,504,215],{"class":195},[189,506,212],{"class":199},[189,508,203],{"class":195},[189,510,511,513,515,518,520],{"class":191,"line":222},[189,512,209],{"class":195},[189,514,227],{"class":199},[189,516,517],{"class":195},">spring-boot-h2console\u003C\u002F",[189,519,227],{"class":199},[189,521,203],{"class":195},[189,523,524,526,528],{"class":191,"line":237},[189,525,240],{"class":195},[189,527,200],{"class":199},[189,529,203],{"class":195},[13,531,532,533,536],{},"判断方法：加完依赖启动，日志里会出现 ",[25,534,535],{},"\u002Fh2-console"," 的映射；没加依赖时它安静地不生效，\n很容易误判成「H2 控制台坏了」。",[175,538,540],{"id":539},"_23-mysql换-profile-而不是改代码","2.3 MySQL：换 profile 而不是改代码",[13,542,543],{},"用 Profile 把两套配置分开放，切换时只换一个参数：",[180,545,547],{"className":387,"code":546,"language":389,"meta":185,"style":185},"# application-mysql.properties\nspring.datasource.url=jdbc:mysql:\u002F\u002Flocalhost:3306\u002Flibrary?useSSL=false&serverTimezone=Asia\u002FSingapore\nspring.datasource.username=library_user\nspring.datasource.password=${DB_PASSWORD:}\nspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver\n\nspring.jpa.hibernate.ddl-auto=validate\n",[25,548,549,554,573,580,587,594,598],{"__ignoreMap":185},[189,550,551],{"class":191,"line":192},[189,552,553],{"class":396},"# application-mysql.properties\n",[189,555,556,558,561,564,567,570],{"class":191,"line":206},[189,557,403],{"class":402},[189,559,560],{"class":195},"=jdbc:mysql:\u002F\u002Flocalhost:3306\u002Flibrary?",[189,562,563],{"class":402},"useSSL",[189,565,566],{"class":195},"=false&",[189,568,569],{"class":402},"serverTimezone",[189,571,572],{"class":195},"=Asia\u002FSingapore\n",[189,574,575,577],{"class":191,"line":222},[189,576,419],{"class":402},[189,578,579],{"class":195},"=library_user\n",[189,581,582,584],{"class":191,"line":237},[189,583,427],{"class":402},[189,585,586],{"class":195},"=${DB_PASSWORD:}\n",[189,588,589,591],{"class":191,"line":247},[189,590,411],{"class":402},[189,592,593],{"class":195},"=com.mysql.cj.jdbc.Driver\n",[189,595,596],{"class":191,"line":256},[189,597,436],{"emptyLinePlaceholder":435},[189,599,600,602],{"class":191,"line":270},[189,601,449],{"class":402},[189,603,604],{"class":195},"=validate\n",[13,606,607],{},"需要说明的几点：",[34,609,610,617,622,633],{},[37,611,612,613,616],{},"密码不要写死在文件里，用 ",[25,614,615],{},"${DB_PASSWORD:}"," 从环境变量取；",[37,618,619,621],{},[25,620,569],{}," 建议显式写，避免本地时区与驱动推断不一致导致时间字段偏移；",[37,623,624,625,628,629,632],{},"数据库和账号要先手动建好，",[25,626,627],{},"library"," 这个库得自己 ",[25,630,631],{},"CREATE DATABASE","；",[37,634,635,641],{},[50,636,637,638],{},"生产环境用 ",[25,639,640],{},"validate","：只检查实体与表是否一致，不改表结构。",[13,643,644,645,648,649,53],{},"启动时指定：",[25,646,647],{},".\u002Fmvnw spring-boot:run -Dspring-boot.run.profiles=mysql","，或者打包后\n",[25,650,651],{},"java -jar library.jar --spring.profiles.active=mysql",[17,653,655],{"id":654},"_3-把类映射到表","3 把类映射到表",[175,657,659],{"id":658},"_31-最基本的实体","3.1 最基本的实体",[180,661,665],{"className":662,"code":663,"language":664,"meta":185,"style":185},"language-java shiki shiki-themes github-light github-dark","package com.example.library.domain;\n\nimport jakarta.persistence.Column;\nimport jakarta.persistence.Entity;\nimport jakarta.persistence.EnumType;\nimport jakarta.persistence.Enumerated;\nimport jakarta.persistence.GeneratedValue;\nimport jakarta.persistence.GenerationType;\nimport jakarta.persistence.Id;\nimport jakarta.persistence.Table;\nimport jakarta.persistence.UniqueConstraint;\n\n@Entity\n@Table(name = \"books\",\n        uniqueConstraints = @UniqueConstraint(name = \"uk_books_isbn\", columnNames = \"isbn\"))\npublic class Book {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n\n    @Column(nullable = false, length = 200)\n    private String title;\n\n    private String author;\n\n    @Column(length = 13)\n    private String isbn;\n\n    @Column(name = \"published_year\")\n    private Integer publishedYear;\n\n    @Enumerated(EnumType.STRING)\n    @Column(nullable = false, length = 20)\n    private BookStatus status = BookStatus.AVAILABLE;\n\n    \u002F** JPA 需要无参构造器，设为 protected 既满足要求又不暴露给业务代码 *\u002F\n    protected Book() {\n    }\n\n    public Book(String title, String author) {\n        this.title = title;\n        this.author = author;\n    }\n\n    public Long getId() {\n        return id;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public void setAuthor(String author) {\n        this.author = author;\n    }\n\n    public String getIsbn() {\n        return isbn;\n    }\n\n    public void setIsbn(String isbn) {\n        this.isbn = isbn;\n    }\n\n    public Integer getPublishedYear() {\n        return publishedYear;\n    }\n\n    public void setPublishedYear(Integer publishedYear) {\n        this.publishedYear = publishedYear;\n    }\n\n    public BookStatus getStatus() {\n        return status;\n    }\n\n    public void setStatus(BookStatus status) {\n        this.status = status;\n    }\n}\n","java",[25,666,667,675,679,687,694,701,708,715,722,729,736,743,747,755,779,816,832,837,846,864,873,878,909,917,922,930,935,953,961,966,984,992,997,1008,1034,1048,1053,1059,1070,1076,1081,1104,1118,1131,1136,1141,1154,1163,1168,1173,1186,1193,1198,1203,1220,1231,1236,1241,1253,1260,1265,1270,1286,1297,1302,1307,1319,1327,1332,1337,1354,1366,1371,1376,1389,1397,1402,1407,1425,1437,1442,1447,1460,1468,1473,1478,1496,1508,1513],{"__ignoreMap":185},[189,668,669,672],{"class":191,"line":192},[189,670,671],{"class":402},"package",[189,673,674],{"class":195}," com.example.library.domain;\n",[189,676,677],{"class":191,"line":206},[189,678,436],{"emptyLinePlaceholder":435},[189,680,681,684],{"class":191,"line":222},[189,682,683],{"class":402},"import",[189,685,686],{"class":195}," jakarta.persistence.Column;\n",[189,688,689,691],{"class":191,"line":237},[189,690,683],{"class":402},[189,692,693],{"class":195}," jakarta.persistence.Entity;\n",[189,695,696,698],{"class":191,"line":247},[189,697,683],{"class":402},[189,699,700],{"class":195}," jakarta.persistence.EnumType;\n",[189,702,703,705],{"class":191,"line":256},[189,704,683],{"class":402},[189,706,707],{"class":195}," jakarta.persistence.Enumerated;\n",[189,709,710,712],{"class":191,"line":270},[189,711,683],{"class":402},[189,713,714],{"class":195}," jakarta.persistence.GeneratedValue;\n",[189,716,717,719],{"class":191,"line":284},[189,718,683],{"class":402},[189,720,721],{"class":195}," jakarta.persistence.GenerationType;\n",[189,723,724,726],{"class":191,"line":299},[189,725,683],{"class":402},[189,727,728],{"class":195}," jakarta.persistence.Id;\n",[189,730,731,733],{"class":191,"line":308},[189,732,683],{"class":402},[189,734,735],{"class":195}," jakarta.persistence.Table;\n",[189,737,738,740],{"class":191,"line":317},[189,739,683],{"class":402},[189,741,742],{"class":195}," jakarta.persistence.UniqueConstraint;\n",[189,744,745],{"class":191,"line":331},[189,746,436],{"emptyLinePlaceholder":435},[189,748,749,752],{"class":191,"line":345},[189,750,751],{"class":195},"@",[189,753,754],{"class":402},"Entity\n",[189,756,757,759,762,765,769,772,776],{"class":191,"line":358},[189,758,751],{"class":195},[189,760,761],{"class":402},"Table",[189,763,764],{"class":195},"(",[189,766,768],{"class":767},"sj4cs","name",[189,770,771],{"class":402}," =",[189,773,775],{"class":774},"sZZnC"," \"books\"",[189,777,778],{"class":195},",\n",[189,780,782,785,787,790,793,795,797,799,802,805,808,810,813],{"class":191,"line":781},15,[189,783,784],{"class":767},"        uniqueConstraints",[189,786,771],{"class":402},[189,788,789],{"class":195}," @",[189,791,792],{"class":402},"UniqueConstraint",[189,794,764],{"class":195},[189,796,768],{"class":767},[189,798,771],{"class":402},[189,800,801],{"class":774}," \"uk_books_isbn\"",[189,803,804],{"class":195},", ",[189,806,807],{"class":767},"columnNames",[189,809,771],{"class":402},[189,811,812],{"class":774}," \"isbn\"",[189,814,815],{"class":195},"))\n",[189,817,819,822,825,829],{"class":191,"line":818},16,[189,820,821],{"class":402},"public",[189,823,824],{"class":402}," class",[189,826,828],{"class":827},"sScJk"," Book",[189,830,831],{"class":195}," {\n",[189,833,835],{"class":191,"line":834},17,[189,836,436],{"emptyLinePlaceholder":435},[189,838,840,843],{"class":191,"line":839},18,[189,841,842],{"class":195},"    @",[189,844,845],{"class":402},"Id\n",[189,847,849,851,854,856,859,861],{"class":191,"line":848},19,[189,850,842],{"class":195},[189,852,853],{"class":402},"GeneratedValue",[189,855,764],{"class":195},[189,857,858],{"class":767},"strategy",[189,860,771],{"class":402},[189,862,863],{"class":195}," GenerationType.IDENTITY)\n",[189,865,867,870],{"class":191,"line":866},20,[189,868,869],{"class":402},"    private",[189,871,872],{"class":195}," Long id;\n",[189,874,876],{"class":191,"line":875},21,[189,877,436],{"emptyLinePlaceholder":435},[189,879,881,883,886,888,891,893,896,898,901,903,906],{"class":191,"line":880},22,[189,882,842],{"class":195},[189,884,885],{"class":402},"Column",[189,887,764],{"class":195},[189,889,890],{"class":767},"nullable",[189,892,771],{"class":402},[189,894,895],{"class":767}," false",[189,897,804],{"class":195},[189,899,900],{"class":767},"length",[189,902,771],{"class":402},[189,904,905],{"class":767}," 200",[189,907,908],{"class":195},")\n",[189,910,912,914],{"class":191,"line":911},23,[189,913,869],{"class":402},[189,915,916],{"class":195}," String title;\n",[189,918,920],{"class":191,"line":919},24,[189,921,436],{"emptyLinePlaceholder":435},[189,923,925,927],{"class":191,"line":924},25,[189,926,869],{"class":402},[189,928,929],{"class":195}," String author;\n",[189,931,933],{"class":191,"line":932},26,[189,934,436],{"emptyLinePlaceholder":435},[189,936,938,940,942,944,946,948,951],{"class":191,"line":937},27,[189,939,842],{"class":195},[189,941,885],{"class":402},[189,943,764],{"class":195},[189,945,900],{"class":767},[189,947,771],{"class":402},[189,949,950],{"class":767}," 13",[189,952,908],{"class":195},[189,954,956,958],{"class":191,"line":955},28,[189,957,869],{"class":402},[189,959,960],{"class":195}," String isbn;\n",[189,962,964],{"class":191,"line":963},29,[189,965,436],{"emptyLinePlaceholder":435},[189,967,969,971,973,975,977,979,982],{"class":191,"line":968},30,[189,970,842],{"class":195},[189,972,885],{"class":402},[189,974,764],{"class":195},[189,976,768],{"class":767},[189,978,771],{"class":402},[189,980,981],{"class":774}," \"published_year\"",[189,983,908],{"class":195},[189,985,987,989],{"class":191,"line":986},31,[189,988,869],{"class":402},[189,990,991],{"class":195}," Integer publishedYear;\n",[189,993,995],{"class":191,"line":994},32,[189,996,436],{"emptyLinePlaceholder":435},[189,998,1000,1002,1005],{"class":191,"line":999},33,[189,1001,842],{"class":195},[189,1003,1004],{"class":402},"Enumerated",[189,1006,1007],{"class":195},"(EnumType.STRING)\n",[189,1009,1011,1013,1015,1017,1019,1021,1023,1025,1027,1029,1032],{"class":191,"line":1010},34,[189,1012,842],{"class":195},[189,1014,885],{"class":402},[189,1016,764],{"class":195},[189,1018,890],{"class":767},[189,1020,771],{"class":402},[189,1022,895],{"class":767},[189,1024,804],{"class":195},[189,1026,900],{"class":767},[189,1028,771],{"class":402},[189,1030,1031],{"class":767}," 20",[189,1033,908],{"class":195},[189,1035,1037,1039,1042,1045],{"class":191,"line":1036},35,[189,1038,869],{"class":402},[189,1040,1041],{"class":195}," BookStatus status ",[189,1043,1044],{"class":402},"=",[189,1046,1047],{"class":195}," BookStatus.AVAILABLE;\n",[189,1049,1051],{"class":191,"line":1050},36,[189,1052,436],{"emptyLinePlaceholder":435},[189,1054,1056],{"class":191,"line":1055},37,[189,1057,1058],{"class":396},"    \u002F** JPA 需要无参构造器，设为 protected 既满足要求又不暴露给业务代码 *\u002F\n",[189,1060,1062,1065,1067],{"class":191,"line":1061},38,[189,1063,1064],{"class":402},"    protected",[189,1066,828],{"class":827},[189,1068,1069],{"class":195},"() {\n",[189,1071,1073],{"class":191,"line":1072},39,[189,1074,1075],{"class":195},"    }\n",[189,1077,1079],{"class":191,"line":1078},40,[189,1080,436],{"emptyLinePlaceholder":435},[189,1082,1084,1087,1089,1092,1096,1099,1101],{"class":191,"line":1083},41,[189,1085,1086],{"class":402},"    public",[189,1088,828],{"class":827},[189,1090,1091],{"class":195},"(String ",[189,1093,1095],{"class":1094},"s4XuR","title",[189,1097,1098],{"class":195},", String ",[189,1100,60],{"class":1094},[189,1102,1103],{"class":195},") {\n",[189,1105,1107,1110,1113,1115],{"class":191,"line":1106},42,[189,1108,1109],{"class":767},"        this",[189,1111,1112],{"class":195},".title ",[189,1114,1044],{"class":402},[189,1116,1117],{"class":195}," title;\n",[189,1119,1121,1123,1126,1128],{"class":191,"line":1120},43,[189,1122,1109],{"class":767},[189,1124,1125],{"class":195},".author ",[189,1127,1044],{"class":402},[189,1129,1130],{"class":195}," author;\n",[189,1132,1134],{"class":191,"line":1133},44,[189,1135,1075],{"class":195},[189,1137,1139],{"class":191,"line":1138},45,[189,1140,436],{"emptyLinePlaceholder":435},[189,1142,1144,1146,1149,1152],{"class":191,"line":1143},46,[189,1145,1086],{"class":402},[189,1147,1148],{"class":195}," Long ",[189,1150,1151],{"class":827},"getId",[189,1153,1069],{"class":195},[189,1155,1157,1160],{"class":191,"line":1156},47,[189,1158,1159],{"class":402},"        return",[189,1161,1162],{"class":195}," id;\n",[189,1164,1166],{"class":191,"line":1165},48,[189,1167,1075],{"class":195},[189,1169,1171],{"class":191,"line":1170},49,[189,1172,436],{"emptyLinePlaceholder":435},[189,1174,1176,1178,1181,1184],{"class":191,"line":1175},50,[189,1177,1086],{"class":402},[189,1179,1180],{"class":195}," String ",[189,1182,1183],{"class":827},"getTitle",[189,1185,1069],{"class":195},[189,1187,1189,1191],{"class":191,"line":1188},51,[189,1190,1159],{"class":402},[189,1192,1117],{"class":195},[189,1194,1196],{"class":191,"line":1195},52,[189,1197,1075],{"class":195},[189,1199,1201],{"class":191,"line":1200},53,[189,1202,436],{"emptyLinePlaceholder":435},[189,1204,1206,1208,1211,1214,1216,1218],{"class":191,"line":1205},54,[189,1207,1086],{"class":402},[189,1209,1210],{"class":402}," void",[189,1212,1213],{"class":827}," setTitle",[189,1215,1091],{"class":195},[189,1217,1095],{"class":1094},[189,1219,1103],{"class":195},[189,1221,1223,1225,1227,1229],{"class":191,"line":1222},55,[189,1224,1109],{"class":767},[189,1226,1112],{"class":195},[189,1228,1044],{"class":402},[189,1230,1117],{"class":195},[189,1232,1234],{"class":191,"line":1233},56,[189,1235,1075],{"class":195},[189,1237,1239],{"class":191,"line":1238},57,[189,1240,436],{"emptyLinePlaceholder":435},[189,1242,1244,1246,1248,1251],{"class":191,"line":1243},58,[189,1245,1086],{"class":402},[189,1247,1180],{"class":195},[189,1249,1250],{"class":827},"getAuthor",[189,1252,1069],{"class":195},[189,1254,1256,1258],{"class":191,"line":1255},59,[189,1257,1159],{"class":402},[189,1259,1130],{"class":195},[189,1261,1263],{"class":191,"line":1262},60,[189,1264,1075],{"class":195},[189,1266,1268],{"class":191,"line":1267},61,[189,1269,436],{"emptyLinePlaceholder":435},[189,1271,1273,1275,1277,1280,1282,1284],{"class":191,"line":1272},62,[189,1274,1086],{"class":402},[189,1276,1210],{"class":402},[189,1278,1279],{"class":827}," setAuthor",[189,1281,1091],{"class":195},[189,1283,60],{"class":1094},[189,1285,1103],{"class":195},[189,1287,1289,1291,1293,1295],{"class":191,"line":1288},63,[189,1290,1109],{"class":767},[189,1292,1125],{"class":195},[189,1294,1044],{"class":402},[189,1296,1130],{"class":195},[189,1298,1300],{"class":191,"line":1299},64,[189,1301,1075],{"class":195},[189,1303,1305],{"class":191,"line":1304},65,[189,1306,436],{"emptyLinePlaceholder":435},[189,1308,1310,1312,1314,1317],{"class":191,"line":1309},66,[189,1311,1086],{"class":402},[189,1313,1180],{"class":195},[189,1315,1316],{"class":827},"getIsbn",[189,1318,1069],{"class":195},[189,1320,1322,1324],{"class":191,"line":1321},67,[189,1323,1159],{"class":402},[189,1325,1326],{"class":195}," isbn;\n",[189,1328,1330],{"class":191,"line":1329},68,[189,1331,1075],{"class":195},[189,1333,1335],{"class":191,"line":1334},69,[189,1336,436],{"emptyLinePlaceholder":435},[189,1338,1340,1342,1344,1347,1349,1352],{"class":191,"line":1339},70,[189,1341,1086],{"class":402},[189,1343,1210],{"class":402},[189,1345,1346],{"class":827}," setIsbn",[189,1348,1091],{"class":195},[189,1350,1351],{"class":1094},"isbn",[189,1353,1103],{"class":195},[189,1355,1357,1359,1362,1364],{"class":191,"line":1356},71,[189,1358,1109],{"class":767},[189,1360,1361],{"class":195},".isbn ",[189,1363,1044],{"class":402},[189,1365,1326],{"class":195},[189,1367,1369],{"class":191,"line":1368},72,[189,1370,1075],{"class":195},[189,1372,1374],{"class":191,"line":1373},73,[189,1375,436],{"emptyLinePlaceholder":435},[189,1377,1379,1381,1384,1387],{"class":191,"line":1378},74,[189,1380,1086],{"class":402},[189,1382,1383],{"class":195}," Integer ",[189,1385,1386],{"class":827},"getPublishedYear",[189,1388,1069],{"class":195},[189,1390,1392,1394],{"class":191,"line":1391},75,[189,1393,1159],{"class":402},[189,1395,1396],{"class":195}," publishedYear;\n",[189,1398,1400],{"class":191,"line":1399},76,[189,1401,1075],{"class":195},[189,1403,1405],{"class":191,"line":1404},77,[189,1406,436],{"emptyLinePlaceholder":435},[189,1408,1410,1412,1414,1417,1420,1423],{"class":191,"line":1409},78,[189,1411,1086],{"class":402},[189,1413,1210],{"class":402},[189,1415,1416],{"class":827}," setPublishedYear",[189,1418,1419],{"class":195},"(Integer ",[189,1421,1422],{"class":1094},"publishedYear",[189,1424,1103],{"class":195},[189,1426,1428,1430,1433,1435],{"class":191,"line":1427},79,[189,1429,1109],{"class":767},[189,1431,1432],{"class":195},".publishedYear ",[189,1434,1044],{"class":402},[189,1436,1396],{"class":195},[189,1438,1440],{"class":191,"line":1439},80,[189,1441,1075],{"class":195},[189,1443,1445],{"class":191,"line":1444},81,[189,1446,436],{"emptyLinePlaceholder":435},[189,1448,1450,1452,1455,1458],{"class":191,"line":1449},82,[189,1451,1086],{"class":402},[189,1453,1454],{"class":195}," BookStatus ",[189,1456,1457],{"class":827},"getStatus",[189,1459,1069],{"class":195},[189,1461,1463,1465],{"class":191,"line":1462},83,[189,1464,1159],{"class":402},[189,1466,1467],{"class":195}," status;\n",[189,1469,1471],{"class":191,"line":1470},84,[189,1472,1075],{"class":195},[189,1474,1476],{"class":191,"line":1475},85,[189,1477,436],{"emptyLinePlaceholder":435},[189,1479,1481,1483,1485,1488,1491,1494],{"class":191,"line":1480},86,[189,1482,1086],{"class":402},[189,1484,1210],{"class":402},[189,1486,1487],{"class":827}," setStatus",[189,1489,1490],{"class":195},"(BookStatus ",[189,1492,1493],{"class":1094},"status",[189,1495,1103],{"class":195},[189,1497,1499,1501,1504,1506],{"class":191,"line":1498},87,[189,1500,1109],{"class":767},[189,1502,1503],{"class":195},".status ",[189,1505,1044],{"class":402},[189,1507,1467],{"class":195},[189,1509,1511],{"class":191,"line":1510},88,[189,1512,1075],{"class":195},[189,1514,1516],{"class":191,"line":1515},89,[189,1517,1518],{"class":195},"}\n",[13,1520,1521,1522,1525,1526,1529],{},"它引用到的 ",[25,1523,1524],{},"BookStatus"," 也得有定义（枚举带一个用于页面显示的 ",[25,1527,1528],{},"label","）：",[180,1531,1533],{"className":662,"code":1532,"language":664,"meta":185,"style":185},"package com.example.library.domain;\n\npublic enum BookStatus {\n\n    AVAILABLE(\"可借\"),\n    BORROWED(\"已借出\"),\n    RESERVED(\"已预约\"),\n    LOST(\"遗失\");\n\n    private final String label;\n\n    BookStatus(String label) {\n        this.label = label;\n    }\n\n    public String getLabel() {\n        return label;\n    }\n}\n",[25,1534,1535,1541,1545,1557,1561,1574,1586,1598,1611,1615,1625,1629,1640,1652,1656,1660,1671,1677,1681],{"__ignoreMap":185},[189,1536,1537,1539],{"class":191,"line":192},[189,1538,671],{"class":402},[189,1540,674],{"class":195},[189,1542,1543],{"class":191,"line":206},[189,1544,436],{"emptyLinePlaceholder":435},[189,1546,1547,1549,1552,1555],{"class":191,"line":222},[189,1548,821],{"class":402},[189,1550,1551],{"class":402}," enum",[189,1553,1554],{"class":827}," BookStatus",[189,1556,831],{"class":195},[189,1558,1559],{"class":191,"line":237},[189,1560,436],{"emptyLinePlaceholder":435},[189,1562,1563,1566,1568,1571],{"class":191,"line":247},[189,1564,1565],{"class":767},"    AVAILABLE",[189,1567,764],{"class":195},[189,1569,1570],{"class":774},"\"可借\"",[189,1572,1573],{"class":195},"),\n",[189,1575,1576,1579,1581,1584],{"class":191,"line":256},[189,1577,1578],{"class":767},"    BORROWED",[189,1580,764],{"class":195},[189,1582,1583],{"class":774},"\"已借出\"",[189,1585,1573],{"class":195},[189,1587,1588,1591,1593,1596],{"class":191,"line":270},[189,1589,1590],{"class":767},"    RESERVED",[189,1592,764],{"class":195},[189,1594,1595],{"class":774},"\"已预约\"",[189,1597,1573],{"class":195},[189,1599,1600,1603,1605,1608],{"class":191,"line":284},[189,1601,1602],{"class":767},"    LOST",[189,1604,764],{"class":195},[189,1606,1607],{"class":774},"\"遗失\"",[189,1609,1610],{"class":195},");\n",[189,1612,1613],{"class":191,"line":299},[189,1614,436],{"emptyLinePlaceholder":435},[189,1616,1617,1619,1622],{"class":191,"line":308},[189,1618,869],{"class":402},[189,1620,1621],{"class":402}," final",[189,1623,1624],{"class":195}," String label;\n",[189,1626,1627],{"class":191,"line":317},[189,1628,436],{"emptyLinePlaceholder":435},[189,1630,1631,1634,1636,1638],{"class":191,"line":331},[189,1632,1633],{"class":827},"    BookStatus",[189,1635,1091],{"class":195},[189,1637,1528],{"class":1094},[189,1639,1103],{"class":195},[189,1641,1642,1644,1647,1649],{"class":191,"line":345},[189,1643,1109],{"class":767},[189,1645,1646],{"class":195},".label ",[189,1648,1044],{"class":402},[189,1650,1651],{"class":195}," label;\n",[189,1653,1654],{"class":191,"line":358},[189,1655,1075],{"class":195},[189,1657,1658],{"class":191,"line":781},[189,1659,436],{"emptyLinePlaceholder":435},[189,1661,1662,1664,1666,1669],{"class":191,"line":818},[189,1663,1086],{"class":402},[189,1665,1180],{"class":195},[189,1667,1668],{"class":827},"getLabel",[189,1670,1069],{"class":195},[189,1672,1673,1675],{"class":191,"line":834},[189,1674,1159],{"class":402},[189,1676,1651],{"class":195},[189,1678,1679],{"class":191,"line":839},[189,1680,1075],{"class":195},[189,1682,1683],{"class":191,"line":848},[189,1684,1518],{"class":195},[13,1686,1687],{},"几个关键点：",[34,1689,1690,1708,1717,1726,1740,1751],{},[37,1691,1692,1697,1698,1700,1701,1704,1705,632],{},[50,1693,1694,1696],{},[25,1695,115],{}," 的类名默认就是表名","（",[25,1699,56],{}," → ",[25,1702,1703],{},"book","）。想换名字用 ",[25,1706,1707],{},"@Table(name = \"books\")",[37,1709,1710,1716],{},[50,1711,1712,1713],{},"必须有 ",[25,1714,1715],{},"@Id","，否则启动就报错；",[37,1718,1719,1725],{},[50,1720,1721,1724],{},[25,1722,1723],{},"id"," 通常不提供 setter","：主键由数据库生成，让外部随便改主键是麻烦的开始；",[37,1727,1728,1729,1700,1731,1733,1734,1736,1737,632],{},"字段名默认就是列名，",[25,1730,60],{},[25,1732,60],{},"。驼峰字段（如 ",[25,1735,1422],{},"）默认映射成\n",[25,1738,1739],{},"published_year",[37,1741,1742,1743,1746,1747,1750],{},"包名必须是 ",[25,1744,1745],{},"jakarta.persistence.*","——",[25,1748,1749],{},"javax.persistence.*"," 从 Spring Boot 3 起就已经不存在；",[37,1752,1753,1758,1759,1761],{},[50,1754,1755,1756],{},"实体不要用 ",[25,1757,159],{},"（01 篇留的坑）：JPA 需要无参构造器和可变字段，",[25,1760,159],{}," 两个都不满足。",[175,1763,1765],{"id":1764},"_32-主键生成策略怎么选","3.2 主键生成策略怎么选",[74,1767,1768,1781],{},[77,1769,1770],{},[80,1771,1772,1775,1778],{},[83,1773,1774],{},"策略",[83,1776,1777],{},"行为",[83,1779,1780],{},"用在哪",[90,1782,1783,1800,1813,1826,1839],{},[80,1784,1785,1790,1797],{},[95,1786,1787],{},[25,1788,1789],{},"GenerationType.IDENTITY",[95,1791,1792,1793,1796],{},"交给数据库自增列（MySQL 的 ",[25,1794,1795],{},"AUTO_INCREMENT","）",[95,1798,1799],{},"MySQL 项目里最常用，简单直观",[80,1801,1802,1807,1810],{},[95,1803,1804],{},[25,1805,1806],{},"GenerationType.SEQUENCE",[95,1808,1809],{},"用数据库序列（Oracle、PostgreSQL），可批量预取",[95,1811,1812],{},"支持序列的数据库，批量插入性能更好",[80,1814,1815,1820,1823],{},[95,1816,1817],{},[25,1818,1819],{},"GenerationType.TABLE",[95,1821,1822],{},"用一张额外的表记录下一个 id",[95,1824,1825],{},"兼容性最好但性能最差，现在基本不用",[80,1827,1828,1833,1836],{},[95,1829,1830],{},[25,1831,1832],{},"GenerationType.AUTO",[95,1834,1835],{},"由提供者决定（Hibernate 6 默认走序列）",[95,1837,1838],{},"想省事，但换数据库时行为会变",[80,1840,1841,1846,1849],{},[95,1842,1843],{},[25,1844,1845],{},"@UuidGenerator",[95,1847,1848],{},"Hibernate 提供的 UUID 主键",[95,1850,1851],{},"需要分布式友好、不暴露连续 id 的场景",[13,1853,1854,1855,1861],{},"一个常被忽略的点：",[50,1856,1857,1860],{},[25,1858,1859],{},"IDENTITY"," 会让 Hibernate 不能用批量插入","，因为必须逐条拿回自增值。\n批量导入场景要留意这一点。",[175,1863,1865],{"id":1864},"_33-表和列的自定义","3.3 表和列的自定义",[13,1867,1868],{},"上面那个实体里已经用到了三种自定义，逐条说明：",[74,1870,1871,1881],{},[77,1872,1873],{},[80,1874,1875,1878],{},[83,1876,1877],{},"写法",[83,1879,1880],{},"作用",[90,1882,1883,1892,1902,1916,1926],{},[80,1884,1885,1889],{},[95,1886,1887],{},[25,1888,1707],{},[95,1890,1891],{},"显式指定表名，不再依赖类名推断",[80,1893,1894,1899],{},[95,1895,1896],{},[25,1897,1898],{},"@Column(name = \"published_year\")",[95,1900,1901],{},"显式指定列名；驼峰字段最好都写，避免依赖命名策略",[80,1903,1904,1909],{},[95,1905,1906],{},[25,1907,1908],{},"@Table(uniqueConstraints = @UniqueConstraint(...))",[95,1910,1911,1912,1915],{},"声明唯一约束，",[25,1913,1914],{},"ddl-auto"," 建表时会带上",[80,1917,1918,1923],{},[95,1919,1920],{},[25,1921,1922],{},"@Enumerated(EnumType.STRING)",[95,1924,1925],{},"枚举存名字而不是序号",[80,1927,1928,1933],{},[95,1929,1930],{},[25,1931,1932],{},"@Column(length = 13, nullable = false)",[95,1934,1935],{},"长度与是否允许为空",[13,1937,1938],{},"要特别提醒的两条：",[34,1940,1941,1953],{},[37,1942,1943,1948,1949,1952],{},[50,1944,1945,1946],{},"枚举字段务必写 ",[25,1947,1922],{},"。默认的 ",[25,1950,1951],{},"ORDINAL"," 存序号，枚举顺序一变，\n历史数据的意思就全错了；",[37,1954,1955,1956,1959,1960,1697,1963,1966,1967,53],{},"时间字段用 ",[25,1957,1958],{},"LocalDate"," \u002F ",[25,1961,1962],{},"LocalDateTime",[25,1964,1965],{},"java.time","），不要用已经过时的 ",[25,1968,1969],{},"java.util.Date",[13,1971,1972,1973,1976,1977,1980],{},"想把多个字段合成一个可复用的值对象（比如「地址」），用 ",[25,1974,1975],{},"@Embeddable"," + ",[25,1978,1979],{},"@Embedded","，\n那是另一个话题，这里不展开。",[17,1982,1984],{"id":1983},"_4-repository一个空接口就够了","4 Repository：一个空接口就够了",[175,1986,1988],{"id":1987},"_41-三层接口","4.1 三层接口",[13,1990,1991],{},"Spring Data 提供了三个常用层级的接口，层层继承：",[74,1993,1994,2004],{},[77,1995,1996],{},[80,1997,1998,2001],{},[83,1999,2000],{},"接口",[83,2002,2003],{},"提供的能力",[90,2005,2006,2031,2047],{},[80,2007,2008,2013],{},[95,2009,2010],{},[25,2011,2012],{},"CrudRepository\u003CT, ID>",[95,2014,2015,116,2018,116,2021,116,2024,116,2027,2030],{},[25,2016,2017],{},"save",[25,2019,2020],{},"findById",[25,2022,2023],{},"findAll",[25,2025,2026],{},"count",[25,2028,2029],{},"deleteById"," 等",[80,2032,2033,2038],{},[95,2034,2035],{},[25,2036,2037],{},"PagingAndSortingRepository\u003CT, ID>",[95,2039,2040,2041,116,2044],{},"在 CRUD 之上加 ",[25,2042,2043],{},"findAll(Pageable)",[25,2045,2046],{},"findAll(Sort)",[80,2048,2049,2054],{},[95,2050,2051],{},[25,2052,2053],{},"JpaRepository\u003CT, ID>",[95,2055,2056,2057,2060],{},"再加刷新、批量删除、",[25,2058,2059],{},"saveAndFlush"," 等 JPA 特有的操作",[13,2062,2063,2069],{},[50,2064,2065,2066],{},"日常用 ",[25,2067,2068],{},"JpaRepository","：它能力最全，而且下面那些方法你都用得到。",[175,2071,2073],{"id":2072},"_42-定义与使用","4.2 定义与使用",[180,2075,2077],{"className":662,"code":2076,"language":664,"meta":185,"style":185},"package com.example.library.repository;\n\nimport com.example.library.domain.Book;\nimport org.springframework.data.jpa.repository.JpaRepository;\n\npublic interface BookRepository extends JpaRepository\u003CBook, Long> {\n}\n",[25,2078,2079,2086,2090,2097,2104,2108,2136],{"__ignoreMap":185},[189,2080,2081,2083],{"class":191,"line":192},[189,2082,671],{"class":402},[189,2084,2085],{"class":195}," com.example.library.repository;\n",[189,2087,2088],{"class":191,"line":206},[189,2089,436],{"emptyLinePlaceholder":435},[189,2091,2092,2094],{"class":191,"line":222},[189,2093,683],{"class":402},[189,2095,2096],{"class":195}," com.example.library.domain.Book;\n",[189,2098,2099,2101],{"class":191,"line":237},[189,2100,683],{"class":402},[189,2102,2103],{"class":195}," org.springframework.data.jpa.repository.JpaRepository;\n",[189,2105,2106],{"class":191,"line":247},[189,2107,436],{"emptyLinePlaceholder":435},[189,2109,2110,2112,2115,2118,2121,2124,2126,2128,2130,2133],{"class":191,"line":256},[189,2111,821],{"class":402},[189,2113,2114],{"class":402}," interface",[189,2116,2117],{"class":827}," BookRepository",[189,2119,2120],{"class":402}," extends",[189,2122,2123],{"class":827}," JpaRepository",[189,2125,196],{"class":195},[189,2127,56],{"class":402},[189,2129,804],{"class":195},[189,2131,2132],{"class":402},"Long",[189,2134,2135],{"class":195},"> {\n",[189,2137,2138],{"class":191,"line":270},[189,2139,1518],{"class":195},[13,2141,2142,2145,2146,2148],{},[50,2143,2144],{},"没有实现类","。写成接口，继承 ",[25,2147,2068],{},"，指定实体类型和主键类型，Spring 在启动时为它\n生成代理实现并注册成 Bean——这就是「零样板代码」的具体含义。",[13,2150,2151,2152,2155,2156,116,2159,116,2161,2163],{},"注入方式和普通 Bean 一样。",[25,2153,2154],{},"BookService"," 接口在 01 篇已经定义（",[25,2157,2158],{},"add",[25,2160,2020],{},[25,2162,2023],{},"），\n把实现换成 JPA 版本，整个类是完整的：",[180,2165,2167],{"className":662,"code":2166,"language":664,"meta":185,"style":185},"package com.example.library.service;\n\nimport com.example.library.domain.Book;\nimport com.example.library.repository.BookRepository;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.List;\nimport java.util.NoSuchElementException;\n\n@Service\n@Transactional(readOnly = true)\npublic class BookServiceImpl implements BookService {\n\n    private final BookRepository bookRepository;\n\n    public BookServiceImpl(BookRepository bookRepository) {\n        this.bookRepository = bookRepository;\n    }\n\n    @Override\n    @Transactional\n    public Book add(String title, String author) {\n        return bookRepository.save(new Book(title, author));\n    }\n\n    @Override\n    public Book findById(Long id) {\n        return bookRepository.findById(id)\n                .orElseThrow(() -> new NoSuchElementException(\"找不到图书：\" + id));\n    }\n\n    @Override\n    public List\u003CBook> findAll() {\n        return bookRepository.findAll();\n    }\n}\n",[25,2168,2169,2176,2180,2186,2193,2200,2207,2211,2218,2225,2229,2236,2255,2272,2276,2285,2289,2303,2315,2319,2323,2330,2337,2356,2375,2379,2383,2389,2404,2415,2446,2450,2454,2460,2476,2487,2491],{"__ignoreMap":185},[189,2170,2171,2173],{"class":191,"line":192},[189,2172,671],{"class":402},[189,2174,2175],{"class":195}," com.example.library.service;\n",[189,2177,2178],{"class":191,"line":206},[189,2179,436],{"emptyLinePlaceholder":435},[189,2181,2182,2184],{"class":191,"line":222},[189,2183,683],{"class":402},[189,2185,2096],{"class":195},[189,2187,2188,2190],{"class":191,"line":237},[189,2189,683],{"class":402},[189,2191,2192],{"class":195}," com.example.library.repository.BookRepository;\n",[189,2194,2195,2197],{"class":191,"line":247},[189,2196,683],{"class":402},[189,2198,2199],{"class":195}," org.springframework.stereotype.Service;\n",[189,2201,2202,2204],{"class":191,"line":256},[189,2203,683],{"class":402},[189,2205,2206],{"class":195}," org.springframework.transaction.annotation.Transactional;\n",[189,2208,2209],{"class":191,"line":270},[189,2210,436],{"emptyLinePlaceholder":435},[189,2212,2213,2215],{"class":191,"line":284},[189,2214,683],{"class":402},[189,2216,2217],{"class":195}," java.util.List;\n",[189,2219,2220,2222],{"class":191,"line":299},[189,2221,683],{"class":402},[189,2223,2224],{"class":195}," java.util.NoSuchElementException;\n",[189,2226,2227],{"class":191,"line":308},[189,2228,436],{"emptyLinePlaceholder":435},[189,2230,2231,2233],{"class":191,"line":317},[189,2232,751],{"class":195},[189,2234,2235],{"class":402},"Service\n",[189,2237,2238,2240,2243,2245,2248,2250,2253],{"class":191,"line":331},[189,2239,751],{"class":195},[189,2241,2242],{"class":402},"Transactional",[189,2244,764],{"class":195},[189,2246,2247],{"class":767},"readOnly",[189,2249,771],{"class":402},[189,2251,2252],{"class":767}," true",[189,2254,908],{"class":195},[189,2256,2257,2259,2261,2264,2267,2270],{"class":191,"line":345},[189,2258,821],{"class":402},[189,2260,824],{"class":402},[189,2262,2263],{"class":827}," BookServiceImpl",[189,2265,2266],{"class":402}," implements",[189,2268,2269],{"class":827}," BookService",[189,2271,831],{"class":195},[189,2273,2274],{"class":191,"line":358},[189,2275,436],{"emptyLinePlaceholder":435},[189,2277,2278,2280,2282],{"class":191,"line":781},[189,2279,869],{"class":402},[189,2281,1621],{"class":402},[189,2283,2284],{"class":195}," BookRepository bookRepository;\n",[189,2286,2287],{"class":191,"line":818},[189,2288,436],{"emptyLinePlaceholder":435},[189,2290,2291,2293,2295,2298,2301],{"class":191,"line":834},[189,2292,1086],{"class":402},[189,2294,2263],{"class":827},[189,2296,2297],{"class":195},"(BookRepository ",[189,2299,2300],{"class":1094},"bookRepository",[189,2302,1103],{"class":195},[189,2304,2305,2307,2310,2312],{"class":191,"line":839},[189,2306,1109],{"class":767},[189,2308,2309],{"class":195},".bookRepository ",[189,2311,1044],{"class":402},[189,2313,2314],{"class":195}," bookRepository;\n",[189,2316,2317],{"class":191,"line":848},[189,2318,1075],{"class":195},[189,2320,2321],{"class":191,"line":866},[189,2322,436],{"emptyLinePlaceholder":435},[189,2324,2325,2327],{"class":191,"line":875},[189,2326,842],{"class":195},[189,2328,2329],{"class":402},"Override\n",[189,2331,2332,2334],{"class":191,"line":880},[189,2333,842],{"class":195},[189,2335,2336],{"class":402},"Transactional\n",[189,2338,2339,2341,2344,2346,2348,2350,2352,2354],{"class":191,"line":911},[189,2340,1086],{"class":402},[189,2342,2343],{"class":195}," Book ",[189,2345,2158],{"class":827},[189,2347,1091],{"class":195},[189,2349,1095],{"class":1094},[189,2351,1098],{"class":195},[189,2353,60],{"class":1094},[189,2355,1103],{"class":195},[189,2357,2358,2360,2363,2365,2367,2370,2372],{"class":191,"line":919},[189,2359,1159],{"class":402},[189,2361,2362],{"class":195}," bookRepository.",[189,2364,2017],{"class":827},[189,2366,764],{"class":195},[189,2368,2369],{"class":402},"new",[189,2371,828],{"class":827},[189,2373,2374],{"class":195},"(title, author));\n",[189,2376,2377],{"class":191,"line":924},[189,2378,1075],{"class":195},[189,2380,2381],{"class":191,"line":932},[189,2382,436],{"emptyLinePlaceholder":435},[189,2384,2385,2387],{"class":191,"line":937},[189,2386,842],{"class":195},[189,2388,2329],{"class":402},[189,2390,2391,2393,2395,2397,2400,2402],{"class":191,"line":955},[189,2392,1086],{"class":402},[189,2394,2343],{"class":195},[189,2396,2020],{"class":827},[189,2398,2399],{"class":195},"(Long ",[189,2401,1723],{"class":1094},[189,2403,1103],{"class":195},[189,2405,2406,2408,2410,2412],{"class":191,"line":963},[189,2407,1159],{"class":402},[189,2409,2362],{"class":195},[189,2411,2020],{"class":827},[189,2413,2414],{"class":195},"(id)\n",[189,2416,2417,2420,2423,2426,2429,2432,2435,2437,2440,2443],{"class":191,"line":968},[189,2418,2419],{"class":195},"                .",[189,2421,2422],{"class":827},"orElseThrow",[189,2424,2425],{"class":195},"(() ",[189,2427,2428],{"class":402},"->",[189,2430,2431],{"class":402}," new",[189,2433,2434],{"class":827}," NoSuchElementException",[189,2436,764],{"class":195},[189,2438,2439],{"class":774},"\"找不到图书：\"",[189,2441,2442],{"class":402}," +",[189,2444,2445],{"class":195}," id));\n",[189,2447,2448],{"class":191,"line":986},[189,2449,1075],{"class":195},[189,2451,2452],{"class":191,"line":994},[189,2453,436],{"emptyLinePlaceholder":435},[189,2455,2456,2458],{"class":191,"line":999},[189,2457,842],{"class":195},[189,2459,2329],{"class":402},[189,2461,2462,2464,2467,2469,2472,2474],{"class":191,"line":1010},[189,2463,1086],{"class":402},[189,2465,2466],{"class":195}," List\u003C",[189,2468,56],{"class":402},[189,2470,2471],{"class":195},"> ",[189,2473,2023],{"class":827},[189,2475,1069],{"class":195},[189,2477,2478,2480,2482,2484],{"class":191,"line":1036},[189,2479,1159],{"class":402},[189,2481,2362],{"class":195},[189,2483,2023],{"class":827},[189,2485,2486],{"class":195},"();\n",[189,2488,2489],{"class":191,"line":1050},[189,2490,1075],{"class":195},[189,2492,2493],{"class":191,"line":1055},[189,2494,1518],{"class":195},[13,2496,2497,2498,2501],{},"对比 01 篇的内存实现，业务代码只改了「用哪个 Repository」——",[50,2499,2500],{},"接口没动，Controller 一个字\n没改","，这就是把依赖交给容器管之后拿到的收益。后面各篇会往这个接口里继续加方法\n（03 篇加分页查询、06 篇加表单新增）。",[13,2503,2504,2506,2507,2510,2511,2514,2515,2518],{},[25,2505,2017],{}," 的语义要注意：",[50,2508,2509],{},"主键为空是插入，主键有值（且已存在）是更新","。它是\n",[25,2512,2513],{},"persist"," 与 ",[25,2516,2517],{},"merge"," 的合并入口。",[17,2520,2522],{"id":2521},"_5-启动时初始化数据commandlinerunner","5 启动时初始化数据：CommandLineRunner",[180,2524,2526],{"className":662,"code":2525,"language":664,"meta":185,"style":185},"package com.example.library;\n\nimport com.example.library.domain.Book;\nimport com.example.library.repository.BookRepository;\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport java.util.List;\n\n@Configuration\npublic class DataInitializer {\n\n    @Bean\n    CommandLineRunner loadSampleData(BookRepository repository) {\n        return args -> {\n            if (repository.count() > 0) {\n                return;\n            }\n            repository.saveAll(List.of(\n                    new Book(\"重构\", \"Martin Fowler\"),\n                    new Book(\"代码大全\", \"Steve McConnell\"),\n                    new Book(\"设计模式\", \"Erich Gamma\")\n            ));\n        };\n    }\n}\n",[25,2527,2528,2535,2539,2545,2551,2558,2565,2572,2576,2582,2586,2593,2604,2608,2615,2630,2641,2662,2670,2675,2692,2711,2729,2747,2752,2757,2761],{"__ignoreMap":185},[189,2529,2530,2532],{"class":191,"line":192},[189,2531,671],{"class":402},[189,2533,2534],{"class":195}," com.example.library;\n",[189,2536,2537],{"class":191,"line":206},[189,2538,436],{"emptyLinePlaceholder":435},[189,2540,2541,2543],{"class":191,"line":222},[189,2542,683],{"class":402},[189,2544,2096],{"class":195},[189,2546,2547,2549],{"class":191,"line":237},[189,2548,683],{"class":402},[189,2550,2192],{"class":195},[189,2552,2553,2555],{"class":191,"line":247},[189,2554,683],{"class":402},[189,2556,2557],{"class":195}," org.springframework.boot.CommandLineRunner;\n",[189,2559,2560,2562],{"class":191,"line":256},[189,2561,683],{"class":402},[189,2563,2564],{"class":195}," org.springframework.context.annotation.Bean;\n",[189,2566,2567,2569],{"class":191,"line":270},[189,2568,683],{"class":402},[189,2570,2571],{"class":195}," org.springframework.context.annotation.Configuration;\n",[189,2573,2574],{"class":191,"line":284},[189,2575,436],{"emptyLinePlaceholder":435},[189,2577,2578,2580],{"class":191,"line":299},[189,2579,683],{"class":402},[189,2581,2217],{"class":195},[189,2583,2584],{"class":191,"line":308},[189,2585,436],{"emptyLinePlaceholder":435},[189,2587,2588,2590],{"class":191,"line":317},[189,2589,751],{"class":195},[189,2591,2592],{"class":402},"Configuration\n",[189,2594,2595,2597,2599,2602],{"class":191,"line":331},[189,2596,821],{"class":402},[189,2598,824],{"class":402},[189,2600,2601],{"class":827}," DataInitializer",[189,2603,831],{"class":195},[189,2605,2606],{"class":191,"line":345},[189,2607,436],{"emptyLinePlaceholder":435},[189,2609,2610,2612],{"class":191,"line":358},[189,2611,842],{"class":195},[189,2613,2614],{"class":402},"Bean\n",[189,2616,2617,2620,2623,2625,2628],{"class":191,"line":781},[189,2618,2619],{"class":195},"    CommandLineRunner ",[189,2621,2622],{"class":827},"loadSampleData",[189,2624,2297],{"class":195},[189,2626,2627],{"class":1094},"repository",[189,2629,1103],{"class":195},[189,2631,2632,2634,2637,2639],{"class":191,"line":818},[189,2633,1159],{"class":402},[189,2635,2636],{"class":195}," args ",[189,2638,2428],{"class":402},[189,2640,831],{"class":195},[189,2642,2643,2646,2649,2651,2654,2657,2660],{"class":191,"line":834},[189,2644,2645],{"class":402},"            if",[189,2647,2648],{"class":195}," (repository.",[189,2650,2026],{"class":827},[189,2652,2653],{"class":195},"() ",[189,2655,2656],{"class":402},">",[189,2658,2659],{"class":767}," 0",[189,2661,1103],{"class":195},[189,2663,2664,2667],{"class":191,"line":839},[189,2665,2666],{"class":402},"                return",[189,2668,2669],{"class":195},";\n",[189,2671,2672],{"class":191,"line":848},[189,2673,2674],{"class":195},"            }\n",[189,2676,2677,2680,2683,2686,2689],{"class":191,"line":866},[189,2678,2679],{"class":195},"            repository.",[189,2681,2682],{"class":827},"saveAll",[189,2684,2685],{"class":195},"(List.",[189,2687,2688],{"class":827},"of",[189,2690,2691],{"class":195},"(\n",[189,2693,2694,2697,2699,2701,2704,2706,2709],{"class":191,"line":875},[189,2695,2696],{"class":402},"                    new",[189,2698,828],{"class":827},[189,2700,764],{"class":195},[189,2702,2703],{"class":774},"\"重构\"",[189,2705,804],{"class":195},[189,2707,2708],{"class":774},"\"Martin Fowler\"",[189,2710,1573],{"class":195},[189,2712,2713,2715,2717,2719,2722,2724,2727],{"class":191,"line":880},[189,2714,2696],{"class":402},[189,2716,828],{"class":827},[189,2718,764],{"class":195},[189,2720,2721],{"class":774},"\"代码大全\"",[189,2723,804],{"class":195},[189,2725,2726],{"class":774},"\"Steve McConnell\"",[189,2728,1573],{"class":195},[189,2730,2731,2733,2735,2737,2740,2742,2745],{"class":191,"line":911},[189,2732,2696],{"class":402},[189,2734,828],{"class":827},[189,2736,764],{"class":195},[189,2738,2739],{"class":774},"\"设计模式\"",[189,2741,804],{"class":195},[189,2743,2744],{"class":774},"\"Erich Gamma\"",[189,2746,908],{"class":195},[189,2748,2749],{"class":191,"line":919},[189,2750,2751],{"class":195},"            ));\n",[189,2753,2754],{"class":191,"line":924},[189,2755,2756],{"class":195},"        };\n",[189,2758,2759],{"class":191,"line":932},[189,2760,1075],{"class":195},[189,2762,2763],{"class":191,"line":937},[189,2764,1518],{"class":195},[13,2766,2767,2770],{},[25,2768,2769],{},"CommandLineRunner"," 是 Boot 提供的钩子，应用启动完成后执行一次，常用来灌测试数据。参数由容器\n注入——这里又见到依赖注入了。",[13,2772,2773,2774,2777,2778,1959,2781,2784,2785,2787],{},"生产数据初始化，官方更推荐 ",[25,2775,2776],{},"spring.sql.init.mode=always"," 配合 ",[25,2779,2780],{},"schema.sql",[25,2782,2783],{},"data.sql","，\n但要注意它和 ",[25,2786,1914],{}," 的执行顺序，两者混用容易把表建两次。",[17,2789,2791],{"id":2790},"_6-ddl-auto-该写哪个值","6 ddl-auto 该写哪个值",[74,2793,2794,2806],{},[77,2795,2796],{},[80,2797,2798,2801,2803],{},[83,2799,2800],{},"值",[83,2802,1777],{},[83,2804,2805],{},"用在",[90,2807,2808,2821,2835,2848,2861],{},[80,2809,2810,2815,2818],{},[95,2811,2812],{},[25,2813,2814],{},"none",[95,2816,2817],{},"什么都不做（H2 这类内嵌库默认值之一）",[95,2819,2820],{},"表由脚本或 DBA 管理",[80,2822,2823,2827,2830],{},[95,2824,2825],{},[25,2826,640],{},[95,2828,2829],{},"启动时校验实体与表是否一致，不一致就报错",[95,2831,2832],{},[50,2833,2834],{},"生产环境推荐",[80,2836,2837,2842,2845],{},[95,2838,2839],{},[25,2840,2841],{},"update",[95,2843,2844],{},"缺的列\u002F表补上，但从不删除多余结构",[95,2846,2847],{},"本地开发方便，改动不可控",[80,2849,2850,2855,2858],{},[95,2851,2852],{},[25,2853,2854],{},"create",[95,2856,2857],{},"每次启动删表重建，关闭时保留",[95,2859,2860],{},"临时调试",[80,2862,2863,2868,2871],{},[95,2864,2865],{},[25,2866,2867],{},"create-drop",[95,2869,2870],{},"每次启动建表，关闭时删表",[95,2872,2873],{},"单元测试、内存库开发",[13,2875,2876],{},"几个提醒：",[34,2878,2879,2887,2895],{},[37,2880,2881,2886],{},[50,2882,2883,2885],{},[25,2884,2841],{}," 不是迁移工具","。改字段名、删列它都不管，时间长了数据库结构和实体就对不上；",[37,2888,2889,2890,2892,2893,632],{},"真实项目里的结构变更要靠迁移工具（Flyway、Liquibase）管理，",[25,2891,1914],{}," 在非内嵌数据库上\n设成 ",[25,2894,640],{},[37,2896,2897,2898,2900],{},"用 H2 跑测试时配 ",[25,2899,2867],{},"，每次都是干净库，测试之间不会互相污染。",[17,2902,2904],{"id":2903},"_7-h2-控制台不用装客户端就能看表","7 H2 控制台：不用装客户端就能看表",[13,2906,2907,2910,2911,2914,2915,2917,2918,2921,2922,2925,2926,53],{},[25,2908,2909],{},"spring.h2.console.enabled=true"," 之后，加上第 2.2 节说的 ",[25,2912,2913],{},"spring-boot-h2console"," 依赖，\n启动应用访问 ",[25,2916,535],{}," 就能用网页版客户端连进内存库，看表、跑 SQL 都行。连接信息就填\n",[25,2919,2920],{},"application.properties"," 里那段 URL（",[25,2923,2924],{},"jdbc:h2:mem:librarydb","）和用户名 ",[25,2927,2928],{},"sa",[13,2930,2931,2932,2935],{},"一个细节：控制台的页面是自带框架画的，如果项目里配了安全框架，记得把 ",[25,2933,2934],{},"\u002Fh2-console\u002F**"," 放行，\n否则会一直跳登录页。",[17,2937,2939],{"id":2938},"_8-容易踩的坑","8 容易踩的坑",[74,2941,2942,2955],{},[77,2943,2944],{},[80,2945,2946,2949,2952],{},[83,2947,2948],{},"现象",[83,2950,2951],{},"原因",[83,2953,2954],{},"处理",[90,2956,2957,2973,2991,3006,3023,3041,3057,3074,3093,3112],{},[80,2958,2959,2965,2970],{},[95,2960,2961,2962],{},"启动报 ",[25,2963,2964],{},"Not a managed type",[95,2966,2967,2968],{},"实体类不在主类所在包的扫描范围内，或忘了 ",[25,2969,115],{},[95,2971,2972],{},"挪进扫描范围，检查注解",[80,2974,2975,2980,2986],{},[95,2976,2977,2978],{},"启动报找不到 ",[25,2979,1749],{},[95,2981,2982,2983],{},"Boot 3 已改用 ",[25,2984,2985],{},"jakarta.*",[95,2987,2988,2989],{},"全部换成 ",[25,2990,1745],{},[80,2992,2993,2996,2999],{},[95,2994,2995],{},"报错提示实体没有默认构造器",[95,2997,2998],{},"只写了有参构造器",[95,3000,3001,3002,3005],{},"补一个 ",[25,3003,3004],{},"protected"," 无参构造器（JPA 需要它）",[80,3007,3008,3014,3017],{},[95,3009,3010,3011,3013],{},"实体用了 ",[25,3012,159],{}," 导致启动失败",[95,3015,3016],{},"JPA 不支持 final 字段与不可变实体",[95,3018,3019,3020,3022],{},"实体改用普通 class，",[25,3021,159],{}," 留给 DTO 和领域值对象",[80,3024,3025,3028,3031],{},[95,3026,3027],{},"表名\u002F列名和预期不一致",[95,3029,3030],{},"默认命名策略是「驼峰转下划线、类名小写」",[95,3032,3033,3034,116,3037,3040],{},"用 ",[25,3035,3036],{},"@Table(name = ...)",[25,3038,3039],{},"@Column(name = ...)"," 显式指定",[80,3042,3043,3046,3052],{},[95,3044,3045],{},"枚举字段存进去的是数字",[95,3047,3048,3049],{},"默认 ",[25,3050,3051],{},"EnumType.ORDINAL",[95,3053,3054,3055],{},"加 ",[25,3056,1922],{},[80,3058,3059,3062,3065],{},[95,3060,3061],{},"MySQL 连不上，报时区或 SSL 错",[95,3063,3064],{},"连接串缺少必要参数",[95,3066,3067,3068,3070,3071],{},"URL 里加 ",[25,3069,569],{},"，本地可加 ",[25,3072,3073],{},"useSSL=false",[80,3075,3076,3079,3086],{},[95,3077,3078],{},"数据重启就没了",[95,3080,3081,3082,3085],{},"用的是 ",[25,3083,3084],{},"jdbc:h2:mem:"," 内存库",[95,3087,3088,3089,3092],{},"改 ",[25,3090,3091],{},"jdbc:h2:file:...","，或换 MySQL",[80,3094,3095,3100,3106],{},[95,3096,3097,3098],{},"写属性了但打不开 ",[25,3099,535],{},[95,3101,3102,3103,3105],{},"Boot 4 里 H2 控制台要额外加 ",[25,3104,2913],{}," 依赖",[95,3107,3108,3109,3111],{},"补依赖重启；日志里会有 ",[25,3110,535],{}," 映射",[80,3113,3114,3120,3123],{},[95,3115,3116,3117],{},"保存时报 ",[25,3118,3119],{},"NULL not allowed for column",[95,3121,3122],{},"关联字段声明了非空，但代码路径没给它赋值",[95,3124,3125],{},"让必填关联在构造器里就传入，或把字段改成可空（见第 04 篇）",[17,3127,3129],{"id":3128},"_9-小结","9 小结",[34,3131,3132,3137,3148,3159,3162,3178],{},[37,3133,3134,632],{},[50,3135,3136],{},"ORM 是思路，JPA 是规范，Hibernate 是规范的一种实现，Spring Data JPA 是更高一层的封装",[37,3138,3139,3140,2514,3142,3144,3145,632],{},"实体用普通 class，标注 ",[25,3141,115],{},[25,3143,1715],{},"，需要无参构造器；枚举用 ",[25,3146,3147],{},"@Enumerated(STRING)",[37,3149,3150,3151,3158],{},"Repository ",[50,3152,3153,3154,3157],{},"只要一个继承 ",[25,3155,3156],{},"JpaRepository\u003C实体, 主键类型>"," 的空接口","，实现由容器生成；",[37,3160,3161],{},"H2 用来跑通流程和写测试，MySQL 通过 Profile 切换，密码从环境变量取；",[37,3163,3164,3166,3167,3169,3170,3172,3173,632],{},[25,3165,1914],{}," 在开发环境用 ",[25,3168,2841],{},"\u002F",[25,3171,2867],{},"，",[50,3174,3175,3176],{},"生产环境只信任 ",[25,3177,640],{},[37,3179,3180,3181,3183,3184,1959,3186,3188],{},"数据初始化可以用 ",[25,3182,2769],{},"，也可以用 ",[25,3185,2780],{},[25,3187,2783],{},"，但别混着来。",[13,3190,3191,3192,3195,3196,3199],{},"到这一步只能按主键查。下一篇写",[50,3193,3194],{},"查询","：怎么用方法名派生查询、怎么用 ",[25,3197,3198],{},"@Query"," 写 JPQL，\n以及分页排序怎么落地。",[3201,3202,3203],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":185,"searchDepth":222,"depth":222,"links":3205},[3206,3207,3208,3213,3218,3222,3223,3224,3225,3226],{"id":19,"depth":206,"text":20},{"id":68,"depth":206,"text":69},{"id":172,"depth":206,"text":173,"children":3209},[3210,3211,3212],{"id":177,"depth":222,"text":178},{"id":377,"depth":222,"text":378},{"id":539,"depth":222,"text":540},{"id":654,"depth":206,"text":655,"children":3214},[3215,3216,3217],{"id":658,"depth":222,"text":659},{"id":1764,"depth":222,"text":1765},{"id":1864,"depth":222,"text":1865},{"id":1983,"depth":206,"text":1984,"children":3219},[3220,3221],{"id":1987,"depth":222,"text":1988},{"id":2072,"depth":222,"text":2073},{"id":2521,"depth":206,"text":2522},{"id":2790,"depth":206,"text":2791},{"id":2903,"depth":206,"text":2904},{"id":2938,"depth":206,"text":2939},{"id":3128,"depth":206,"text":3129},"学习",null,"2026-10-15","用 ORM 把类映射到表：@Entity、@Id、@GeneratedValue 怎么用，CrudRepository 三层接口怎么选，H2 与 MySQL 怎么配，以及 ddl-auto 到底该写哪个值。","md",false,{},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-02",{"title":5,"description":3230},"Spring 学习笔记","posts\u002Fjavaspring\u002Fspring-notes-02",[3239,137,3240],"Spring","学习笔记","NS-WIhzdyLeNF3mln2sR08D_rrADU9sLEXcR54RPlFE",[3243,3249,3254,3259,3264,3269,3274,3279,3284,3289,3294,3299,3304,3309,3314,3319,3324,3329,3334,3339,3344,3349,3354,3359,3364,3369,3374,3379,3384,3388,3389,3393,3397,3401,3405,3409,3413,3417,3421,3425,3429,3435,3439,3445,3449,3453,3457,3461,3465,3469,3473,3478,3482,3486,3491,3496,3502,3506,3511,3515],{"path":3244,"title":3245,"description":3246,"date":3247,"series":3248,"seriesOrder":955},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-28","软件分析与设计笔记 28：draw.io 作图手册：五张图的符号位置、落笔顺序与自检","不模拟软件界面，只讲清三件事：每个 UML 符号在 draw.io 里搜什么词、一张图该按什么顺序落笔、画完拿什么标准自查。覆盖用例图、活动图、顺序图、类图、界面草图五种图纸，末尾附「最容易画反的十二笔」总检表。","2026-11-19","软件分析与设计笔记",{"path":3250,"title":3251,"description":3252,"date":3253,"series":3248,"seriesOrder":937},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-27","软件分析与设计笔记 27：Paper 1 应试：从读案例到交卷的一次完整演算","拿一份完整的案例，从头到尾走一遍：怎么读案例列角色、怎么在四个小问之间分配时间、每一张图从哪一句话推出来、两张表怎么填、交卷前拿什么清单自查。附 C25 真题与样卷的题型分值对照。","2026-11-18",{"path":3255,"title":3256,"description":3257,"date":3258,"series":3248,"seriesOrder":932},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-26","软件分析与设计笔记 26：总结：把整条链路串起来","系列收尾：从需求到代码的完整链路回顾、每一环的产出与验收方式、六条贯穿全系列的原则，以及一份可以直接拿去做项目自查的清单。","2026-11-17",{"path":3260,"title":3261,"description":3262,"date":3263,"series":3248,"seriesOrder":924},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-25","软件分析与设计笔记 25：代码重构：坏味道与手法","重构不改变外部行为、只改善内部结构：四种常见坏味道与对应手法、为什么动手之前必须先有测试、什么时候该重构，以及重构和前面那些设计模型是什么关系。","2026-11-16",{"path":3265,"title":3266,"description":3267,"date":3268,"series":3248,"seriesOrder":919},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-24","软件分析与设计笔记 24：设计原则：内聚、耦合与多态","好设计怎么衡量：内聚与耦合分别在说什么、一次从「差设计」到「多态重设计」的完整对照、抽象类与接口的选择依据，以及复用为什么会从优势变成风险。","2026-11-15",{"path":3270,"title":3271,"description":3272,"date":3273,"series":3248,"seriesOrder":911},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-23","软件分析与设计笔记 23：设计建模：从对象模型到能实现的方案","分析模型能用但还不能直接开工：对象模型怎么映射到关系模型、继承映射的三种方案怎么选、数据访问层与 Data Broker 解决什么问题，以及三层架构与 MVC 的分工。","2026-11-14",{"path":3275,"title":3276,"description":3277,"date":3278,"series":3248,"seriesOrder":880},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-22","软件分析与设计笔记 22：分析建模：边界、实体与控制对象","不经过分析直接写代码会踩什么坑、三种分析对象各管一段、怎么用顺序图完成一次用例实现，以及从顺序图导出类的属性、操作与关系的方法。","2026-11-13",{"path":3280,"title":3281,"description":3282,"date":3283,"series":3248,"seriesOrder":875},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-21","软件分析与设计笔记 21：UML：类图与顺序图","为什么需要一种共同的画法：类符号三格怎么画、五种关系的符号与设计含义、每种关系落到 Java 是什么写法，顺序图的三要素怎么摆；以及这三种最容易找不到的符号在 draw.io 里各搜什么词。","2026-11-12",{"path":3285,"title":3286,"description":3287,"date":3288,"series":3248,"seriesOrder":866},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-20","软件分析与设计笔记 20：面向对象概念：对象、关系与多态","面向对象不是「用某个语言写代码」：对象与类的区别、封装真正在保护什么、五种关系（依赖、关联、聚合、组合、继承）各自何时用，以及多态为什么能减少条件判断。","2026-11-11",{"path":3290,"title":3291,"description":3292,"date":3293,"series":3248,"seriesOrder":848},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-19","软件分析与设计笔记 19：规范化与逻辑数据模型","一次改地址引发的数据不一致，讲清规范化的真正动机；UNF 到 3NF 每一步在解决什么问题，派生字段为什么不能存，优化为什么和规范化方向相反，以及逻辑数据模型在 3NF 之上补了什么。","2026-11-10",{"path":3295,"title":3296,"description":3297,"date":3298,"series":3248,"seriesOrder":839},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-18","软件分析与设计笔记 18：数据库分析：实体、关系与数据字典","为什么不能拿到需求就动手建表：实体与关系怎么判定、基数为什么必须由业务方确认、数据命名要遵守什么规则，以及数据字典四列里最容易被空着的那一列。","2026-11-09",{"path":3300,"title":3301,"description":3302,"date":3303,"series":3248,"seriesOrder":834},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-17","软件分析与设计笔记 17：用生成式 AI 做设计与开发：能用的场景与要防的坑","生成式 AI 在工作流里真正站得住的三个场景 —— 造示例数据、解释陌生代码、起草初稿；它为什么会复现社会偏见、为什么会编得像真的，以及哪些内容绝不能直接采用。","2026-11-08",{"path":3305,"title":3306,"description":3307,"date":3308,"series":3248,"seriesOrder":818},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-16","软件分析与设计笔记 16：从 persona 到 URS：一份交付物的完整骨架","把前面十几篇串成一份能交出去的东西：用户需求规格说明书的九个部分、每一章来自哪个环节、由谁确认，以及面向决策者的提案该讲清哪三件事。","2026-11-07",{"path":3310,"title":3311,"description":3312,"date":3313,"series":3248,"seriesOrder":781},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-15","软件分析与设计笔记 15：非功能需求：把「快、稳、安全」变成数字","为什么「系统要快」不是一条需求：非功能需求怎么量化，决定性能的用户量、交易量、数据量三张表怎么估，安全需求要写清什么，以及可靠性的「不可用」该怎么定义。","2026-11-06",{"path":3315,"title":3316,"description":3317,"date":3318,"series":3248,"seriesOrder":358},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-14","软件分析与设计笔记 14：软件测试：层级、方法与等价类划分","「我们测过了」这句话问题在哪：为什么静态评审和动态测试缺一不可，五个测试层级各自验证什么，白盒与黑盒的差别，以及用等价类划分和边界值把无限输入变成有限用例的方法。","2026-11-05",{"path":3320,"title":3321,"description":3322,"date":3323,"series":3248,"seriesOrder":345},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-13","软件分析与设计笔记 13：需求获取：五种调查方法与一场访谈","需求从哪里来：五种事实调查方法各自适合回答什么、代价是什么，访谈从准备到记录的全过程，问卷设计里最容易犯的错，以及访谈记录必须写下来的那几项信息。","2026-11-04",{"path":3325,"title":3326,"description":3327,"date":3328,"series":3248,"seriesOrder":331},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-12","软件分析与设计笔记 12：前端设计：把流程变成屏幕","界面设计不是「画得好看」：从活动图走到屏幕的三步，CRUD 任务流怎么当检查表，界面必须回答的三个问题，信息架构与五种导航类型，视觉一致性与留白，以及移动端与 Web 端的差别。","2026-11-03",{"path":3330,"title":3331,"description":3332,"date":3333,"series":3248,"seriesOrder":317},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-11","软件分析与设计笔记 11：Agentic AI 与业务流程自动化","AI agent 到底是什么：五项能力与四个核心组件，什么场景该用定时任务、什么该用程序、什么才值得用 agent，两种实现路径的差别，必须提前防住的四类失败模式，以及设计方法从提示词到编排的演进。","2026-11-02",{"path":3335,"title":3336,"description":3337,"date":3338,"series":3248,"seriesOrder":308},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-10","软件分析与设计笔记 10：精炼用例模型：include、extend 与泛化","三种精炼用例关系的判断标准：include 什么时候用、它和 extend 的区别到底在哪、参与者泛化与用例泛化各解决什么问题，以及精炼到什么程度就该停手。","2026-11-01",{"path":3340,"title":3341,"description":3342,"date":3343,"series":3248,"seriesOrder":299},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-09","软件分析与设计笔记 09：功能需求（二）：从流程到业务实体","活动图画完之后，怎么从流程里筛出真正的业务实体：三条判断标准、哪些名词其实不该建实体、属性从哪里来，以及领域模型和后面的数据模型分别负责什么。","2026-10-31",{"path":3345,"title":3346,"description":3347,"date":3348,"series":3248,"seriesOrder":284},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-08","软件分析与设计笔记 08：功能需求（一）：用例模型与活动图","从用户故事到用例的翻译过程：什么才算「好」的参与者，系统边界怎么划，用例描述必须写清哪四件事，活动图怎么把分支、并行与泳道画清楚，以及这两个图在 draw.io 里各用哪个形状、最容易画错哪一笔。","2026-10-30",{"path":3350,"title":3351,"description":3352,"date":3353,"series":3248,"seriesOrder":270},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-07","软件分析与设计笔记 07：用户故事：把需求写成可验收的一句话","用户故事的三段结构，怎么从用户画像和数据地图里把它提取出来，一条故事多大算合适、时序为什么也是需求的一部分，以及验收标准怎么写才算真的可验收。","2026-10-29",{"path":3355,"title":3356,"description":3357,"date":3358,"series":3248,"seriesOrder":256},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-06","软件分析与设计笔记 06：范围界定（数据侧）：用数据校验你的判断","访谈结论和数字对不上时该怎么办：分析的四个层次分别在回答什么问题、为什么不能跳级、数据准备与搜集怎么做成一张应用地图，以及数据最容易在哪几个地方骗你。","2026-10-28",{"path":3360,"title":3361,"description":3362,"date":3363,"series":3248,"seriesOrder":247},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-05","软件分析与设计笔记 05：范围界定（用户侧）：用户画像与客户旅程图","为什么「所有用户」是一个陷阱，项目边界该怎么划，用户画像要写哪五个要素、最容易写成什么废纸，客户旅程图怎么把阶段、想法和情绪画在同一张纸上，以及怎么从这两样东西一步步推到可验证的需求。","2026-10-27",{"path":3365,"title":3366,"description":3367,"date":3368,"series":3248,"seriesOrder":237},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-04","软件分析与设计笔记 04：接手陌生案例的第一步：三问与一张表","拿到一个完全陌生的业务案例，怎么从零开始不跑偏：只问 Who、What、Why 三个问题，把「问题—事实—假设—理由」记成一张四列表，说清为什么假设必须写明理由，以及带着问题进访谈为什么比空手去有效得多。","2026-10-26",{"path":3370,"title":3371,"description":3372,"date":3373,"series":3248,"seriesOrder":222},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-03","软件分析与设计笔记 03：Agile 与 Scrum：迭代、角色与产出物","从「三周做完交付，客户说这不是我要的」讲起，说清瀑布式与迭代式的根本差别，Scrum 的三个角色和三种产出物，一个 sprint 里四次固定活动各自在解决什么问题，以及敏捷里最常见的几个误解。","2026-10-25",{"path":3375,"title":3376,"description":3377,"date":3378,"series":3248,"seriesOrder":206},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-02","软件分析与设计笔记 02：设计思维、用户驱动与数据驱动","从「客服要一个能一次看完所有排期的界面」这个翻车案例讲起，说清设计思维到底是什么、用户驱动与数据驱动各自能看见什么又看不见什么、为什么真正的落点是把两者合起来的 data-informed design，以及访谈中怎么问出有用的信息。","2026-10-24",{"path":3380,"title":3381,"description":3382,"date":3383,"series":3248,"seriesOrder":192},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-01","软件分析与设计笔记 01：课程地图与从需求到设计的主干","这一篇不写具体方法，先把整条链路铺开：软件分析与设计到底要解决什么问题，从需求到设计分成哪两段，中间有哪十几个环节，一份完整的交付物里应该有什么，以及后面 27 篇笔记怎么排、新人该按什么顺序读。","2026-10-23",{"path":3385,"title":3386,"description":3387,"date":3229,"series":3236,"seriesOrder":192},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-01","Spring 学习笔记 01：IoC、依赖注入与第一个 Spring Boot 应用","从「手写 new 出来的三层结构」的痛点讲起，说清 IoC 容器、Bean 与依赖注入是怎么回事，再用 Maven 与 Spring Initializr 跑起第一个能返回 JSON 的应用。",{"path":3234,"title":5,"description":3230,"date":3229,"series":3236,"seriesOrder":206},{"path":3390,"title":3391,"description":3392,"date":3229,"series":3236,"seriesOrder":222},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-03","Spring 学习笔记 03：JPA 查询——派生方法、JPQL 与分页排序","Repository 自带的方法只够按主键查，这篇讲三种自己造查询的方式：方法名派生、@Query 写 JPQL、原生 SQL，以及 Pageable 分页排序和投影返回。",{"path":3394,"title":3395,"description":3396,"date":3229,"series":3236,"seriesOrder":237},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-04","Spring 学习笔记 04：JPA 关联映射与仓储测试","一对多、多对多怎么映射，谁负责维护外键，FetchType 与 CascadeType 该怎么选，顺带用 @DataJpaTest 与 AssertJ 把仓储层测起来。",{"path":3398,"title":3399,"description":3400,"date":3229,"series":3236,"seriesOrder":247},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-05","Spring 学习笔记 05：Spring MVC 与 Thymeleaf 入门","一次页面请求在 Spring MVC 里走了哪几步，@Controller 怎么把数据交给模板，@PathVariable 与 @RequestParam 怎么取参数，以及控制器为什么必须是无状态的。",{"path":3402,"title":3403,"description":3404,"date":3229,"series":3236,"seriesOrder":256},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-06","Spring 学习笔记 06：表单绑定与服务端校验","把表单字段绑到对象上，用 Bean Validation 注解做服务端校验，把错误信息显示回用户刚填的那一页，再补上自定义校验注解与全局异常处理。",{"path":3406,"title":3407,"description":3408,"date":3229,"series":3236,"seriesOrder":270},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-07","Spring 学习笔记 07：Thymeleaf 片段复用与国际化","用 th:fragment 与 th:insert\u002Fth:replace 把导航栏、页脚抽成片段，页面套用统一布局，再用 messages.properties 与 LocaleResolver 做中英文切换。",{"path":3410,"title":3411,"description":3412,"date":3229,"series":3236,"seriesOrder":284},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-08","Spring 学习笔记 08：HTTP Session 与会话状态管理","HTTP 无状态带来的问题，Cookie 与 Session 怎么分工，Spring 里读写会话的几种方式，以及会话固定、超时、多实例共享这些必须想清楚的事。",{"path":3414,"title":3415,"description":3416,"date":3229,"series":3236,"seriesOrder":299},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-09","Spring 学习笔记 09：拦截器 HandlerInterceptor","登录检查、权限判断、请求耗时统计这类横切逻辑该放哪：HandlerInterceptor 的三个回调各在什么时机执行，怎么注册与排除路径，以及它和过滤器的分工。",{"path":3418,"title":3419,"description":3420,"date":3229,"series":3236,"seriesOrder":308},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-10","Spring 学习笔记 10：Service 层与事务","一组数据库操作要么全成功要么全失败，靠 @Transactional 怎么做到；传播行为、隔离级别、回滚规则怎么选，以及自调用失效、乐观锁悲观锁这些实战里的坑。",{"path":3422,"title":3423,"description":3424,"date":3229,"series":3236,"seriesOrder":317},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-11","Spring 学习笔记 11：REST API 基础——把 Service 暴露成接口","同一套 Service 怎么同时服务页面和客户端：@RestController 的路由与方法语义、状态码怎么给、DTO 怎么定，以及 MockMvc 端到端和切片测试的两种写法与各自的坑。",{"path":3426,"title":3427,"description":3428,"date":3229,"series":3236,"seriesOrder":331},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-12","Spring 学习笔记 12：REST 进阶——版本、更新、跨域与文档化","接口能跑通之后要处理的几件事：URL 与响应结构怎么定、版本怎么演进、更新怎么做到可安全重试、CORS 怎么配、HATEOAS 与 Spring Data REST 该不该用。",{"path":3430,"title":3431,"description":3432,"date":3433,"series":3434,"seriesOrder":237},"\u002Fposts\u002Fjava\u002Fjava-functionalprogramming-notes","Java 函数式编程笔记：Lambda、函数式接口与 Streams","学习 Java 函数式编程时的个人笔记：Lambda 表达式的五步简写与方法引用、常见函数式接口、Stream 流水线与惰性求值，以及创建流、中间操作、终止操作与收集器的用法。示例代码均为自写，运行结果来自本地实测。","2026-10-13","Java 学习笔记",{"path":3436,"title":3437,"description":3438,"date":3433,"series":3434,"seriesOrder":222},"\u002Fposts\u002Fjava\u002Fjava-generics-notes","Java 泛型笔记：泛型方法、有界类型与泛型类","学习 Java 泛型时的个人笔记：从重复的重载方法与多态尝试讲起，再到泛型方法与类型推断、有界泛型类型、Comparable 与 Number 边界，以及泛型类在集合中的用法。示例代码均为自写，运行结果来自本地实测。",{"path":3440,"title":3441,"description":3442,"date":3443,"series":3444,"seriesOrder":237},"\u002Fposts\u002Fmysql\u002Fmysql-learning-04","MySQL 学习笔记 04：数据操纵（增删改）","MySQL 学习笔记第四篇：INSERT 插入（含批量插入与 INSERT ... SELECT）、UPDATE 更新、DELETE 删除，以及各自的坑。","2026-10-10","SQL 学习笔记",{"path":3446,"title":3447,"description":3448,"date":3443,"series":3444,"seriesOrder":247},"\u002Fposts\u002Fmysql\u002Fmysql-learning-05","MySQL 学习笔记 05：DDL 概览","MySQL 学习笔记第五篇：数据定义语言概览——Schema、Table、View、Index、Constraints 这几类对象分别对应哪些 CREATE \u002F ALTER \u002F DROP 命令。",{"path":3450,"title":3451,"description":3452,"date":3443,"series":3444,"seriesOrder":256},"\u002Fposts\u002Fmysql\u002Fmysql-learning-06","MySQL 学习笔记 06：CREATE TABLE 与数据类型","MySQL 学习笔记第六篇：CREATE TABLE 的完整语法、常用数据类型的选型建议，以及外键的两种定义方式。",{"path":3454,"title":3455,"description":3456,"date":3443,"series":3444,"seriesOrder":270},"\u002Fposts\u002Fmysql\u002Fmysql-learning-07","MySQL 学习笔记 07：索引","MySQL 学习笔记第七篇：索引的作用与 B+ 树结构、单列与复合索引的创建、索引的代价与适用场景。",{"path":3458,"title":3459,"description":3460,"date":3443,"series":3444,"seriesOrder":284},"\u002Fposts\u002Fmysql\u002Fmysql-learning-08","MySQL 学习笔记 08：DROP 与 ALTER","MySQL 学习笔记第八篇：DROP 删除数据库对象、ALTER 修改表结构，以及常见的坑。",{"path":3462,"title":3463,"description":3464,"date":3443,"series":3444,"seriesOrder":299},"\u002Fposts\u002Fmysql\u002Fmysql-learning-09","MySQL 学习笔记 09：约束与数据完整性","MySQL 学习笔记第九篇：SQL 约束——NOT NULL、CHECK、PRIMARY KEY、FOREIGN KEY 等如何保证数据完整性，以及它们的对比。",{"path":3466,"title":3467,"description":3468,"date":3443,"series":3444,"seriesOrder":308},"\u002Fposts\u002Fmysql\u002Fmysql-learning-10","MySQL 学习笔记 10：视图","MySQL 学习笔记第十篇：视图（Views）的定义与特性、和表的对比、创建与使用，以及可更新视图的限制。",{"path":3470,"title":3471,"description":3472,"date":3443,"series":3444,"seriesOrder":317},"\u002Fposts\u002Fmysql\u002Fmysql-learning-11","MySQL 学习笔记 11：存储过程","MySQL 学习笔记第十一篇：存储过程（Stored Procedures）的概念、创建与调用、带参数的写法，以及优缺点。",{"path":3474,"title":3475,"description":3476,"date":3477,"series":3444,"seriesOrder":192},"\u002Fposts\u002Fmysql\u002Fmysql-learning-01","MySQL 学习笔记 01：MySQL 基础知识","MySQL 学习笔记第一篇：MySQL Server 与 Workbench 的安装，数据库与 DBMS 的基本概念，关系模型、ACID 事务、常见数据类型，以及后续的学习路线。","2026-10-09",{"path":3479,"title":3480,"description":3481,"date":3477,"series":3444,"seriesOrder":206},"\u002Fposts\u002Fmysql\u002Fmysql-learning-02","MySQL 学习笔记 02：SELECT 查询与过滤","MySQL 学习笔记第二篇：SELECT 的基本语法与逻辑执行顺序，WHERE 过滤（比较、LIKE、IN、BETWEEN、NULL）、各种 JOIN、UNION 与子查询。",{"path":3483,"title":3484,"description":3485,"date":3477,"series":3444,"seriesOrder":222},"\u002Fposts\u002Fmysql\u002Fmysql-learning-03","MySQL 学习笔记 03：聚合、分组与输出控制","MySQL 学习笔记第三篇：COUNT\u002FSUM\u002FAVG 等聚合函数、GROUP BY 分组、HAVING 过滤、ORDER BY 与 LIMIT，以及一条 SELECT 的完整逻辑执行顺序。",{"path":3487,"title":3488,"description":3489,"date":3490,"series":3434,"seriesOrder":206},"\u002Fposts\u002Fjava\u002Fjava-oop-notes","Java 面向对象笔记：类、继承、多态与异常","学习 Java 面向对象编程时的个人笔记：类与对象、封装与访问修饰符、继承与多态、集合、抽象类与接口、异常处理。示例代码均为自写，运行结果来自本地实测。","2026-10-06",{"path":3492,"title":3493,"description":3494,"date":3495,"series":3434,"seriesOrder":192},"\u002Fposts\u002Fjava\u002Fjava-basics-notes","Java 基础语法笔记：从环境安装到方法与数组","学习 Java 基础语法时的个人笔记：JDK 安装、输入输出、变量与运算符、条件与循环、字符串、数组与方法。示例代码均为自写，运行结果来自本地实测。","2026-10-05",{"path":3497,"title":3498,"description":3499,"date":3500,"series":3501,"seriesOrder":192},"\u002Fposts\u002Fgit\u002Fgit-install-and-basics","Git 安装与基本使用","从装好 Git 到跑通日常提交流程，把最常用的几条命令和它们背后的三个区域讲清楚。","2026-10-04","Git 入门",{"path":3503,"title":3504,"description":3505,"date":3500,"series":3501,"seriesOrder":206},"\u002Fposts\u002Fgit\u002Flocal-remote-repository","本地远程仓库的部署","用自己的机器当 Git 服务器：建一个裸仓库、走 SSH 连上去，把本地提交推上去。",{"path":3507,"title":3508,"description":3509,"date":3500,"series":3510,"seriesOrder":192},"\u002Fposts\u002Fguide\u002Fguide-file-layout","文件都放在哪","文章、图片、封面分别放在仓库的什么位置，哪些目录可以放手改、哪些碰了会出问题。","博客使用指南",{"path":3512,"title":3513,"description":3514,"date":3500,"series":3510,"seriesOrder":206},"\u002Fposts\u002Fguide\u002Fguide-frontmatter","frontmatter 每个字段是什么意思","文章开头那段被横线包住的字段，逐个说明作用、默认行为和写错的后果。",{"path":3516,"title":3517,"description":3518,"date":3500,"series":3510,"seriesOrder":222},"\u002Fposts\u002Fguide\u002Fguide-markdown","正文怎么写 —— Markdown 与排版效果","标题、链接、图片、代码、表格分别怎么写，以及本站对它们做了哪些自定义处理。",1793268152446]