JDBC介绍
Java数据库连接,JDBC(Java Database Connectivity,简称JDBC)
是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
简单说,jdbc 是Java语言为了屏蔽具体的具体的数据库操作的细节不同提供的一个框架。
在关系型数据库的处理中,大致流程都一样:
- 连接数据库
- 执行语句
- 返回数据
- 处理数据
所以,为了统一,Java官方就增加了对流程做了规范,增加了一步加载驱动,也就是加载不同的实现。
下图是 JDBC 作用图示[1]:
JDBC使用
JDBC 的使用比较简单,简单总结还是数据的大致流程。
这里看看 JDBC 连接数据的细致流程:
- 读取配置
- 加载驱动
- 连接数据库
这里以 MySQL 为例:
java
// 1.读取配置
Properties properties = new Properties();
try {
InputStream in = Util.class.getClassLoader().getResourceAsStream("app.properties");
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
System.out.println("未找到配置文件");
}
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
// 2,加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 3.连接数据库
conn = DriverManager.getConnection(url, username, password);
数据操作
重要类、接口、方法
- Statement
接口,主要声明执行语句和结果获取,在我们进行 DDL 操作的时候,可以通过 Connection.createStatement() 获取再操作。
- preparestatement
是 Connection 类 的方法,通过调用此方法,传入提前定义好的 sql 语句,返回 PrepareStatement 接口的对象,同样也可以去调用 executeQuery/executeUpdate 去执行静态语句。
- callablestatement
继承自PreparedStatement接口,用于调用存储过程。
-
boolean execute() 在 PrepareStatement 语句中使用,效果等同于 executeQuery/executeUpdate,但有一点需要注意,如是是查询语句,执行后要返回 true/false,如果要获取查询的结果,应该再次调用 Statement.getResultSet,如果是 update 类的语句,则可以调用 Statement.getUpdateCount。
-
ResultSet executeQuery()
执行了 SQL DQL 查询语句后,返回查询的结果集。
- int executeUpdate
执行 DML 后,返回整型值,可以是影响行数,或者0,主要针对 INSERT、UPDATE、DELETE 语句。
- void addBatch()
在如执行批量插入操作时,将一组参数加入到 PrepareStatement 中。
- int[] executeBatch()
提交批量操作的命令,正式执行批量操作。
- setSomeType()
主要针对 PrepareStatement 对象,对占位符的参数进行设置,占位符的序号+字段值。
结果集-ResultSet
- getSomeType()
类似于 ResultSet.getString(),传入参数主要2种,一种通过字段名,一种通过字段在表中的序号。
- Boolean next()
通过游标的移动判断是否还有数据,如返回数据后,通过 next() 方法判断是否可以继续获取数据。
使用步骤
基本步骤:
- 加载JDBC驱动程序
- 建立数据库连接Connection
- 创建执行SQL的语句Statement
- 处理执行结果ResultSet/int
这里我们通过一个简短的示例看看 JDBC 的使用。
环境:
- JDK:17
- MySQL:5.7
新建一个 maven 项目,添加如下依赖:
xml
<dependencies>
<!-- mysql driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.1.0</version>
</dependency>
<!-- druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
<!-- junit -->
<!-- 基础测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.0</version>
</dependency>
</dependencies>
在 resource 目录新建个配置文件,app.properties:
ini
url=jdbc:mysql://{ip}:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username=root
password=123456
initialSize=5
maxActive=10
minIdle=5
这里新建个 工具类,主要功能就是获取连接和关闭连接:
java
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class Util {
public static Connection getConn() {
Connection conn = null;
try {
// 1.读取配置
Properties properties = new Properties();
try {
InputStream in = Util.class.getClassLoader().getResourceAsStream("app.properties");
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
System.out.println("未找到配置文件");
}
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
// 2,加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 3.连接数据库
conn = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("驱动类找不到");
} catch (SQLException e) {
e.printStackTrace();
System.out.println("数据库连接失败");
}
return conn;
}
public static void close(Connection conn, Statement stm, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stm != null) {
try {
stm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
我们操作的表结构:
接下耒,就可以测试 JDBC的CRUD了。
增-插入操作(insert)
这里主要演示单条插入和批量插入数据:
java
import org.junit.jupiter.api.Test;
import java.sql.*;
import java.util.Random;
public class TestSqlInsert {
@Test
public void insert() throws SQLException {
Connection conn = null;
// 1.获取conn
conn = Util.getConn();
// 2.sql 语句,占位符
String sql = "INSERT INTO example (`name`, `age`) VALUES (?, ?)";
// 3.获取sql语句对象
PreparedStatement preparedStatement = conn.prepareStatement(sql);
// 4.填入字段内容
// 第一个占位符字段,string类型
preparedStatement.setString(1, "Gabriel");
// 第二个占位符字段,int类型
preparedStatement.setInt(2, 7);
// 5.执行sql
preparedStatement.executeUpdate();
Util.close(conn, preparedStatement, null);
}
@Test
public void insertBatch() throws SQLException {
Connection conn = null;
conn = Util.getConn();
// 关闭自动提交
conn.setAutoCommit(false);
String sql = "INSERT INTO example (name, age) VALUES (?, ?)";
PreparedStatement statement = conn.prepareStatement(sql);
for (int i = 0; i < 100; i++) {
statement.setString(1, "Galaxy" + i);
statement.setInt(2, new Random().nextInt(18, 65));
statement.addBatch();
if (i % 10 == 0) {
statement.executeBatch();
}
}
statement.executeBatch();
conn.commit();
statement.close();
conn.close();
}
}
删-按条件删除记录行(delete)
java
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestSqlDelete {
@Test
public void delete() throws SQLException {
Connection conn = null;
conn = Util.getConn();
String sql = "DELETE FROM example WHERE age>?";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, 60);
preparedStatement.executeUpdate();
Util.close(conn, preparedStatement, null);
}
}
改-更新数据(update)
按照条件更新记录行:
java
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestSqlUpdate {
@Test
public void update() throws SQLException {
Connection conn = null;
// 1.获取conn
conn = Util.getConn();
// 2.sql语句
String sql = "UPDATE example SET age=? WHERE id=?";
// 3.获取prepareStatement
PreparedStatement preparedStatement = conn.prepareStatement(sql);
// 4.根据占位符填入值
preparedStatement.setInt(1, 66);
preparedStatement.setInt(2, 2);
// 5.执行语句
int result = preparedStatement.executeUpdate();
System.out.println("影响行数:" + result);
Util.close(conn, preparedStatement, null);
}
}
查-查询数据(select)
由于只是示例,这里简单获取所有数据,或者按照条件查询数据:
java
import org.junit.jupiter.api.Test;
import java.sql.*;
public class TestSqlQuery {
@Test
public void queryAll() throws SQLException {
Connection conn = null;
// 1.获取连接
conn = Util.getConn();
// 2.sql语句
String sql = "SELECT * FROM example";
// 3.获取对象
PreparedStatement preparedStatement = conn.prepareStatement(sql);
// 4.执行语句
ResultSet rs = preparedStatement.executeQuery();
// 5.处理结果,多条结果用while,单条用if
while (rs.next()) {
// 通过字段名获取对象值
int id = rs.getInt("id");
// 通过字段列序号获取值
String name = rs.getString(2);
int age = rs.getInt("age");
Date createTime = rs.getDate("create_time");
System.out.println(String.format("id: %d, name: %s, age: %d, create_time: %s", id, name, age, createTime.toString()));
}
Util.close(conn, preparedStatement, rs);
}
@Test
public void queryByName() throws SQLException{
Connection conn = null;
// 1.获取连接
conn = Util.getConn();
// 2.sql语句
String sql = "SELECT * FROM example WHERE name=?";
// 3.获取对象
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1,"Greg");
// 4.执行语句
ResultSet rs = preparedStatement.executeQuery();
// 5.处理结果,多条结果用while,单条用if
if (rs.next()) {
// 通过字段名获取对象值
int id = rs.getInt("id");
// 通过字段列序号获取值
String name = rs.getString(2);
int age = rs.getInt("age");
Date createTime = rs.getDate("create_time");
System.out.println(String.format("id: %d, name: %s, age: %d, create_time: %s", id, name, age, createTime.toString()));
}
Util.close(conn, preparedStatement, rs);
}
}
事务处理
事务处理也比较简单,先开启事务,关闭自动提交,然后执行语句,提交,执行有问题则回滚事务:
java
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestTransaction {
@Test
public void test() throws SQLException {
Connection conn = Util.getConn();
PreparedStatement smt = null;
try {
// 1.开启事务
conn.setAutoCommit(false);
String sql = "UPDATE example SET age=? WHERE name=?";
smt = conn.prepareStatement(sql);
smt.setInt(1, 55);
smt.setString(2, "Greg");
smt.execute();
// 2.提交事务
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
// 3.回滚事务
conn.rollback();
}
smt.close();
conn.close();
}
}
数据定义操作-DML
常用的如建库或者建表,或者设置索引等。
下面演示一个建表的操作:
java
import org.junit.jupiter.api.Test;
import java.sql.*;
public class TestDDL {
@Test
public void testDDL() throws SQLException {
Connection conn = Util.getConn();
Statement statement = conn.createStatement();
// create table
String tableSql = "CREATE TABLE test (id int auto_increment primary key, name varchar(20), age int)";
// exec
int res = statement.executeUpdate(tableSql);
System.out.println("建表成功:" + res);
}
}
连接池
如果是频繁的数据库操作,还是建议通过数据库连接池来代理,通过连接池,我们可以:
- 不用频繁创建和销毁资源
- 更加快速的获取连接
连接池选择很多,这里我们主要通过阿里巴巴的 Druid 为例。
在 连接池 的示例中,我们主要实现创建连接池和连接池的简单应用。
连接池工具类:
java
import com.alibaba.druid.pool.DruidDataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class PoolUtil {
public static Connection getPoolConn() {
Connection conn = null;
// 读取配置
try {
Properties properties = new Properties();
try {
InputStream in = PoolUtil.class.getClassLoader().getResourceAsStream("app.properties");
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
System.out.println("未找到连接池的配置文件");
}
// 创建连接池源
DruidDataSource dataSource = new DruidDataSource();
// 设置数据库连接信息
dataSource.setUrl(properties.getProperty("url"));
dataSource.setUsername(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
// 设置连接池信息
dataSource.setInitialSize((int)Integer.parseInt((String)properties.get("initialSize")));
dataSource.setMaxActive((int)Integer.parseInt((String)properties.get("maxActive")));
dataSource.setMinIdle((int)Integer.parseInt((String)properties.get("minIdle")));
conn = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
public static void close(Connection conn, Statement stm, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stm != null) {
try {
stm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
连接池的应用测试
java
import org.junit.jupiter.api.Test;
import java.sql.*;
public class TestPool {
@Test
public void test() {
System.out.println("Preparing test>>>");
Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;
try {
conn = PoolUtil.getPoolConn();
// create sql
String sql = "SELECT * FROM example";
preparedStatement = conn.prepareStatement(sql);
rs = preparedStatement.executeQuery();
while (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
Date createTime = rs.getDate(4);
System.out.println(String.format("id: %d, name: %s, age: %d, create_time: %s", id, name, age, createTime.toString()));
}
} catch (SQLException e) {
System.out.println("数据库操作有问题");
e.printStackTrace();
} finally {
PoolUtil.close(conn, preparedStatement, rs);
}
}
}
通过上面的示例,我们可以发现,其实连接池的使用上和直连差别不大,但还是建议使用 连接池 访问数据库。
总结
上面的学习中,我们了解了 JDBC 是 Java 中用于数据库连接的接口,我们通过 JDBC 简单实现了对数据库的增删改查,最后学习了连接池的创建与使用,实际的项目应用中,只要项目代码稍微复杂点,我们更多的会选择 ORM框架 辅助编码实现。
参考: