什么是JDBC
Java数据库连接,(Java Database
Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
那就是是说Java定义了一套接口,而对应的数据库厂商需要实现这个接口(也就是所谓的驱动)。
简单理解:Java规定了访问数据库的API,数据库厂商去实现它
这个API就是java.sql
路径下的一系列接口,比如说
以mysql为例
java.sql.Driver
接口,实现就是com.mysql.jdbc.Driver
java.sql.Connection
接口,实现就是com.mysql.cj.jdbc.ConnectionImpl
java.sql.Statement
接口,实现就是com.mysql.cj.jdbc.StatementImpl
- …等等还有很多
下述来源于wiki百科
JDBC API主要位于JDK中的java.sql包中(之后扩展的内容位于javax.sql包中),主要包括(斜体代表接口,需驱动程序提供者来具体实现):
- DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。
- Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。
- Connection:数据库连接,负责进行与数据库间的通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。
- Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。
- PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。
- CallableStatement:用以调用数据库中的存储过程。
- SQLException:代表在数据库连接的建立和关闭和SQL语句的执行过程中发生了例外情况(即错误
使用方式
使用方式其实还是比较简单
- 加载对应数据库的驱动(
Driver
) - 通过
DriverManager
来获取连接(Connection
) - 创建对应语句(
Statement
,PreparedStatement
) - 接收执行结果(
ResultSet
)
对应代码:
Connection connection = null;
Statement statement = null;
try {
//步骤1:加载Driver
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&nullCatalogMeansCurrent=true";
String username = "root";
String password = "123456";
String sql = "select * from `test_info`";
//步骤2:通过`DriverManager`来获取连接(`Connection`)
connection = DriverManager.getConnection(url, username, password);
//步骤3:创建对应语句(`Statement`,`PreparedStatement`)
statement = connection.createStatement();
//步骤4:接收执行结果(`ResultSet`)
ResultSet resultSet = statement.executeQuery(sql);
} finally {
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}
首先,步骤1中将com.mysql.jdbc.Driver
加载到虚拟机中后,会执行一个静态代码块:
注意: 这个Driver是com.mysql.cj.jdbc.Driver
,上面的com.mysql.jdbc.Driver
继承于它。com.mysql.cj.jdbc.Driver
作用就是当它被加载的时候自动将自己注册到驱动管理器中。
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
}
}
步骤二中,通过DriverManager.getConnection(url, username, password)
来获取到连接Connection
。
问: 如果我同时加载Mysql的驱动和Oracle的驱动,那么
DriverManager
如何去区分使用哪一个驱动
答:通过url中的协议,比如说 jdbc:mysql://localhost:3306 ,在mysql中com.mysql.cj.conf.ConnectionUrl
来解析url判断是否是驱动可以处理。
轮训所有的Driver
,如果Driver
返回了Connection
就跳出循环返回,这就需要驱动厂商自己根据url来判断自己的驱动是否能支持该url对应的连接。
private static Connection getConnection(
...
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
...
}
以mysql为例,算了代码太长了,感兴趣的可以看下com.mysql.cj.jdbc.NonRegisteringDriver
第三步,需要创建一个Statement
或 PreparedStatement
来执行sql,它们的区别在于,Statement
执行的就是纯纯的sql语句,而PreparedStatement
代表着未完成的sql,也就是需要后续额外的数据来补充sql。
例子:
select * from test_info
,使用 Statement
select * from test_info where id = ?
,使用PreparedStatement
String sql = "select * from `test_info` where id=? and name=?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setLong(1, 1L);
preparedStatement.setString(2, "hahaha");
ResultSet resultSet = preparedStatement.executeQuery();
如果想看PreparedStatement
处理后的sql可以直接进行打印:
System.out.println("sql:" + preparedStatement);
mysql对于PreparedStatement
方法的实现是ClientPreparedStatement
,其中的toString
方法被重写了
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(this.getClass().getName());
buf.append(": ");
buf.append(((PreparedQuery) this.query).asSql());
return buf.toString();
}
第四步,接收数据库返回的结果并解析ResultSet
private static void printResultSet(ResultSet resultSet) throws SQLException {
while (resultSet.next()) {
ResultSetMetaData metaData = resultSet.getMetaData();
printMetaData(metaData, resultSet);
}
}
private static void printMetaData(ResultSetMetaData metaData, ResultSet resultSet) throws SQLException {
StringBuilder builder = new StringBuilder();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String type = metaData.getColumnTypeName(i);
builder.append("列名称:"+metaData.getColumnName(i)+" ");
builder.append("列类型:"+metaData.getColumnTypeName(i)+" ");
builder.append("数值:");
if (type.equals("BIGINT")) {
builder.append(resultSet.getLong(i));
} else if (type.equals("INT")) {
builder.append(resultSet.getInt(i));
} else if (type.equals("DATETIME")) {
builder.append(DateUtil.formatDateTime(resultSet.getDate(i)));
}
builder.append(" ");
builder.append("\n");
}
System.out.println(builder);
}
控制台的输出结果:
列名称:id 列类型:BIGINT 数值:1
列名称:test_enum 列类型:INT 数值:1
列名称:tenant_id 列类型:BIGINT 数值:4
列名称:test_two_enum 列类型:INT 数值:2
列名称:created_time 列类型:DATETIME 数值:2022-12-27 00:00:00
列名称:time 列类型:DATETIME 数值:null
对于事务的提交和级别的设置是在Connection
实例中
try {
//设置事务为不自动提交
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setLong(1, 1L);
ResultSet resultSet = preparedStatement.executeQuery();
//提交事务
connection.commit();
} catch (Exception ex){
if(connection!=null){
//回滚事务
connection.rollback();
}
}
隔离级别connection.setTransactionIsolation();
方法
mysql的使用枚举为org.apache.ibatis.session.TransactionIsolationLevel
ok,本次分享到这里就结束了,本章比较简单。
分享的原因是后续想在最近过年比较清闲的时间里面,将mybatis的前置知识先铺垫完,后续在好好整理下mybatis