Java/后端

Java的sql相关

讲述了java中的sql链接相关的初步内容。

从 ArrayList 到 MySQL:第一个 Java 持久化小项目

这次做的是一个很简单的图书管理系统。第一版只把数据放在 ArrayList 里,程序一关数据就没了。后来我新建了一个 Maven 项目,用 JDBC 把它和本地 MySQL 连接起来,真正完成了增删改查和数据持久化。

虽然项目很小,但这次算是第一次把 Java、SQL、Maven 和分层结构串在了一起。

一、项目结构

现在项目大致分成这几层:

App

UI

BookService

BookDao

JDBC

MySQL

对应的职责是:

Book          保存图书数据
UI            接收输入、显示菜单和结果
BookService   做业务判断
BookDao       执行 SQL
DBConnection  创建数据库连接
App           程序入口

这比把所有代码都写在 main 方法里清楚很多。

二、Maven 是做什么的

之前的普通 Java 项目,依赖基本靠自己处理。换成 Maven 项目后,项目的依赖写在 pom.xml 里。

这次用到的 MySQL 驱动就是通过 Maven 引入的:

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.4.0</version>
</dependency>

以后别人拿到项目,只要有 Maven,就可以根据 pom.xml 下载依赖并编译项目。

常用命令:

mvn clean compile
mvn test

第一次执行 Maven 时下载很多文件是正常的。Maven 会把它们缓存到本地仓库,之后一般不会重复下载。

三、Java 怎么连接 MySQL

我在 DBConnection 中保存了数据库连接信息:

private static final String URL =
        "jdbc:mysql://localhost:3306/training"
        + "?useSSL=false"
        + "&allowPublicKeyRetrieval=true"
        + "&serverTimezone=Asia/Shanghai";

这里的几个部分分别是:

localhost       本机
3306            MySQL 默认端口
training        数据库名
useSSL=false    本地练习时关闭 SSL
serverTimezone  设置时区

allowPublicKeyRetrieval=true 是为了解决 MySQL 8 连接时出现的:

Public Key Retrieval is not allowed

本地练习可以这样配置,正式项目不能把数据库密码直接写死在代码里,也不能把密码提交到公开仓库。

真正创建连接的代码是:

DriverManager.getConnection(URL, USERNAME, PASSWORD);

四、JDBC 里最重要的三个对象

1. Connection

Connection 表示 Java 和数据库之间的一条连接。

Connection conn = DBConnection.getConnection();

2. PreparedStatement

PreparedStatement 用来执行带参数的 SQL。

String sql = "select id, name, author from books where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, id);

SQL 里的 ? 是占位符,setInt(1, id) 会把第一个 ? 设置成 id

不能直接拼接字符串:

// 不推荐
"select * from books where id = " + id

使用 PreparedStatement 更安全,也更符合实际项目的写法。

3. ResultSet

ResultSet 保存 SELECT 查询出来的结果。

try (ResultSet rs = ps.executeQuery()) {
    if (rs.next()) {
        String name = rs.getString("name");
        int id = rs.getInt("id");
    }
}

rs.next() 可以理解为“移动到下一行”。第一次调用时,就是移动到第一条查询结果。

五、try-with-resources

数据库连接、SQL 执行对象和查询结果都需要关闭。以前可能要手动调用 close(),现在可以这样写:

try (Connection conn = DBConnection.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql)) {

    // 执行 SQL
}

代码块执行完后,Java 会自动关闭这些资源。这样可以减少忘记关闭连接的问题。

六、DAO 负责和数据库说话

BookDao 只负责 SQL,不负责菜单,也不负责决定业务规则。

图书管理系统的 CRUD 和 SQL 大致对应:

Java 操作SQL
添加图书INSERT
查询图书SELECT
修改图书UPDATE
删除图书DELETE

例如添加图书:

String sql = "insert into books (id, name, author) values (?, ?, ?)";

ps.setInt(1, book.getId());
ps.setString(2, book.getName());
ps.setString(3, book.getAuthor());

return ps.executeUpdate() > 0;

executeUpdate() 返回受影响的行数。大于 0,通常就表示添加、修改或删除成功。

七、数据库的一行如何变成 Java 对象

查询结果不是 Book 对象,而是数据库的一行数据,所以我写了一个 mapRow 方法:

private Book mapRow(ResultSet rs) throws SQLException {
    return new Book(
            rs.getString("name"),
            rs.getInt("id"),
            rs.getString("author")
    );
}

它做的事情很直接:

数据库 id     -> Book.id
数据库 name   -> Book.name
数据库 author  -> Book.author

以后使用 Spring、MyBatis 之类的框架时,框架会帮忙做很多类似的映射,但理解这个过程还是很重要的。

八、Service 和 DAO 的区别

BookDao 负责执行:

select
insert
update
delete

BookService 负责做判断,例如:

  • ID 是否大于 0
  • 书名和作者是否为空
  • 添加时 ID 是否已经存在
  • 修改时图书是否存在
  • 修改时没有填写的字段是否保留原值

例如修改图书时,用户可能只输入新书名,没有输入新作者。BookService 会先查询旧数据,再把新旧数据合并:

String name = patch.getName() == null
        ? current.getName()
        : patch.getName();

String author = patch.getAuthor() == null
        ? current.getAuthor()
        : patch.getAuthor();

这样就不会因为用户没有输入作者,把数据库中的作者更新成 NULL

这也是我这次真正理解到的一点:

DAO 负责“怎么操作数据库”,Service 负责“什么情况下允许操作”。

九、从内存版到数据库版,变化在哪里

内存版的核心是:

private List<Book> bookList;
private Map<Integer, Integer> bookIndex;

数据放在 Java 进程里,程序结束后就消失。

数据库版不再用 ArrayList 保存主数据,而是:

Java 对象

BookDao

SQL

MySQL 表

程序重新启动后,SELECT 仍然可以从 MySQL 中读出之前的数据,这就是持久化。

十、这次遇到的几个问题

Maven 第一次运行下载很多文件

这是正常的。下载的是 Maven 插件、项目模板和依赖,不是 Maven 没有安装。

项目默认使用 Java 7

Maven 的旧模板默认使用 Java 7,需要在 pom.xml 中改成:

<maven.compiler.release>17</maven.compiler.release>

电脑安装的 JDK 版本和项目要求使用的 Java 版本是两件事,都需要确认。

MySQL 报公钥错误

DBeaver 和 Java JDBC 连接都需要允许公钥获取:

allowPublicKeyRetrieval=true

数据库连接失败时仍然进入菜单

后来在 App 中加了 return:连接失败就直接结束程序,不再启动后面的 UI。

十一、这次练习的结果

这个项目虽然只是图书管理系统,但我实际走完了这样一条链路:

Java 对象
→ Service 业务逻辑
→ DAO
→ PreparedStatement
→ MySQL
→ ResultSet
→ Java 对象

我也第一次清楚地看到,数据库不是“另外学一门东西”,而是 Java 后端程序保存和处理业务数据的一部分。

下一步可以开始学习 Spring Boot。不过正式的设备资产与工单项目会重新设计,不会直接把这个图书管理系统复制过去。