标题目录
JDBC
基本概念
- Java Database Connectivity:java访问数据库的解决方案
- 希望用相同的方式访问不同的数据库,以实现与具体数据库无关的java操作界面
- JDBC定义一套标准接口,即访问数据库的通用API,不同的数据库厂商根据各自数据库的特点去实现这些接口
JDBC 接口
- 驱动管理器
- DriverManager
- 连接对象
- Connection
- DatabaseMetaData
- SQL执行对象
- Statement
- PreparedStatement
- CallableStatement
- 查询结果集对象
- ResultSet
- ResultSetMetaData
JDBC 工作原理
- JDBC定义接口
- 数据库厂商实现接口
- 程序员调用接口,实际调用的是底层数据库厂商的实现部分
JDBC API
JDBC工作过程
- 创建驱动,建立连接
- 创建语句执行对象
- 执行SQL语句
- 处理结果集
- 关闭连接
Driver 接口及驱动加载
- 加载驱动类Driver
- Driver是JDBC定义的接口,不同的数据库都提供了对应的实现类
- 使用Class.forName(“驱动完全限定类”)方式加载驱动
- 不同的数据库驱动类的完全限定名不同
- 常见的数据库Driver实现类完全限定名
- ORACLE:“oracle.jdbc.driver.OracleDriver”
- MySQL:“com.mysql.cj.jdbc.Driver”
Class.forName("com.mysql.cj.jdbc.Driver");
Connection 接口
- 使用驱动管理器与数据库建立连接
- DriverManager是JDBC提供的类,用于与数据库建立连接
- 建立连接时要指明数据库URL,用户名及密码
- URL的格式:jdbc:subprotocol:subname
- 不同的数据库URL格式不完全一致
Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mysql?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true",
"root", //用户名
"root" //密码
);
Statemen 接口
- Statement接口用于表示SQL语句执行对象
- 通过Connection提供的方法createStatement()方法获取
- boolean execute(String sql):可执行所有种类SQL,但通常用于执行DDL,如果执行的SQL语句返回了一个结果集则返回true,否则返回false
- int executeUpdate(String sql):用于执行DML语句,返回值表示受 SQL 语句影响的行数
- ResultSet executeQuery(String sql):用于执行DQL语句并获取查询结果集
- 执行DDL语句
创建Userinfo表,包含id,username,password,nickname,age字段
Statement state = connection.createStatement();
String sql = "CREATE TABLE userinfo(\n" +
" id INT PRIMARY KEY AUTO_INCREMENT,\n" +
" username VARCHAR(30),\n" +
" password VARCHAR(30),\n" +
" nickname VARCHAR(30),\n" +
" age INT(3)\n" +
")";
state.execute(sql);
- 执行DML语句
向Userinfo表中插入一条记录
Statement state = connection.createStatement();
String sql = "INSERT INTO userinfo(username,password,nickname,age)\n" +
"VALUES('张三','123456','阿三',22)";
int num = state.executeUpdate(sql);
if(num>0){System.out.println("插入成功");}
修改Userinfo表中一条记录
Statement state = connection.createStatement();
String sql = "UPDATE userinfo " +
"SET password='666666' " +
"WHERE username='张三'";
int num = state.executeUpdate(sql);
System.out.println(num>0?"修改成功":"修改失败");
删除Userinfo表中一条记录
Statement state = connection.createStatement();
String sql = "DELETE FROM userinfo " +
"WHERE id=1";
int num = state.executeUpdate(sql);
System.out.println(num>0?"删除成功":"删除失败");
ResultSet 接口
- JDBC提供的接口ResultSet
- ResultSet表示查询结果集,主要方法:
方法 | 功能 |
---|---|
boolean next() | 结果集向下移动一条记录,如果存在则返回true,否则返回false |
String getString(int index) | 获取该记录指定下标对应的String类型字段值 |
String getString(String name) | 获取该记录指定字段名对应的String类型字段值 |
int getInt(int index) | 获取该记录指定下标对应的int类型字段值 |
int getInt(String name) | 获取该记录指定字段名对应的int类型字段值 |
如上格式还有其他数据类型字段值的getXXX(…)方法 | 分别通过下标和字段名获取对应字段值的操作 |
- 执行DQL语句
try(Connection connection = DBUtil.getConnection();){
Statement statement = connection.createStatement();
String sql = "SELECT id,name,salary,title "+
"FROM teacher";
ResultSet rs = statement.executeQuery(sql);
//遍历结果集
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
int salary = rs.getInt("salary");
String title = rs.getString("title");
System.out.println(id+","+name+","+salary+","+title);
}
} catch (SQLException e){
e.printStackTrace();
}
PreparedStatement 接口
- PreparedStatement用于执行预编译SQL语句
- 预编译SQL
- 预编译SQL可以使用占位符 " ? " 来表示需要使用的 " 值 "
- 占位符内容仅表达需要使用的值,而不会影响SQL语义
- 占位符不可以改变语义
- 常用方法:
方法 | 作用 |
---|---|
setXxx(int parameterIndex, Xxx value) | 为指定位置的占位符设置具体的值,如 setInt、setString 等 |
executeQuery() | 执行查询语句,返回一个 ResultSet 对象 |
executeUpdate() | 执行插入、更新或删除语句,返回受影响的行数 |
execute() | 执行 SQL 语句,返回一个布尔值,表示是否返回了结果集 |
根据ID修改用户信息:密码,昵称,年龄:
public class JDBCDemo10 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户ID:");
String id = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
System.out.println("请输入昵称:");
String nickname = scanner.nextLine();
System.out.println("请输入年龄:");
int age = scanner.nextInt();
try (Connection connection = DBUtil.getConnection()) {
String sql = "UPDATE userinfo SET password=?,nickname=?,age=? "+
"WHERE id="+"'"+id+"'";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1,password);
ps.setString(2,nickname);
ps.setInt(3,age);
int num = ps.executeUpdate();
System.out.println(num>0?"修改成功":"修改失败");
}catch(SQLException e){
e.printStackTrace();
}
}
}
关闭操作
在 JDBC 操作中,Connection、Statement 、PreparedStatement 以及 ResultSet 这些资源在使用完毕后都需要关闭,从而避免资源泄漏
- 可以在 finally 块中,分别检查它们是否为 null,如果不为 null 则用 close() 方法关闭
finally {
// 关闭资源
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
而在Java 7 及以上版本,可以在 try 语句后面的括号中声明和初始化资源,当 try 块结束时,无论是否发生异常,这些资源都会自动调用 close() 方法关闭
try (
Connection connection = DBUtil.getConnection();
PreparedStatement ps = connection.prepareStatement(
"UPDATE userinfo SET password=?,nickname=?,age=? WHERE id=?"
)
) {
ps.setString(1, password);
ps.setString(2, nickname);
ps.setInt(3, age);
ps.setString(4, id);
int num = ps.executeUpdate();
System.out.println(num > 0 ? "修改成功" : "修改失败");
} catch (SQLException e) {
e.printStackTrace();
}
工具类
在 Java 中进行 JDBC 操作时,为了提高代码的复用性和可维护性,通常会创建一个工具类来封装数据库连接、关闭等操作
public class DBUtil {
static{
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/tedu?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true",
"root",
"root");
}
}
这样在每次创建Connection时只需要
Connection connection = DBUtil.getConnection()
数据库连接池
数据库连接池在初始化时会创建一定数量的数据库连接对象,并将这些连接对象存储在一个池中。当应用程序需要与数据库进行交互时,它会从连接池中请求一个连接。如果池中有可用的连接,就直接将其分配给应用程序使用;如果池中没有可用连接,并且当前连接数尚未达到最大连接数限制,连接池会创建新的连接对象并分配给应用程序。当应用程序使用完连接后,需要将连接归还给连接池,而不是直接关闭连接。连接池会对归还的连接进行检查和清理,以便后续再次使用。
以常见的 Druid 数据库连接池为例
- 要使用 Druid 连接池,首先需要在项目中添加相应的依赖。以 Maven 为例,在 pom.xml 中添加以下依赖
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
- 创建并配置 Druid 数据源
通过设置一系列参数来初始化 DruidDataSource 对象,这些参数包括数据库连接的 URL、用户名、密码,以及连接池的一些基本配置,如初始连接数、最大连接数等
public class DBUtil {
private static DruidDataSource ds;
static{
//设置用于连接数据库的基本信息
ds.setUrl("jdbc:mysql://localhost:3306/tedu?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true");
ds.setUsername("root");
ds.setPassword("root");
ds.setMaxActive(20); //最大连接数
ds.setInitialSize(5); //设置初始容量
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}
JDBC中的事务控制
- JDBC中在执行DML语句时,默认是自动提交事务的
- 事务在JDBC中是依靠链接对象Connection来维护的
- JDBC中控制事务的相关方法
方法 | 作用 |
---|---|
setAutoCommit(boolean autoCommit) | 设置事务是否自动提交 |
commit() | 提交事务 |
rollback() | 回滚事务 |
public class JDBCDemo11 {
public static void main(String[] args) {
try (Connection connection = DBUtil.getConnection()){
connection.setAutoCommit(false); //不要自动提交
String sql = "DELETE FROM userinfo WHERE id=3";
Statement statement = connection.createStatement();
int num = statement.executeUpdate(sql);
System.out.println(num>0?"删除成功":"删除失败");
/*
无论执行的是提交事务,还是回滚事务,都会导致本次事务结束
*/
connection.commit();//COMMIT
System.out.println("事务提交了");
// connection.rollback();//ROLLBACK
// System.out.println("事务回滚了");
//如果下面又开始执行其他DML操作时,会自动开启一个新的事务
} catch (SQLException e) {
e.printStackTrace();
}
}
}
设置事务的隔离级别
- 事物的隔离级别有4种
- 读未提交:SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
- 读已提交:SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED
- 可重复读:SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ
- 序列化:SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE
- 读未提交:允许一个事务读取另一个事务未提交的数据
- 读已提交:只能读取已提交的数据
- 可重复读:在一个事务内多次读取同一数据时,结果是一致的
- 序列化:强制事务串行执行,可避免所有的并发问题,但性能低
产生的问题:
隔离级别 | 脏读 | 不可重复 | 幻读 | 加锁读 |
---|---|---|---|---|
读未提交 | √ | √ | √ | × |
读已提交 | × | √ | √ | × |
可重复读 | × | × | √ | × |
序列化 | × | × | × | √ |
在Java中可以通过 Connection 接口的 setTransactionIsolation(int level) 方法设置事务隔离级别
参数:
名称 | 值 | |
---|---|---|
读未提交 | Connection.TRANSACTION_READ_UNCOMMITTED | 1 |
读已提交 | Connection.TRANSACTION_READ_COMMITTED | 2 |
可重复读 | Connection.TRANSACTION_REPEATABLE_READ | 4 |
序列化 | Connection.TRANSACTION_SERIALIZABLE | 8 |
不支持事务 | Connection.TRANSACTION_NONE | 0 |
示例(可重复读):
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ)