JDBC入门讲解
JDBC简述
JDBC是Java DataBase Connectivity的简称,它可以赋予Java操作数据库的能力。
JDBC并不是单纯的一套接口,它更是一套规范,不同的数据库厂商都需要遵守这个规范。
不同数据库厂商根据自己的数据库的特点给出了这套规范的具体实现,这个具体实现驱动。
而作为Java的使用者,我们只需要学习一套JDBC API就可以了。
Hello JDBC
环境信息
-
jdk 1.8
-
mysql 8.0.13
-
IntelliJ IDEA 2019.1.4
操作步骤
下载数据库驱动
因为我本地的数据库是mysql 8.0.13,所以这里使用的是mysql-connector-java-8.0.16
驱动
下载地址:https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.16
创建一个普通项目
新建lib目录 → 将驱动复制到lib中 → 将lib目录添加到项目库中
编写代码
- mysql 8.0建议使用com.mysql.cj.jdbc.Driver驱动
- mysql 8.0在连接数据库时必须设置时区
package com.kdy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Author: kdy
* Date: Created in 2021/7/12 22:03
*/
public class HelloJDBC {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.加载驱动;mysql 8.0建议使用com.mysql.cj.jdbc.Driver驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.配置连接信息
// useUnicode=true:支持useUnicode编码支持中文
// characterEncoding=utf-8:设置字符集编码防止乱码
// serverTimezone=Hongkong:设置时区,mysql8.0必须设置时区
String url = "jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8";
String username = "root";
String password = "123456";
// 3.创建数据库连接,Connection就代表数据库
Connection connection = DriverManager.getConnection(url, username, password);
// 4.创建执行SQL的对象,Statement代表执行SQL的对象
Statement statement = connection.createStatement();
// 5.执行SQL,查看返回结果;ResultSet代表返回的结果集
String sql = "select * from user";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
System.out.println("-------kdy------");
System.out.println("id is " + resultSet.getObject("id"));
System.out.println("name is " + resultSet.getObject("name"));
System.out.println("pwd is " + resultSet.getObject("pwd"));
System.out.println("-------kdy------");
}
// 6.释放连接
resultSet.close();
statement.close();
connection.close();
}
}
代码优化
在src目录下创建配置文件db.properties,将配置信息全部提取到配置文件中
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8
username=root
password=123456
编写工具类JDBCUtils
package com.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* Author: kdy
* Date: Created in 2021/7/12 23:51
*/
public class JDBCUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static {
try{
InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(in);
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
// 数据库驱动只用加载一次,所以放在这个位置加载
Class.forName(driver);
} catch (IOException e) {
System.err.println("数据库配置文件db.properties加载失败");
} catch (ClassNotFoundException e) {
System.err.println("数据库驱动" + driver + "加载失败");
}
}
// 获取数据库连接
public static Connection getConnection(){
Connection connection = null;
try {
connection = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
System.err.println("获取数据库连接失败");
}
return connection;
}
// 释放数据库连接
public static void closed(Connection conn, Statement state, ResultSet result){
if(result != null){
try {
result.close();
} catch (SQLException e) {
System.err.println("资源ResultSet释放失败");
}
}
if(state != null){
try {
state.close();
} catch (SQLException e) {
System.err.println("资源Statement释放失败");
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
System.err.println("资源Connection释放失败");
}
}
}
}
功能实现
package com.kdy;
import com.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* Author: kdy
* Date: Created in 2021/7/13 0:13
*/
public class HelloJDBCoptimization {
public static void main(String[] args) throws Exception {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from user");
while (resultSet.next()){
System.out.println("-------kdy-coptimization------");
System.out.println("id is " + resultSet.getObject("id"));
System.out.println("name is " + resultSet.getObject("name"));
System.out.println("pwd is " + resultSet.getObject("pwd"));
}
JDBCUtils.closed(connection,statement,resultSet);
}
}
JDBC API
接口 | 说明 |
---|---|
DriverManager | 用于管理一组JDBC驱动程序的基本服务 |
Connection | 与特定数据库的连接(会话)。 执行SQL语句并在连接的上下文中返回结果 |
Statement | 用于执行静态SQL语句并返回其生成的结果的对象 |
ResultSet | 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成 |
DriverManager
DriverManager主要功能有两个
- 注册驱动
- 获取数据库连接Connection
JDBC 2.0 API中新增的DataSource
接口提供了另一种连接到数据源的方法,具体内容见数据库连接池
关于获取Connection没有什么可以说的,通过getConnection()方法就可以获取Connection对象
我们来看看DriverManager是如何完成加载驱动的,因为我们上述代码在加载驱动时并没有发现DriverManager的参与,其实驱动加载的标准写法应该是
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
但是我们通过源码可以发现com.mysql.cj.jdbc.Driver()类实际上是一个静态代码块,其中的内容就包括了标准写法,所以我们只需要操作类加载即可自动完成驱动的注册
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
Connection
在创建数据库连接时我们需要注意url的编写,不同数据库的url写法不一致;且mysql 8.0在连接时必须设置好时区
生成的Connection对象就代表着数据库,通过这个对象可以完成事务的自动提交、事务回滚等操作
Statement
Statement对象用于将Java中的SQL传递给数据库,用于执行
Statement一共有三种类型
- Statement:用于执行不带参数的简单SQL语句
- PreparedStatement(从 Statement 继承):用于执行带或不带参数的预编译SQL语句
- CallableStatement(从PreparedStatement 继承):用于执行数据库存储过程的调用
Statement常用方法
方法 | 说明 |
---|---|
ResultSet executeQuery(String sql) | 执行查询操作,返回的数据会放在ResultSet对象中 |
boolean execute(String sql) | 执行任意类型sql,返回boolean,表示是否返回ResultSet对象 |
int executeUpdate(String sql) | 执行插入、删除、更新等操作,返回值为影响的行数 |
PreparedStatement
如果通过Statement去执行SQL很容易出现SQL注入的问题
为了解决这一问题我们可以通过PreparedStatement来执行SQL语法
PreparedStatement防止SQL注入的本质就是把参数的参数当作字符
具体使用方式如下
package com.kdy;
import com.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Author: kdy
* Date: Created in 2021/7/13 0:48
*/
public class PreparedStatementTest {
public static void main(String[] args) throws SQLException {
Connection conn = JDBCUtils.getConnection();
// 预编译sql,先写sql不执行,使用?占位符代替参数
String sql = "select * from user where id = ? and name = ?";
PreparedStatement preState = conn.prepareStatement(sql);
// 手动给参数进行赋值,第一个参数表示给第几个?赋值
preState.setInt(1,1);
preState.setString(2,"张三");
// 执行sql
ResultSet resultSet = preState.executeQuery();
while (resultSet.next()){
System.out.println("-------kdy-preparedStatementTest------");
System.out.println("id is " + resultSet.getObject("id"));
System.out.println("name is " + resultSet.getObject("name"));
System.out.println("pwd is " + resultSet.getObject("pwd"));
}
JDBCUtils.closed(conn,preState,resultSet);
}
}
ResultSet
Statement执行查询后会返回ResultSet
ResultSet中会存放查询后返回信息,存放的数据会像数据库一样排列,且有一个记录指针
我们可以通过里面的指针操作行数据,通过列的序列或者列名操作列数据
ResultSet 常用方法
方法 | 说明 |
---|---|
boolean next() | 将指针向下移动并返回此位置是否有值 |
boolean previous() | 将指针向上移动并返回此位置是否有值 |
int getInt(int colIndex) | 根据列的id获取数据,数据以int的形式返回 |
int getInt(int colLabel) | 根据列的名字获取数据,数据以int的形式返回 |
...... | ...... |
JDBC事务
首先需要开启事务conn.setAutoCommit(false);关闭数据库的自动提交,自动开启事务
其次编写业务代码
如果全部成功则提交conn.commit();
如果存在失败的则回滚conn.rollback();【默认失败就会回滚】
数据库连接池
在DriverManager类的文档注释上有这样一句话
JDBC 2.0 API中新增的
DataSource
接口提供了另一种连接到数据源的方法,具体内容见数据库连接池
所以DataSource可以取代DriverManager,他们都是连接到数据库的方法
数据库连接池DatSources负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
编写连接池只需要实现DataSources接口即可,当然我们可以使用一些开源的数据源实现,常见的数据源实现有DBCP、C3P0、Druid
DBCP
首先我们需要两个jar包commons-dbcp.jar和commons-pool.jar
- commons-dbcp.jar:https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp/1.4
- commons-pool.jar:https://mvnrepository.com/artifact/commons-pool/commons-pool/1.6
核心配置文件
# 连接设置,这些名字都是DBCP数据源定义好的
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8
username=root
password=123456
# 初始化连接
initialSize=10
# 最大连接数量
maxActive=50
# 最大空闲连接
maxIdle=20
# 最小空闲连接
minIdle=5
# 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒
maxWait=60000
# JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:【属性名=property;】
# 注意:user 与 password 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8
# 指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
# driver default 指定由连接池所创建的连接的只读(read-only)状态。
# 如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
# defaultReadOnly=
# driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
# 可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
修改我们的获取Connection的工具类代码
package com.utils;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* Author: kdy
* Date: Created in 2021/7/14 11:25
*/
public class JDBCUtils_DBCP {
private static DataSource dataSource = null;
static {
try{
InputStream in = JDBCUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(in);
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取数据库连接
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
System.err.println("获取数据库连接失败");
}
return connection;
}
// 释放数据库连接
public static void closed(Connection conn, Statement state, ResultSet result){
if(result != null){
try {
result.close();
} catch (SQLException e) {
System.err.println("资源ResultSet释放失败");
}
}
if(state != null){
try {
state.close();
} catch (SQLException e) {
System.err.println("资源Statement释放失败");
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
System.err.println("资源Connection释放失败");
}
}
}
}
C3P0
首先我们需要两个jar包c3p0.jar和commons-pool.jar
- c3p0.jar:https://mvnrepository.com/artifact/com.mchange/c3p0/0.9.5.5
- mchange-commons-java.jar:https://mvnrepository.com/artifact/com.mchange/mchange-commons-java/0.2.19
核心配置文件,c3p0会自动扫描src目录下的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 没有name,如果没有指定使用哪个配置,则使用这个默认配置 -->
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="checkoutTimeout">30000</property>
<property name="idleConnectionTestPeriod">30</property>
<property name="initialPoolSize">3</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">2</property>
<property name="maxStatements">200</property>
</default-config>
<!-- 命名的配置,可以通过方法调用实现 -->
<named-config name="mysql">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 如果池中数据连接不够时一次增长多少个 -->
<property name="acquireIncrement">5</property>
<!-- 初始化数据库连接池时连接的数量 -->
<property name="initialPoolSize">20</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">25</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
</named-config>
</c3p0-config>
修改我们的获取Connection的工具类代码
package com.utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Author: kdy
* Date: Created in 2021/7/14 12:19
*/
public class JDBCUtils_C3P0 {
private static ComboPooledDataSource dataSource = null;
static {
try{
// 使用xml配置
dataSource = new ComboPooledDataSource("mysql");
// 使用java配置
// dataSource = new ComboPooledDataSource();
// dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
// dataSource.setUser("root");
// ....
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取数据库连接
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
System.err.println("获取数据库连接失败");
}
return connection;
}
// 释放数据库连接
public static void closed(Connection conn, Statement state, ResultSet result){
if(result != null){
try {
result.close();
} catch (SQLException e) {
System.err.println("资源ResultSet释放失败");
}
}
if(state != null){
try {
state.close();
} catch (SQLException e) {
System.err.println("资源Statement释放失败");
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
System.err.println("资源Connection释放失败");
}
}
}
}
GitHub代码地址:https://gitee.com/jwyddr2/cnblogs-kdy/tree/master/jdbc/jdbc-kdy
参考
https://www.bilibili.com/video/BV1NJ411J79W?p=44
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!