JDBC简介
JDBC,全程为Java Database Connectivity,即Java数据库连接。允许Java程序与关系型数据库进行交互。通过 JDBC,程序可以连接到数据库并执行SQL语句,实现对数据库的增、删、查、改等操作。
JDBC定义了统一的接口规范,而各个数据库厂商根据该规范提供具体的接口实现。这些实现类通常封装在JAR文件中,也就是常说的数据库驱动JAR包。
JDBC工作流程
JDBC的工作原理基于客户端/服务器模式。Java程序作为客户端,与数据库服务器建立连接,并通过JDBC API向数据库发送SQL请求。数据库根据SQL语句执行相关操作,返回结果到Java程序。开发者通过JDBC提供的API对这些操作进行处理。
JDBC与数据库交互的基本流程通常如下:
1.加载数据库驱动程序:在程序中加载对应数据库的JDBC驱动程序。
2.建立数据库连接:通过DriverManager或DataSource获取Connection对象。
3.创建Statement或PreparedStatement:通过Connection创建Statement或PreparedStatement来执行SQL语句。
4.执行SQL语句:通过Statement或PreparedStatement执行SQL语句操作数据库。
5.处理结果:如果执行查询操作,使用ResultSet获取结果并进行处理。
6.关闭资源:在操作完成后,关闭ResultSet、Statemen或PreparedStatement和Connection等资源,释放数据库连接。
JDBC核心组件
DriverManager
用于管理JDBC驱动的服务类,程序中主要使用该类来获取Connection对象,从而建立与数据库的连接。它负责加载并管理可用的数据库驱动,确保正确选择适合当前数据库的驱动程序。
常用函数:
// 注册驱动(JDK6之后无需再注册)
public static void registerDriver(java.sql.Driver driver)// 获取连接对象
public static Connection getConnection(String url,String user, String password)
Connection
Connection代表与数据库的连接对象,每个Connection实例对应一个物理数据库连接会话。在访问数据库之前,必须先获取Connection对象。Connection是 JDBC 操作的核心,提供了执行 SQL 语句和管理连接生命周期的功能。Connection还负责管理事务,提供了commit和rollback方法,用于提交事务和回滚事务。
常用函数:
// 返回一个Statement对象
Statement createStatement();// 返回预编译的Statement对象,即PreparedStatement对象
PreparedStatement prepareStatement(String sql);// 返回CallableStatement对象,用于调用存储过程
CallableStatement prepareCall(String sql);// 创建一个保存点,在事务中,保存点允许在事务中回滚到特定点,而不必回滚整个事务
Savepoint setSavepoint();// 以指定名字来创建一个保存点
Savepoint setSavepoint(String name);// 设置事务的隔离级别
// 隔离级别如下
// Connection.TRANSACTION_READ_UNCOMMITTED:允许脏读(最低的隔离级别)
// Connection.TRANSACTION_READ_COMMITTED:不允许脏读,但允许不可重复读
// Connection.TRANSACTION_REPEATABLE_READ:不允许脏读和不可重复读,但可能会发生幻读
// Connection.TRANSACTION_SERIALIZABLE:最高的隔离级别,完全防止脏读、不可重复读和幻读
void setTransactionIsolation(int level);// 事务回滚
void rollback();// 事务回滚到指定的保存点
void rollback(Savepoint savepoint);// 设置自动提交模式
// true:启用自动提交模式,意味着每个SQL语句都将在执行后自动提交
// false:禁用自动提交模式,事务的提交需要手动调用commit(),事务的回滚需要手动调用rollback()
void setAutoCommit(boolean autoCommit);// 提交事务
void commit();
Statement
Statement通过Connection获取,用于执行SQL语句并与数据库进行交互。然而,由于SQL语句通常是直接拼接在代码中的,如果未对用户输入进行充分的校验和处理,容易导致SQL注入攻击的风险。此外,Statement每次执行SQL时都需要重新编译语句,因此在性能上相对较低。
常用函数:
// 执行SQL查询语句,并返回查询结果ResultSet
ResultSet executeQuery(String sql);// 执行DML,返回受影响的行数,也可以用于执行DDL,执行DDL返回的结果是0
int executeUpdate(String sql);// 可执行任何SQL,如果执行后第一个结果为ResultSet,则返回true,如果执行后第一个结果为受影响的行数或者没有任何结果,则返回false
boolean execute(String sql);// 获取当前Statement对象所允许的最大字段大小。字段大小是指单个字段可以存储的最大字符数(对于字符串类型的字段)
int getMaxFieldSize();// 允许设置Statement对象所允许的最大字段大小。这个方法的目的是限制查询结果中每个字段的最大字节数。这可以防止返回非常大的字段(如大型文本或二进制数据)对内存和性能产生负面影响,如果设置为0,表示没有限制
void setMaxFieldSize(int max);// 获取当前Statement对象的最大行数限制。
int getMaxRows();// 设置Statement对象查询结果的最大行数限制,如果设置为0,则表示没有限制。
void setMaxRows(int max);// 获取当前Statement对象的查询超时时间。
int getQueryTimeout();// 设置Statement对象的查询超时时间,设置为0表示没有超时限制。
void setQueryTimeout(int seconds);// 取消当前正在执行的SQL查询,在执行长时间运行的查询时,可以调用此方法来取消查询。
void cancel();// 将多个SQL语句一起提交到数据库执行,通常用于批量插入、更新或删除。
void addBatch(String sql);// 在执行批处理之前或之后,清空已经添加到批处理中的SQL语句,从而释放内存或重置批处理状态。
void clearBatch();// 执行批处理中的所有SQL语句,并返回一个整数数组,每个元素表示相应SQL语句影响的行数。
int[] executeBatch();
PreparedStatement
PreparedStatement是Statement的一个扩展,同样通过Connection获取。与Statement不同,PreparedStatement在执行SQL语句之前会先对其进行预编译,并将编译后的SQL语句进行缓存,这样可以显著提高执行效率。通过预编译,数据库能够复用相同的SQL语句,避免每次执行时都需要重新解析和编译,从而减少了性能开销。另外,PreparedStatement支持参数化查询,即通过占位符(?
)传递查询参数,而不是将参数直接拼接进SQL语句中。这样可以有效防止SQL注入攻击,确保应用程序的安全性。总体来说,PreparedStatement比Statement效率更高、更安全。
常用函数:
// 执行SQL查询语句,并返回查询结果ResultSet
ResultSet executeQuery();// 执行更新SQL语句并返回更新的行数。
int executeUpdate()// 可执行任何SQL,如果执行后第一个结果为ResultSet,则返回true,如果执行后第一个结果为受影响的行数或者没有任何结果,则返回false
boolean execute();// 设置指定参数为NULL,parameterIndex表示参数位置,sqlType表示SQL类型(例如Types.INTEGER、Types.VARCHAR等)。
void setNull(int parameterIndex, int sqlType);// 设置指定参数为布尔值x
void setBoolean(int parameterIndex, boolean x);// 设置指定参数为字节值x
void setByte(int parameterIndex, byte x);// 设置指定参数为短整型值x
void setShort(int parameterIndex, short x);// 设置指定参数为整型值x
void setInt(int parameterIndex, int x);// 设置指定参数为长整型值x
void setLong(int parameterIndex, long x);// 设置指定参数为浮点值x
void setFloat(int parameterIndex, float x);// 设置指定参数为双精度浮点值x
void setDouble(int parameterIndex, double x);// 设置指定参数为BigDecimal类型的值x,通常用于存储高精度的数值。
void setBigDecimal(int parameterIndex, BigDecimal x);// 设置指定参数为字符串值x
void setString(int parameterIndex, String x);// 设置指定参数为字节数组x
void setBytes(int parameterIndex, byte x[]);// 设置指定参数为java.sql.Date类型的日期值x
void setDate(int parameterIndex, java.sql.Date x);// 设置指定参数为java.sql.Time类型的日期值x
void setTime(int parameterIndex, java.sql.Time x);// 设置指定参数为java.sql.Timestamp类型的日期值x
void setTimestamp(int parameterIndex, java.sql.Timestamp x);// 设置指定参数为ASCII流输入流x,length表示输入流的长度。
void setAsciiStream(int parameterIndex, java.io.InputStream x, int length);// 设置指定参数为二进制流输入流x,length表示输入流的长度,可能抛出SQLException。
void setBinaryStream(int parameterIndex, java.io.InputStream x, int length) throws SQLException;// 清除当前PreparedStatement中所有已设置的参数,重置参数状态。
void clearParameters();// 设置指定参数为Object类型的值x
void setObject(int parameterIndex, Object x);// 设置指定参数为Object类型的值x,并明确指定目标SQL类型
void setObject(int parameterIndex, Object x, int targetSqlType);// 将当前的SQL操作添加到批处理中,用于执行批量更新。
void addBatch();// 返回当前PreparedStatement的元数据,提供关于查询结果的描述,如列数、列名、列类型等信息。
ResultSetMetaData getMetaData();// 返回当前PreparedStatement的参数元数据,提供关于参数的信息,如参数数量、类型等。
ParameterMetaData getParameterMetaData();// 执行一个批量更新或大规模更新操作,并返回受影响的行数,返回的是long类型,适用于大数据量操作。
long executeLargeUpdate();
ResultSet
结果集对象,代表了SQL查询执行后的结果集。通过ResultSet对象,开发人员可以逐行读取和处理数据库查询返回的数据。ResultSet的作用类似于一个指向数据库结果集的游标,可以通过它检索到查询结果中的每一行数据。
常用函数:
// 将ResultSet光标移动到下一个数据行,如果有数据行返回true,若无则返回false,用于遍历查询结果集中的每一行
boolean next();// 返回上一个从ResultSet获取的列值是否为NULL。如果是NULL,返回true;否则返回false。
boolean wasNull();// 提供结果集的元数据,获取查询结果的列数、列名、列类型等信息
ResultSetMetaData getMetaData();// 根据列名返回列的索引(从1开始)
int findColumn(String columnLabel);// 判断当前光标是否位于结果集的第一行之前
boolean isBeforeFirst();// 判断当前光标是否位于结果集的最后一行之后
boolean isAfterLast();// 判断当前光标是否位于结果集的第一行
boolean isFirst();// 判断当前光标是否位于结果集的最后一行
boolean isLast();// 将光标移到结果集的第一行之前
void beforeFirst();// 将光标移到结果集的最后一行之后
void afterLast();// 将光标移到结果集的第一行,如果结果集至少包含一行数据,则返回true;否则返回false
boolean first();// 将光标移到结果集的最后一行,如果结果集至少包含一行数据,则返回true;否则返回false
boolean last();// 返回当前行的行号,如果游标在第一行,返回1;如果在结果集之前或之后,返回0。
int getRow();// 将光标直接移动到指定的行,如果移动成功,返回true;如果指定行不存在,返回false。
boolean absolute(int row);// 将光标相对当前位置移动指定的行数,用于相对当前位置移动光标,正值表示向后移动,负值表示向前移动
boolean relative(int rows);// 将光标移到前一行
boolean previous();// 设置每次从数据库中拉取的行数。用于优化大数据集的查询
void setFetchSize(int rows);// 返回每次查询时从数据库中获取的行数
int getFetchSize();// 使用索引或者字段名称
// 获取当前行指定列的字符串值
String getString(int columnIndex);
String getString(String columnLabel);// 获取当前行指定列的短整型值
short getShort(int columnIndex);
short getShort(String columnLabel);// 获取当前行指定列的整数值
int getInt(int columnIndex);
int getInt(String columnLabel);// 获取当前行指定列的长整型值
long getLong(int columnIndex);
long getLong(String columnLabel);// 获取当前行指定列的浮点型值
float getFloat(int columnIndex);
float getFloat(String columnLabel);// 获取当前行指定列的双精度浮点型值
double getDouble(int columnIndex);
double getDouble(String columnLabel);// 获取当前行指定列的字节数组值
byte[] getBytes(int columnIndex);
byte[] getBytes(String columnLabel);// 获取当前行指定列的Date类型值
java.sql.Date getDate(int columnIndex);
java.sql.Date getDate(String columnLabel);// 获取当前行指定列的Time类型值
java.sql.Time getTime(int columnIndex);
java.sql.Time getTime(String columnLabel);// 获取当前行指定列的Timestamp类型值
java.sql.Timestamp getTimestamp(int columnIndex);
java.sql.Timestamp getTimestamp(String columnLabel);// 获取当前行指定列的ASCII流类型值。
java.io.InputStream getAsciiStream(int columnIndex);
java.io.InputStream getAsciiStream(String columnLabel);// 获取当前行指定列的二进制流类型值
java.io.InputStream getBinaryStream(int columnIndex);
java.io.InputStream getBinaryStream(String columnLabel);// 获取当前行指定列的对象值,返回通用的Object类型
Object getObject(int columnIndex);
Object getObject(String columnLabel);
CallableStatemen
CallableStatemen也通过Connection获取,主要用于执行存储过程和函数,主要用途是调用数据库中的存储过程或函数,并处理输入参数、输出参数和返回值。使用CallableStatemen可以通过 JDBC 调用预定义的存储过程,支持通过IN、OUT、INOUT参数传递数据。
常用函数:
// 注册输出参数,指定该参数的位置(通过索引)和SQL数据类型
void registerOutParameter(int parameterIndex, int sqlType);// 注册输出参数,并指定该参数的类型和精度。它主要用于数字类型(如DECIMAL或NUMERIC),可以指定小数点后的精度(scale)
void registerOutParameter(int parameterIndex, int sqlType, int scale);// 用于注册输出参数,并指定参数的SQL类型以及类型名称。通常用于自定义类型或数据库特定类型
void registerOutParameter(int parameterIndex, int sqlType, String typeName);// 通过参数名称来注册输出参数,允许根据存储过程中的参数名称而不是索引来访问输出参数。
void registerOutParameter(String parameterName, int sqlType);// 通过参数名称来注册输出参数,并为数字类型指定精度
void registerOutParameter(String parameterName, int sqlType, int scale);// 于通过参数名称来注册输出参数,并指定SQL类型和类型名称
void registerOutParameter(String parameterName, int sqlType, String typeName);// setInt,setString,这些与PreparedStatement类似
// getInt,getString,这些与ResultSet类似
JDBC编程样例
查询(Statement)
public static void main(String[] args) throws Exception {// 1.注册驱动(JDK6之后无需再注册)// Class.forName("com.mysql.cj.jdbc.Driver");// DriverManager.registerDriver(new Driver());// 2.获取连接对象String url = "jdbc:mysql://localhost:3306/databases";String username = "root";String password = "1";Connection connection = DriverManager.getConnection(url, username, password);// 3.获取执行SQL语句的对象Statement statement = connection.createStatement();// 4.编写SQL语句,并执行,接受返回的结果集String sql = "select id,name,age from t_table";ResultSet resultSet = statement.executeQuery(sql);// 5.处理结果:遍历resultSet结果集while (resultSet.next()) {int id = resultSet.getInt("id");String name = resultSet.getString("name");int age = resultSet.getDouble("age");System.out.println(id + "\t" + name + "\t" + age);}// 6.释放资源(先开后关原则)resultSet.close();statement.close();connection.close();}
查询(PreparedStatement)
public static void main(String[] args) throws Exception {// 1.注册驱动(JDK6之后无需再注册)// Class.forName("com.mysql.cj.jdbc.Driver");// DriverManager.registerDriver(new Driver());//2.获取连接对象String url = "jdbc:mysql://localhost:3306/databases";String username = "root";String password = "1";Connection connection = DriverManager.getConnection(url, username, password);//3.获取执行SQL语句对象PreparedStatement preparedStatement = connection.prepareStatement("select id,name,age from t_table where name = ?");//4.为?占位符复制,并执行SQL语句,接受返回的结果preparedStatement.setString(1, name);ResultSet resultSet = preparedStatement.executeQuery();//5.处理结果:遍历resultSetwhile (resultSet.next()) {int id = resultSet.getInt("id");String name = resultSet.getString("name");int age = resultSet.getInt("age");System.out.println(id + "\t" + name + "\t" + age);}//6.释放资源resultSet.close();preparedStatement.close();connection.close();}
修改
public static void main(String[] args) throws Exception {// 1.注册驱动(JDK6之后无需再注册)// Class.forName("com.mysql.cj.jdbc.Driver");// DriverManager.registerDriver(new Driver());//2.获取连接对象String url = "jdbc:mysql://localhost:3306/databases";String username = "root";String password = "1";Connection connection = DriverManager.getConnection(url, username, password);//3.获取执行SQL语句对象PreparedStatement preparedStatement = connection.prepareStatement("update t_table set name = ? where id = ?");//4.为?占位符复制,并执行SQL语句,接受返回的结果preparedStatement.setString(1, "a");preparedStatement.setInt(2, 6);//5.执行,根据受影响行数判断执行结果int result = preparedStatement.executeUpdate();if (result > 0) {System.out.println("成功!");} else {System.out.println("失败!");}//6.释放资源preparedStatement.close();connection.close();}
增加
public static void main(String[] args) throws Exception {// 1.注册驱动(JDK6之后无需再注册)// Class.forName("com.mysql.cj.jdbc.Driver");// DriverManager.registerDriver(new Driver());//2.获取连接对象String url = "jdbc:mysql://localhost:3306/databases";String username = "root";String password = "1";Connection connection = DriverManager.getConnection(url, username, password);//3.获取执行SQL语句对象PreparedStatement preparedStatement = connection.prepareStatement("insert into t_table(id,name,age) values (?,?,?)");//4.为?占位符复制,并执行SQL语句,接受返回的结果preparedStatement.setInt(1, 10);preparedStatement.setString(2, "b");preparedStatement.setInt(3, 20);//5.执行,根据受影响行数判断执行结果int result = preparedStatement.executeUpdate();if (result > 0) {System.out.println("成功!");} else {System.out.println("失败!");}//6.释放资源preparedStatement.close();connection.close();}
删除
public static void main(String[] args) throws Exception {// 1.注册驱动(JDK6之后无需再注册)// Class.forName("com.mysql.cj.jdbc.Driver");// DriverManager.registerDriver(new Driver());//2.获取连接对象String url = "jdbc:mysql://localhost:3306/databases";String username = "root";String password = "1";Connection connection = DriverManager.getConnection(url, username, password);//3.获取执行SQL语句对象PreparedStatement preparedStatement = connection.prepareStatement("delete from t_table where id = ?");//4.为?占位符复制,并执行SQL语句,接受返回的结果preparedStatement.setDouble(1, 6);//5.执行,根据受影响行数判断执行结果int result = preparedStatement.executeUpdate();if (result > 0) {System.out.println("成功!");} else {System.out.println("失败!");}//6.释放资源preparedStatement.close();connection.close();}
调用存储过程(返回查询结果集)
存储过程示例(MySQL):
create procedure get_age(in id int, in name varchar(100))
beginselect id, name, age from t_table a where a.id = id and a.name = name;
end;
Java程序示例:
public static void main(String[] args) throws SQLException {// 1.注册驱动(JDK6之后无需再注册)// Class.forName("com.mysql.cj.jdbc.Driver");// DriverManager.registerDriver(new Driver());//2.获取连接对象String url = "jdbc:mysql://localhost:3306/databases";String username = "root";String password = "1";Connection connection = DriverManager.getConnection(url, username, password);//3.调用存储过程CallableStatement callableStatement = connection.prepareCall("{call get_age(?,?)}");//4.设置存储过程参数callableStatement.setInt(1, 1);callableStatement.setString(2, "root");//5.处理结果:遍历resultSetResultSet resultSet = callableStatement.executeQuery();//6.获取结果集元数据ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();//7.遍历所有列while (resultSet.next()) {for (int i = 1; i <= columnCount; i++) {// 获取列名和列值String columnName = metaData.getColumnName(i);Object columnValue = resultSet.getObject(i);System.out.println(columnName + ": " + columnValue);}}//8.释放资源resultSet.close();callableStatement.close();connection.close();}
调用存储过程(返回结果值)
存储过程示例(MySQL):
create procedure get_age(in id int, in name varchar(100),out age int)
beginselect a.age into age from t_table a where a.id = id and a.name = name;
end;
Java程序示例:
public static void main(String[] args) throws SQLException {// 1.注册驱动(JDK6之后无需再注册)// Class.forName("com.mysql.cj.jdbc.Driver");// DriverManager.registerDriver(new Driver());//2.获取连接对象String url = "jdbc:mysql://localhost:3306/databases";String username = "root";String password = "1";Connection connection = DriverManager.getConnection(url, username, password);//3.调用存储过程CallableStatement callableStatement = connection.prepareCall("{call get_age(?,?,?)}");//4.设置存储过程参数callableStatement.setInt(1, 1);callableStatement.setString(2, "root");//5.注册输出参数callableStatement.registerOutParameter(3, java.sql.Types.INTEGER);//6.执行,并获取结果callableStatement.execute();System.out.println(callableStatement.getInt(3));//7.释放资源callableStatement.close();connection.close();}
JDBC的优缺点
优点:
1.JDBC作为Java的一部分,具有Java平台的跨平台特性,支持所有Java能够运行的平台。
2.JDBC提供了一套统一的接口,应用程序与数据库之间的耦合度低,通过不同的数据库驱动,可以轻松切换数据库。
3.JDBC不需要引入复杂的配置文件或额外的框架,它是轻量级的并且直接集成在Java标准库中,适合快速开发和小规模应用。
缺点:
1.JDBC要求开发者手动管理数据库连接、Statement、ResultSet等资源。每次操作完数据库后,都需要显式地关闭这些资源。如果未能及时关闭这些资源,可能会导致内存泄漏或连接泄漏问题。
2.JDBC本身仅提供基本的数据库连接和操作功能,没有ORM框架那样的高级抽象功能。开发者需要自己编写代码来处理数据库和对象之间的转换(如将结果集映射成Java对象)
3.JDBC本身不提供缓存机制,每次查询数据库时都会重新执行SQL语句,无法像一些ORM框架那样通过缓存机制提高查询效率。