JDBC
对以前学习 JDBC 的一个重温。
顾名思义就是 Java 数据库连接,用于访问各种数据库的一个 API,由于各大数据库厂商的源码不同,所以 Java 只提供了接口,具体实现就交由各大数据库厂商实现,我们使用只需要引入对于数据库版本的 Jar 包即可实现对其数据库的操作。
核心接口
- Connection:特定数据库的连接(一次会话)。
- PreparedStatement:表示预编译的 SQL 语句对象。
- Statement:用于执行静态 SQL 语句并返回结果的对象。
- ResultSet:表示数据库结果集的数据表,通常执行查询操作的语句生成的结果集。
- Callable Statement:用于执行 SQL 存储过程的接口。
核心类
-
DriverManager:数据库驱动管理类
-
DataSource:数据源,也称数据库连接池。
步骤
1.加载驱动,并获取 Connection
注册驱动:
Class.forName("com.mysql.cj.jdbc.Driver");// 需要处理异常。
以前学习的时候并不知道驱动是怎么加载的,实际上是自己对 Class.forName(); 这个方法不了解导致。
Class.forName(xxx.xx.xx)返回的是一个类。
Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。
在此处我们主要是第二个作用,也就是要加载数据库驱动过程(由数据库厂商提供)。
具体代码如下:
可以看到就一个空构造和一个静态代码块,静态代码块在类加载时就会执行,也就是注册 DriverManager 的过程。
主要是 DrivarManager.registerDriver(Driver driver); 这个方法的意义是注册给定的驱动程序 DriverManager。那么当我们成功的注册了驱动程序,就会被加载到 registeredDrivers 列表中。等待获取具体的驱动程序。
简单来说就是提供过 Class.forName(),注册驱动 Driver 到 registeredDrivers 列表中,被 DriverManager 所统一管理。
在一开始,我们需要通过反射去创建 Driver,再将 Driver 注册到 DriverManager。后面只需要通过反射加载 Driver,即可完成驱动程序的注册。后面 MySQL 提供的 5.1 以后不再需要我们反射加载 Driver,配置文件包含了 Driver 的路径,使用 JDBC 会自动加载。
上面是典型的C/S两层架构,在频繁创建和关闭时都会比较消耗资源(一个 DriverManager 获取的连接均是一个物理连接。),而且还有其他弊端,在此基础上有提出三层架构,多了一个 DataSource(数据库连接池)。
DataSource:
同时它也是个接口,由第三方提供实现,包含连接池和连接池管理,统称数据库连接池。
其使用场景在于频繁访问数据库,分布式事务等应用场景。
通过对连接的复用,关闭资源的策略,降低在上诉场景所需要的系统资源。
获取 connection 连接实例:
Connection connection = DriverManager.getConnection(String url,String user,String pwd);
2.创建 SQL 语句,通过 Statement、PreparedStatement 发送 SQL 语句到数据库服务器,执行并返回结果
我们拿到 connection 之后,需要通过 Statement、PreparedStatement,发送 SQL 语句,交由数据库执行。
Statement
用于执行静态的 SQL 语句,即最后传入的SQL语句一定是 String 类型且已知具体内容。容易遭受恶意 SQL 注入,安全性不高。
PreparedStatement
预编译 SQL 语句对象,SQL 语句预编译并存储在 PreparedStatement 对象中,可以多次频繁使用,节省资源,提高效率,并且内部提供了防止 SQL 注入的一些策略,能有效地防止 SQL 注入,提高安全性。
在生产环境中带有参数的 SQL 语句就尽可能使用 PreparedStatement,提高安全性。
Statement 主要执行不带参数的 SQL 语句。因为创建两者时 Statement 所用资源少于后者,即使消耗资源多一点,为了安全性,可以不考虑消耗问题。
PreparedStatement 预编译:
String sql = "insert into student values(null,?,?,?)";
PreparedStatement pStmt = conn.prepareStatement(sql);// 当执行时会先给数据库进行预编译。
pstmt.setString(1,var1);
pstmt.setString(2,var2);
pstmt.setString(3,var3);
int a = pStmt.executeUpdate();// 当执行时直接执行预编译的 SQL 语句。
意思是先将 SQL 发送给数据库进行预编译,成功后,当执行调用 executeUpdate() 方法时,数据库直接引用? 的参数并且执行 SQL 语句。
以前还有一个疑问,就是 PreparedStatement 如果执行一次后关闭了,下次在执行相同的 SQL 语句获得的 PreparedStatement 对象还需要进行预编译吗?
这种情况下,是需要的,虽然时相同的 Connection 与 SQL 语句,但是是不同的 PreparedStatement 实例对象,所以会再次编译。
所以需要缓存 PreparedStatement 实例,URL 中设置cachePrepStmts=true即可。**这样就是根据 SQL 模板来缓存 PreparedStatement 实例,下次获取就根据 SQL 模板是否一致,如果一致就将缓存的实例引用给予新对象,否则就创建新对象进行预编译。 **
使用 useServerPrepStmts=true 开启预编译(默认关闭)。
使用 prepStmtCacheSize 变量来设置缓存PreparedStatement的上限。
例如 prepStmtCacheSqlLimit 变量来设置SQL模板的长度上限。
3.处理返回结果
如果是查询语句就会返回 ResultSet 结果集对象。查询的语句就都在对象中了。
String sql = "xxxx";
PreparedStatement pStmt = connection.prepareStatement(sql);
ResultSet resultSet = pStmt.executeQuery();// 获取结果集对象
while (resultSet.next()){
int id = resultSet.getInt("id");
int no = resultSet.getInt("no");
String eName = resultSet.getString("ename");
double score = resultSet.getDouble("score");
String email = resultSet.getString("email");
// ... 具体业务操作
}
resultSet.close();
一般业务中,都是通过 ResultSet 去获取查询的数据,并且封装到实体类中以供后续使用。
但是 ResultSet 有一些特性:
- 是否可滚动
- 不可滚动:默认情况下,游标只能从上至下读取数据(只能调用 next() 移动游标)。
- 可滚动:设置可滚动,可以通过其他方法移动游标。
- 是否敏感
- 敏感:数据库更新数据,ResultSet 数据也更新(几乎没有数据库厂商实现)
- 不敏感:数据库更新不影响 ResultSet 的数据(默认情况下)。
- 是否可更新
- 可更新:ResultSet 更新影响数据库。
- 不可更新:ResultSet 更新不会影响数据库(默认情况下)。
在 Connection.createStatement 中设置。
同时 PrepareStatement 也可以实现。
具体使用看业务场景。
4.关闭连接
try {
if (statement != null)
statement.close(); // 关闭资源
} catch (Exception e) {
e.printStackTrace();
}
try {
if (connection != null)
getPoolSize();
pool.returnConnection(connection); // 将连接放回连接池,也可以直接 connection.close() 关闭。
getPoolSize();
} catch (Exception e) {
e.printStackTrace();
}
小结:其实最后我们常用的都是数据库连接池来帮助我们管理数据库连接实例,保证其安全性,可用性,高效率。接下来就是实现一个简易功能的连接池,了解其中基本核心的原理。