java-JDBC的使用和手写框架

前言

不论是自己的小项目也好,还是日常使用也好,都离不开Java使用数据库,自己之前也封装过一些使用JDBC库,今天打算花点时间好好梳理下,顺便手写一个简单的ORM框架,并且之后打算深入研究下mybatis框架。

原生JDBC

JDBC(Java DataBase Connectivity)java数据库连接,简单来说就是java下面的数据库连接API(接口),我们知道天下数据库种类很多,sun公司(现在应该说是oracle公司)的程序员不可能为这么多的数据库提供实现类,他们既没有这个必要,而且他们也不知道数据库的具体实现细节,所以这一部分应该交给各家数据库自己去实现,Java只需要自己制作标准即可。也就是我们使用接口编写代码,实际上的实现类是由各个厂商提供的驱动。

废话不多说,这里先提供一个最简单的数据库连接demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Main {
public static final String HOST_IP = "xxx.xxx.xxx.xxx";
public static final int PORT = 3306;
public static final String USER = "root";
private static final String PASSWD = "mypassword";
public static final String DATABASE = "db";

public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://" + HOST_IP + ":" + PORT + "/" + DATABASE, USER, PASSWD);
String sql = "select * from Orders where order_num > 20007";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery(sql);
while (resultSet.next()) {
System.out.println(resultSet.getObject(1) + "\t" + resultSet.getObject(2));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {

if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

try {
if (null != connection) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

简单来说,有以下四个接口,分别是DriverManager(这个是类不是接口)、Connection、PreparedStatement(Statement)、ResultSet,中文也很好理解,分别叫驱动管理、连接、预处理语句和结果集,下面打算从源码角度来看看JDK是如何定义它们的。

DriverManager

​ 我们在实际使用中这个类就两个功能:注册驱动和获得数据库连接。你可能会觉得奇怪,明明代码里只有获取连接,哪里来的注册驱动呢?其实这个类下面是有一个叫registerDriver()的方法的。而且你可能还会觉得神奇的是,我明明只是加载了指定的驱动,但是似乎并没有用,这个到底是怎么做到的呢?要解开这个秘密,看下源码便知道了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.mysql.cj.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}

// 静态代码块,在类加载的时候会执行
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}

从上面的代码中可以看到,只要当这个类被加载的时候,它就会执行静态代码段里面的内容,就会调用DriverManager去注册数据库的驱动。PS:现在可以不用注册了(Java SPI)。

Connection

image-20200314145914717

利用IDEA可以很明显看出继承自两个接口:

  • 一个是AutoCloseable接口,也就是说可以支持下面的语法:
1
2
3
try (Connection conn = ......) {
// use resource here
}

​ 这样就会自动关闭。

  • 第二个是Wrapper这个接口,里面只有两个方法,但是我看不太懂,先暂时放一边。

最后是Connection本身,它自己有两块重要的功能,一个是创建执行sql语句的对象,另一个是事务相关的操作,这里挑选几个重要的方法:

  • createStatement() 创建一个Statement对象并且返回。
  • prepareStatement(String sql) 接受一个sql语句,并且将其预编译,返回一个PrepareStatement对象。
  • setAutoCommit(boolean autoCommit) 设置是否自动提交事务,默认是将一个sql语句都当成一个事务。所以传入false就相当于开启事务。
  • commit() 提交事务,当所有数据库执行语句完成之后使用。
  • rollback() 回滚,必须在自动提交是关闭的情况下才有效。一般会写在catch语句中。

Statement

其实在日常使用中几乎不怎么使用它,因为存在被SQL注入的风险,而一般使用PreparedStatement来代替它,但是它们俩其实在使用上几乎一样,所以拿它作为例子。它同样实现了AutoCloseable接口和Wrapper接口。下面是它的主要方法:

  • execute(String sql) 可以执行任何语句。一般不使用。

  • executeQuery(String sql) 执行一个查询的sql语句并且返回一个ResultSet对象。

  • executeUpdate(String sql) 可以执行增删改语句(即DML),还可以执行DDL语句,但是一般不用来执行DDL,返回值是影响的行数。

PreparedStatement

日常使用这个东西,使用预编译技术来防止sql注入。写sql语句的时候,参数使用占位符?代替,然后把sql语句代入到connection对象的preparedstatement方法中。然后将?赋值即可。

ResultSet

由字面意思可知,就是用来存放从数据库中查找到的结果集。接口和上面两位一样。

  • boolean next() 最开始的时候光标在第一行之前,执行这个方法会将光标移动一行。
  • 各类get方法,这里略过。

有了上面的知识梳理,对JDBC的操作进行一些简单的封装,相信是非常容易的事情。但是我们希望能够更进一步。因为Java是面向对象的,我们自然希望能够让Java中的一个对象,和数据库中查询出来一条记录进行匹配,也就是ORM(Object Relationship Mapping)

手写ORM

首先先来分析下,实现这个简单的框架,至少需要分成哪些部分。首先是数据库的连接部分

数据库连接部分

主流框架用的是XML进行配置,我这边为了简单起见,直接用文件读取,本质上两者都是一样的。

连接肯定需要有四个部分,分别是driver、url、username和password。