• JDBC技术(java数据库连接技术)


    目录

    一:JDBC技术介绍

    二:JDBC使用路线

    三:全新JDBC核心API

            3.1:引入mysql-jdbc驱动jar

            3.2:JDBC基本使用步骤分析

            3.3:基于statement演示查询

            3.4:基于statement方式问题

            3.5:基于preparedStatement方式优化

            3.6:基于preparedStatement演示curd

            3.7:preparedStatement使用方式总结

    四:全新JDBC扩展提升

            4.1:自增长主键回显实现,批量数据插入性能提升

    五:常见错误及分析

    六:其它知识 


    引入:USB技术介绍

            USB,是英文Universal Serial Bus(通用串行总线)的缩写,是一个外部总线标准,用于规范与外部设备的连接和通讯

            USB是一个技术统称,由三部分组成

                    第一部分:USB的规范和设计标准(概念)

                    第二部分:电脑端的USB接口(接口)

                    第三部分:外设的USB接口和具体发送信号的驱动程序(实现类) 

    正题

    一:JDBC技术介绍

            JDBC:Java Database Connectivity(Java连接数据库技术)

            通俗点说,在Java代码中,使用JDBC提供的方法,可以发送字符串类型的SQL语句到数据库管理软件(MySQL、Oracle等),并且获取语句执行结果,进而实现数据库数据CURD的技术

            JDBC概述来说就是Java程序与数据库软件之间连接的桥梁

    JDBC规范和接口JDBC第三方数据库厂商
    Java语言只提供规范(接口),规定数据库操作方法;标准的类库存在于java.sql,javax.sql包下java连接数据库技术统称各数据库厂商,根据Java的JDBC规范(接口),完成具体的实现驱动代码(jar),实现代码可以不同,但是方法都相同

    总结:

            1.JDBC由两部分组成:

                    一是Java提供的JDBC规范(接口)

                    二是各个数据库厂商的实现驱动jar包(jar包是java程序打成的一种压缩包格式,你可以将这些jar包引入你的项目中,然后你可以使用这个java程序中类和方法以及属性了)

            2.JDBC技术是一种典型的面向接口编程

    优势:

            我们只要学习JDBC接口规定方法,即可操作所有数据库软件

            项目中期需要切换数据库,我们只需要更新第三方驱动jar包,不需要更改代码 

    二:JDBC使用路线

            具体核心类和接口

                    DriverManager

                            1.将第三方数据库厂商的实现驱动jar注册到程序中

                            2.可以根据数据库连接信息获取connection

                    Connection

                            1.和数据库建立的连接,在连接对象上,可以多次执行数据库curd操作

                            2.可以获取statement(静态SQL路线,没有动态值语句)和preparedStatement(预编译SQL路线,有动态值语句)、callablestatement(执行标准存储过的SQL语句)对象

                    Statement、PreparedStatement、CallableStatement

                            1.具体发送SQL语句到数据库管理软件的对象

                            2.不同发送方式稍有不同

                    Result

                            1.面向对象思维的产物(抽象成数据库的查询结果表)

                            2.存储DQL查询数据库结果的对象

                            3.需要我们进行解析,获取具体的数据库数据

    三:全新JDBC核心API

            3.1:引入mysql-jdbc驱动jar

                    1.驱动jar版本选择

                    2.java工程导入依赖

                            a.项目创建lib文件夹

                            b.导入驱动依赖jar包

                            c.jar包右键-添加为项目依赖

            3.2:JDBC基本使用步骤分析

                    1.注册驱动-将依赖的jar包,进行安装

                    2.获取连接-Connection

                    3.创建发送SQL语句对象-statement

                    4.发送SQL语句,并获取返回结果-ResultSet结果对象

                    5.结果集解析

                    6.资源关闭-释放(由内向外关

            3.3:基于statement演示查询

                    1.准备数据库数据

                    2.查询目标

                    3.基于statement实现查询

    1. package com.jiayifeng.api.statement;
    2. import com.mysql.cj.jdbc.Driver;
    3. import java.sql.*;
    4. /**
    5. * author 爱编程的小贾
    6. * create 2023-10-15 14:20
    7. */
    8. public class StatementQueryPart {
    9. public static void main(String[] args) throws SQLException {
    10. // 1.注册驱动
    11. /*
    12. 注册驱动
    13. 依赖:
    14. 驱动版本8+ com.mysql.cj.jdbc.Driver
    15. 驱动版本5+ com.mysql.jdbc.Driver
    16. */
    17. DriverManager.registerDriver(new Driver());//8.0+(8.0.27)
    18. // 2.获取连接
    19. /*
    20. Java程序要和数据库创建连接
    21. Java程序,连接数据库,肯定是调用某个方法,方法也需要填入连接数据库的基本信息:
    22. 数据库ip地址:127.0.0.1
    23. 数据库端口号:3306
    24. 账号:root
    25. 密码:passwd
    26. 连接数据库的名称:MySQLName
    27. */
    28. /*
    29. 参数1:url
    30. jdbc:数据库厂商名://ip地址:port/数据库名
    31. 参数2:username 数据库软件的账号
    32. password 数据库软件的密码
    33. */
    34. // java.sql 接口 = 实现类
    35. Connection connection = DriverManager.
    36. getConnection("jdbc:mysql://localhost:3306/studentdb", "root", "ASUS010519");
    37. // 3.创建statement
    38. Statement statement = connection.createStatement();
    39. // 4.发送SQL语句,并且获取返回结果
    40. String sql = "select * from student;";
    41. ResultSet resultSet = statement.executeQuery(sql);
    42. // 5.进行结果集解析
    43. // 看有没有下一行数据,有,你就可以获取
    44. while(resultSet.next()){
    45. String sno = resultSet.getString("sno");
    46. String sname = resultSet.getString("sname");
    47. String ssex = resultSet.getString("ssex");
    48. String sage = resultSet.getString("sage");
    49. String sdept = resultSet.getString("sdept");
    50. int sclass = resultSet.getInt("sclass");
    51. System.out.println(sno + sname + ssex + sage + sdept + sclass);
    52. }
    53. // 6.关闭资源
    54. resultSet.close();
    55. statement.close();
    56. connection.close();
    57. }
    58. }
            3.4:基于statement方式问题

                    存在问题:

                            ①SQL语句需要字符串拼接,比较麻烦

                            ② 只能拼接字符串类型,其他的数据库类型无法处理

                            ③可能发生注入攻击

                                    动态值充当了SQL语句结构,影响了原有的查询结果

    1. package com.jiayifeng.api.statement;
    2. import com.mysql.cj.jdbc.Driver;
    3. import java.sql.*;
    4. import java.util.Properties;
    5. import java.util.Scanner;
    6. /**
    7. * author 爱编程的小贾
    8. * create 2023-10-15 15:03
    9. *
    10. * 一:模拟用户登录
    11. * 1.明确jdbc的使用教程和详细讲解内部设计API方法
    12. * 2.发现问题,引出preparedStatement
    13. *
    14. * TODO:
    15. * 1.输入账号和密码
    16. * 2.进行数据库信息查询(t_user)
    17. * 3.反馈登陆成功还是登录失败
    18. *
    19. * TODO:
    20. * 1.键盘输入事件,收集账号和密码信息
    21. * 2.注册驱动
    22. * 3.获取连接
    23. * 4.创建statement
    24. * 5.发送查询SQL语句,并获取返回结果
    25. * 6.结果判断,显示登录成功还是失败
    26. * 7.关闭资源
    27. */
    28. public class StatementUserLoginPart {
    29. public static void main(String[] args) throws Exception {
    30. // 1.获取用户输入信息
    31. Scanner scanner = new Scanner(System.in);
    32. System.out.println("请输入账号:");
    33. String account = scanner.nextLine();
    34. System.out.println("请输入密码:");
    35. String password = scanner.nextLine();
    36. // 2.注册驱动
    37. /*
    38. 方案一:
    39. DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
    40. 问题:会注册两次驱动
    41. 1.DriverManager.registerDriver() 方法本身会注册一次
    42. 2.Driver.static{ DriverManager.registerDriver()} 静态代码块,也会注册一次!
    43. 解决:只想注册一次驱动
    44. 只触发静态代码块即可 Driver
    45. 触发静态代码块:
    46. 类加载机制:类加载的时刻,会触发静态代码块!
    47. 加载 [class 文件 -> jvm虚拟机的class对象]
    48. 连接 [验证(检查文件类型) -> 准备(静态变量默认值) -> 解析(触发静态代码块)]
    49. 初始化(给静态属性赋真实值)
    50. 如何触发类加载:
    51. 1.new 关键字
    52. 2.调用静态方法
    53. 3.调用静态属性
    54. 4.接口1.8 default默认实现
    55. 5.反射
    56. 6.子类触发父类
    57. 7.程序的入口main
    58. */
    59. // 方案一:
    60. // DriverManager.registerDriver(new Driver());
    61. // 方案二:mysql新版本的驱动 | 换成oracle
    62. // new Driver();
    63. // 字符串 -> 提取到外部的配置文件 -> 可以在不改变代码的情况下,完成数据库驱动的切换
    64. Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
    65. // DriverManager.registerDriver(new Driver());//8.0+(8.0.27)
    66. // 3.获取数据库连接
    67. /*
    68. 核心属性:
    69. 1.数据库软件所在的主机的IP地址:localhost | 127.0.0.1
    70. 2.数据库软件的主机的端口号:3306
    71. 3.连接的具体库:studentdb
    72. 4.连接的账号:root
    73. 5.连接的密码:passwd
    74. 6.可选的信息:没有
    75. 三个参数:
    76. String url 数据库软件所在地址的信息,连接的具体库,以及其它可选信息
    77. 语法:jdbc:数据库管理软件名称[mysql,oracle]://ip地址|port端口号/数据库名?key=value&key=value
    78. String user 数据库的账号
    79. String passwd 数据库的密码
    80. 两个参数:
    81. String url:同上
    82. Properties info:存储账号和密码
    83. Properties类似于Map,只不过key = value都是字符串形式!
    84. 一个参数:
    85. String url
    86. */
    87. Connection connection = DriverManager.
    88. getConnection("jdbc:mysql://localhost:3306/studentdb","root","ASUS010519");
    89. Properties info = new Properties();
    90. info.put("user","root");
    91. info.put("passwd","ASUS010519");
    92. Connection connection1 = DriverManager
    93. .getConnection("jdbc:mysql://localhost:3306/studentdb", info);
    94. Connection connection2 = DriverManager.
    95. getConnection("jdbc:mysql://localhost:3306/studentdb?user=root&passwd=ASUS010519");
    96. // 4.创建发送SQL语句的statement对象
    97. // statement可以发送SQL语句到数据库,并且获取返回结果
    98. Statement statement = connection.createStatement();
    99. // 5.发送SQL语句(1.编写SQL语句 2.发送SQL语句)
    100. String sql1 = "select * from student where account = '" + account + "' and password = '" + password + "';";
    101. // String sql1 = "select * from student;";
    102. /*
    103. SQL分类:
    104. DDL(容器创建、修改、删除)
    105. DML(插入、删除、修改)
    106. DQL(查询)
    107. DCL(权限控制)
    108. TPL(事务控制语言)
    109. 参数:sql 非DQL
    110. 返回:int
    111. 情况一:DML 返回影响的行数,例如:删除了三条数据 return 3;
    112. 情况二:非DML return 0;
    113. int row = executeUpdate(sql);
    114. 参数:sql DQL
    115. 返回:resultSet 结果封装对象
    116. ResultSet resultSet = executeQuery(sql);
    117. */
    118. // int i = Statement.executeUpdate(sql);
    119. ResultSet resultSet = statement.executeQuery(sql1);
    120. // 6.查询结果集解析
    121. /*
    122. java是一种面向对象的思维,将查询结果封装成了resultSet对象,我们应该理解,内部一定也是有行和列的!
    123. resultSet -> 逐行获取数据,行 —> 行的列的数据
    124. 想要进行数据解析,我们需要进行两件事情:
    125. 1.移动游标指定获取数据行
    126. 2.获取指定数据行的列数据即可
    127. 1.游标移动问题:
    128. resultSet内部包含一个游标,指定当前行数据
    129. 默认游标指定的是第一行数据之前
    130. 我们可以调用next()方法向后移动一行游标
    131. 如果我们有很多行数据,我们可以使用while(next()){获取每一行数据}
    132. boolean = next() true:有更多行数据,并且向下移动一行
    133. false:没有更多行数据
    134. 2.获取列的数据问题(获取光标指定的行中的列的数据)
    135. resultSet.get类型(String columnLable | int columnIndex);
    136. columnLable:列名 如果有别名 写别名
    137. columnIndex:列的下角标获取 从左向右 从1开始
    138. */
    139. while(resultSet.next()){
    140. String sno = resultSet.getString("sno");
    141. String sname = resultSet.getString("sname");
    142. String ssex = resultSet.getString("ssex");
    143. String sage = resultSet.getString("sage");
    144. String sdept = resultSet.getString("sdept");
    145. int sclass = resultSet.getInt("sclass");
    146. System.out.println(sno + sname + ssex + sage + sdept + sclass);
    147. }
    148. // 移动一次光标,只要有数据,就代表登录成功
    149. if (resultSet.next()) {
    150. System.out.println("登录成功!");
    151. }else{
    152. System.out.println("登录失败!");
    153. }
    154. // 7.关闭资源
    155. resultSet.close();
    156. statement.close();
    157. connection.close();
    158. }
    159. }
            3.5:基于preparedStatement方式优化
    1. package com.jiayifeng.preparedStatement;
    2. import java.sql.*;
    3. import java.util.Scanner;
    4. /**
    5. * author 爱编程的小贾
    6. * create 2023-10-24 16:20
    7. */
    8. public class PSUerLoginPart {
    9. public static void main(String[] args) throws ClassNotFoundException, SQLException {
    10. // 1.获取用户输入信息
    11. Scanner scanner = new Scanner(System.in);
    12. System.out.println("请输入账号:");
    13. String account = scanner.nextLine();
    14. System.out.println("请输入密码:");
    15. String password = scanner.nextLine();
    16. // 2.ps的数据库流程
    17. // 2.1:注册驱动
    18. Class.forName("com.mysql.cj.jdbc.Driver");
    19. // 2.2:获取连接
    20. Connection connection = DriverManager.
    21. getConnection("jdbc:mysql://localhost:3306/studentdb?user=root&password=ASUS010519\");");
    22. /*
    23. statement
    24. 1.创建statement
    25. 2.拼接SQL语句
    26. 3.发送SQL语句,并且获取返回结果
    27. preparedStatement
    28. 1.编写SQL语句结果,不包含动态值部分的语句,动态值部分使用占位符 ? 替代
    29. 注意:? 只能替代动态值
    30. 2.创建preparedStatement,并且传入动态值
    31. 3.动态值 占位符 赋值 ? 单独赋值即可
    32. 4.发送SQL语句即可,并获取返回结果
    33. */
    34. // 2.3:编写SQL语句结果
    35. String sql = "select * from student where accouont = ? and password = ?";
    36. // 2.4:创建预编译statement并且设置SQL语句结果
    37. PreparedStatement preparedStatement = connection.prepareStatement(sql);
    38. // 2.5:单独的占位符进行赋值
    39. /*
    40. 参数1:index 占位符的位置 从左向右数 从1开始 账号 ? 1
    41. 参数2:object 占位符的值 可以设置任何类型的数据,避免了我们拼接和使类型更加丰富
    42. */
    43. preparedStatement.setObject(1,account);
    44. preparedStatement.setObject(2,password);
    45. // 2.6:发送SQL语句,并获取返回结果
    46. // statement.executeUpdate | executeQuery(String sql);
    47. // preparedStatement.executeUpdate | executeQuery(); TODO:因为它已经知道语句并且知道语句动态值
    48. ResultSet resultSet = preparedStatement.executeQuery();
    49. // 2.7:结果集解析
    50. if(resultSet.next()){
    51. System.out.println("登录成功");
    52. }else{
    53. System.out.println("登录失败");
    54. }
    55. // 2.8:关闭资源
    56. resultSet.close();
    57. preparedStatement.close();
    58. connection.close();
    59. }
    60. }
            3.6:基于preparedStatement演示curd
    1. package com.jiayifeng.preparedStatement;
    2. import org.junit.Test;
    3. import java.sql.Connection;
    4. import java.sql.DriverManager;
    5. import java.sql.PreparedStatement;
    6. import java.sql.SQLException;
    7. /**
    8. * author 爱编程的小贾
    9. * create 2023-10-24 16:49
    10. *
    11. * 一:使用preparedStatement进行student表的增删改查(curd操作)
    12. */
    13. public class PSCURDPart {
    14. @Test
    15. public void testInsert() throws ClassNotFoundException, SQLException {
    16. /*
    17. student表中插入一条数据
    18. */
    19. Class.forName("com.mysql.cj.jdbc.Driver");
    20. Connection connection = DriverManager.
    21. getConnection("jdbc:mysql://localhost:3306/jiayifeng", "root", "ASUS010519");
    22. String sql = "insert into t_user(account,password,nickname) values(?,?,?);";
    23. PreparedStatement preparedStatement = connection.prepareStatement(sql);
    24. preparedStatement.setObject(1,"test");
    25. preparedStatement.setObject(2,"test");
    26. preparedStatement.setObject(3,"二狗子");
    27. int rows = preparedStatement.executeUpdate();
    28. if(rows > 0){
    29. System.out.println("数据插入成功");
    30. }else {
    31. System.out.println("数据插入失败");
    32. }
    33. preparedStatement.close();
    34. connection.close();
    35. }
    36. @Test
    37. public void testUpdate() throws ClassNotFoundException, SQLException {
    38. Class.forName("com.mysql.cj.jdbc.Driver");
    39. Connection connection = DriverManager.
    40. getConnection("jdbc:mysql://localhost:3306/jiayifeng", "root", "ASUS010519");
    41. String sql = "update t_user set nickname=? where id=?;";
    42. PreparedStatement preparedStatement = connection.prepareStatement(sql);
    43. preparedStatement.setObject(1,"三狗子");
    44. preparedStatement.setObject(2,"3");
    45. int rows = preparedStatement.executeUpdate();
    46. if(rows > 0){
    47. System.out.println("数据更新成功");
    48. }else {
    49. System.out.println("数据更新失败");
    50. }
    51. preparedStatement.close();
    52. connection.close();
    53. }
    54. @Test
    55. public void testDelete() throws ClassNotFoundException, SQLException {
    56. Class.forName("com.mysql.cj.jdbc.Driver");
    57. Connection connection = DriverManager.
    58. getConnection("jdbc:mysql://localhost:3306/jiayifeng", "root", "ASUS010519");
    59. String sql = "delete from t_user where id = ?;";
    60. PreparedStatement preparedStatement = connection.prepareStatement(sql);
    61. preparedStatement.setObject(1,"3");
    62. int rows = preparedStatement.executeUpdate();
    63. if(rows > 0){
    64. System.out.println("数据删除成功");
    65. }else {
    66. System.out.println("数据删除失败");
    67. }
    68. preparedStatement.close();
    69. connection.close();
    70. }
    71. }
            3.7:preparedStatement使用方式总结

            ①注册驱动

            ②获取连接

            ③编写SQL语句

            ④创建preparedStatement并且传入SQL语句结构

            ⑤占位符赋值

            ⑥发送SQL语句,并且获取结果

            ⑦结果集解析

            ⑧关闭资源

    四:全新JDBC扩展提升

            4.1:自增长主键回显实现,批量数据插入性能提升

            功能需求:

                    1.Java程序获取插入数据时,MySQL维护自增长维护的主键id值,这就是自增长

                    2.作用:在多表关联插入数据时,一般主表的主键都是自动生成的。所以在插入数据之前无法知道这条数据的主键,但是从表需要在插入数据之前就绑定主表的主键,这时可以使用主键回显技术

    1. package com.jiayifeng.preparedStatement;
    2. import org.junit.Test;
    3. import java.sql.*;
    4. /**
    5. * author 爱编程的小贾
    6. * create 2023-10-24 17:59
    7. *
    8. * TODO:
    9. * t_user插入一条数据!并且获取数据库自增长的主键
    10. *
    11. * TODO:
    12. * 总结:
    13. * ①创建prepareStatement的时候,告知,携带回数据库自增长的主键
    14. * ②获取司机装主键值的结果集对象,一行一列,获取对应的数据库即可
    15. *
    16. */
    17. public class PSOtherPart {
    18. @Test
    19. public void returnPrimaryKey() throws Exception {
    20. Class.forName("com.mysql.cj.jdbc.Driver");
    21. Connection connection = DriverManager.
    22. getConnection("jdbc:mysql://localhost:3306/jiayifeng", "root", "ASUS010519");
    23. String sql = "insert into t_user(account,password,nickname) values(?,?,?);";
    24. PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
    25. preparedStatement.setObject(1,"test");
    26. preparedStatement.setObject(2,"123456");
    27. preparedStatement.setObject(3,"狗蛋");
    28. int i = preparedStatement.executeUpdate();
    29. if(i > 0){
    30. System.out.println("数据插入成功");
    31. // 可以获取回显的主键
    32. // 获取司机装主键的结果集对象,一行 一列 id=值
    33. ResultSet resultSet = preparedStatement.getGeneratedKeys();
    34. resultSet.next();//移动下光标
    35. int id = resultSet.getInt(1);
    36. System.out.println("id = " + id);
    37. }else {
    38. System.out.println("数据插入失败");
    39. }
    40. preparedStatement.close();
    41. connection.close();
    42. }
    43. // 使用批量插入的方式插入10000条数据
    44. /*
    45. TODO:总结批量插入
    46. 1.路径后面添加?rewriteBatchedStatements=true 允许批量插入
    47. 2.insert into values [必须写] 语句不能添加;结束
    48. 3.不是执行语句每条,是批量添加addBatch();
    49. 4.遍历添加完毕以后,统一批量执行 executeBatch()
    50. */
    51. @Test
    52. public void testBatchInsert() throws ClassNotFoundException, SQLException {
    53. Class.forName("com.mysql.cj.jdbc.Driver");
    54. Connection connection = DriverManager.
    55. getConnection("jdbc:mysql://localhost:3306/jiayifeng?rewriteBatchedStatements=true", "root", "ASUS010519");
    56. String sql = "insert into t_user(account,password,nickname) values(?,?,?);";
    57. PreparedStatement preparedStatement = connection.prepareStatement(sql);
    58. long start = System.currentTimeMillis();
    59. for(int i = 0;i < 10000;i++){
    60. preparedStatement.setObject(1,"dd"+i);
    61. preparedStatement.setObject(2,"dd"+i);
    62. preparedStatement.setObject(3,"驴蛋蛋"+i);
    63. preparedStatement.addBatch();//不执行,追加到values后面
    64. }
    65. preparedStatement.executeBatch();//执行批量操作
    66. long end = System.currentTimeMillis();
    67. System.out.println("执行10000次数据插入消耗的时间:" + (end - start));
    68. preparedStatement.close();
    69. connection.close();
    70. }
    71. }

    五:常见错误及分析

            错误一:java.lang.ClassNotFoundException:com.mysql.cj.jdbc.Driver

                    原因:JDBC的依赖(jar包)没有导入

            错误二:java.sql.SQLException:Access denied for user 'root'@'localhost'(using password:YES)

                    原因:连接数据库密码错误

            错误三:java.sql.SQLSyntaxErrorException:Unknown database 'databasename'

                    原因:连接数据库找不到

            错误四:java.sql.SQLSyntaxErrorException:Table 'studentdb.users' doesn't exist

                    原因:数据库中找不到指定的表

    六:其它知识 

            ①com.mysql.cj.jdbc.Driver 这个类中只有静态代码块 注册了当前驱动

            ②为何注释了当前驱动代码仍然可以正常运行?

                    java6以后不需要显式的书写加载驱动内容

            ③MySQL5和MySQL8驱动连接数据库的区别?

                    mysql5的驱动:com.mysql.jdbc.Driver

                    mysql5的URL:jdbc:mysql://127.0.0.1:3306/databasename

            ④如果我们想要更换数据库,比如说更换Oracle/sql server

                    只需要更改:

                            A、驱动

                            B、类加载、url、用户名、密码即可

  • 相关阅读:
    AT_dp_c Vacation(dp)
    南怀瑾:“心静出贵人”,中年后这三个地方静,一切都会越来越顺!
    win10环境安装kettle&linux环境安装kettle
    论文文献管理工具——Zotero
    PTA 浙大版《C语言程序设计(第4版)》题目集 参考答案(函数题)
    【JVM技术专题】垃圾回收认知和调优的“思南(司南)“「下篇」
    MyCat的安装
    【最新计算机毕业设计】ssm基于微信小程序的高校新生报到系统
    沁恒CH32V103C8T6开发环境笔记
    ld: symbol(s) not found for architecture arm64
  • 原文地址:https://blog.csdn.net/weixin_63925896/article/details/133841371