JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。 使用Java程序访问数据库时,Java代码并不是直接通过直接建立TCP连接去访问数据库,而是通过JDBC接口来建立连接,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。在JDBC驱动实现类中,通过建立TCP连接来建立真正的网络通讯连接。具体的JDBC驱动是由数据库厂商提供的,他们都是实现Java提供的JDBC标准,来实现操作本公司旗下的数据库,例如Mysql数据库许需要 mysql-connector-java-版本号 的驱动包(jar包),有了驱动包之后,我们就能实现Java操作数据库了。
加载驱动类:DriverManager驱动管理类使用该驱动类,进行连接的获取与创建JDBC驱动包中的Driver类的对象。
不推荐:硬编码,应用程序与数据库驱动耦合
com.mysql.cj.jdbc.Driver driver = new com.mysql.cj.jdbc.Driver();
推荐 : 反射的方式创建Driver类的对象,高版本的JDBC驱动包,可以自动加载驱动类,该步骤可以省略
Class.forName("com.mysql.cj.jdbc.Driver");
Connection 数据连接接口:获取并创建Connection接口的实现类对象(比如MySQL的ConnectionImpl实现类)
参数1:数据库连接字符串(JDBC连接字符串):jdbc:数据库类型:数据库地址(服务器地址、端口号、数据库名称)?连接参数
jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8
参数2:数据库用户名
参数3:数据库密码
Statement(PreparedStatement)数据库操作接口:执行SQL语句
ResultSet 数据库结果集接口:执行select语句后,获取查询结果
步骤:
1.建立连接、设置数据库连接参数;
2.更新SQL语句;
3.创建Statement数据库操作对象,执行executeQuery(),执行后获取查询ResultSet结果集对象;
4.释放资源。
注意:由于Connection、Statement、ResultSet都是需要关闭的资源,所以这里使用try with resource,实现自动关闭。
package com.fulian.demo01;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
// 使用Statement执行查询
public class Test02 {
public static void main(String[] args) {
// 数据库连接参数
final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
final String DB_USER_NAME = "用户名";
final String DB_USER_PASS = "密码";
// 查询所有用户
try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
System.out.println("数据库连接成功!" + con);
// 2.SQL
int val1 = 1, val2 = 6;
String sql = "SELECT * FROM user_info WHERE user_id >= " + val1 + " AND user_id <= " + val2;
System.out.println("sql = " + sql);
// 2.1 创建Statement数据库操作对象
try (Statement st = con.createStatement();) {
System.out.println(st);
// 2.2 执行SQL(查询),执行后获取查询ResultSet结果集对象
try (ResultSet rs = st.executeQuery(sql);) {
System.out.println(rs);
// 2.3
// 获取某一行中若干字段值
while (rs.next()) {
// 如果rs.next()方法的返回值为true,代表存在下一行数据
// rs则移动游标(指向标),只想下一行
// rs.getXXX()方法,则开始读取游标只读取游标指向的行
int userId = rs.getInt(1); // 获取当前行中的第1个字段值(user_id用户编号字段)
String phoneNumber = rs.getString(2); // 获取当前行中的第2个字段值(login_phone_number登陆手机号码字段)
String realName = rs.getString(3); // 获取当前行中的第3个字段值(user_real_name用户真实姓名字段)
Date lastLogin = rs.getDate(4); // 获取当前行中的第4个字段值(last_login_date_time字段)
System.out.println("用户编号:" + userId);
System.out.println("手机号码:" + phoneNumber);
System.out.println("真实姓名:" + realName);
System.out.println("最后登录:" + lastLogin);
System.out.println();
}
}
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
优点:1.完全避免SQL注入问题,保持数据库的安全性;
2.PreparedStatement创建时,传入带有?占位符的SQL语句,保证每次传入的sql语句都是相同的,只是占位符的数据不同,执行时,基于MySQL预编译机制,提高数据库的执行效率。
步骤:
1.建立连接、设置数据库连接参数;
2.更新SQL语句;
3.创建PreparedStatement数据库操作对象,执行executeQuery(),执行后获取查询ResultSet结果集对象;
4.释放资源。
package com.fulian.demo01;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test04 {
public static void main(String[] args) {
// 数据库连接参数
final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
final String DB_USER_NAME = "用户名";
final String DB_USER_PASS = "密码";
// 用户登录
try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
System.out.println("数据库连接成功!" + con);
// 2.SQL
String phoneNumber = "12345678900";
String loginPassword = "1794";
String sql = "SELECT * FROM user_info WHERE login_phone_number = ? AND login_password = ?";
System.out.println("sql = " + sql);
// 2.1 创建PreparedStatement数据库操作对象
// PreparedStatement创建时,传入带有?占位符的SQL语句,并执行
// PreparedStatement执行时,基于MySQL预编译机制,提高数据库的执行效率
try (PreparedStatement pst = con.prepareStatement(sql);) {
// 执行前,处理?占位符
pst.setString(1, phoneNumber);
pst.setString(2, loginPassword);
// 2.2 执行SQL(查询)
try (ResultSet rs = pst.executeQuery();) {
// 输出登录状态
if (rs.next()) {
System.out.println("登录成功!");
}else {
System.out.println("登录失败!");
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
在jdbc中,所有的增加、删除、修改都是update操作,步骤如下:
1.建立连接、设置数据库连接参数;
2.更新SQL语句;
3.创建PreparedStatement数据库操作对象,执行executeUpdate()方法,执行后获取修改行数;
4.释放资源。
以增加数据为例,代码如下:
package com.fulian.demo01;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Test08 {
public static void main(String[] args) {
// 数据库连接参数
final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
final String DB_USER_NAME = "用户名";
final String DB_USER_PASS = "密码";
// 增加
// 1.创建数据库连接
try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
// 2.SQL
String sql = "INSERT INTO material_info(material_name,material_stock,material_unit)VALUES(?,?,?)";
try(PreparedStatement pst = con.prepareStatement(sql);){
pst.setString(1, "消毒棉片片");
pst.setInt(2, 98);
pst.setString(3, "包");
int rows = pst.executeUpdate();
System.out.println("影响行数:" + rows);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
那么,如果想要增加一条数据并返回新增加数据的主键呢?
在创建PreparedStatement的时候,指定一个RETURN_GENERATED_KEYS标志位,表示JDBC驱动必须返回插入的自增主键。示例代码如下:
try(PreparedStatement pst = con.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);){
pst.setString(1, "消毒玻璃水");
pst.setInt(2, 532);
pst.setString(3, "瓶");
// 先执行添加操作
int rows = pst.executeUpdate();
System.out.println("影响行数:" + rows);
// 在获取自动生成的主键值
ResultSet rs = pst.getGeneratedKeys();
if(rs.next()) {
int newRecord = rs.getInt(1);
System.out.println("新纪录的编号值:" + newRecord);
}
}
数据库事务(Transaction)是由若干个SQL语句构成的一个操作序列。数据库系统保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行,即数据库事务具有ACID特性: Atomicity:原子性 、Consistency:一致性、Isolation:隔离性、Durability:持久性。
在JDBC中,有两种事务类型,一种是隐式事务,执行完sql语句自动提交事务,另一种是显式事务,需要在jdbc中手动关闭自动提交,每次执行完sql语句,必须得手动提交或回滚。
package com.fulian.demo02;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Test02 {
public static void main(String[] args) {
// 转出账户
String sourceAccount = "张三丰";
int sourceMoney = 5000;
// 转入帐户
String targetAccount = "李四狗";
int targetMoney = 5000;
// 数据库连接参数
final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
final String DB_USER_NAME = "用户名";
final String DB_USER_PASS = "密码";
// 数据库连接
try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
// 关闭自动提交事务
con.setAutoCommit(false);
// 转出
String sql1 = "UPDATE bank SET currentMoney = currentMoney - ? WHERE customerName = ?";
try (PreparedStatement pst1 = con.prepareStatement(sql1);){
pst1.setInt(1, sourceMoney);
pst1.setString(2, sourceAccount);
int row1 = pst1.executeUpdate();
System.out.println("转出操作影响行数:" + row1);
// 转入
String sql2 = "UPDATE bank SET currentMoney = currentMoney + ? WHERE customerName = ?";
try (PreparedStatement pst2 = con.prepareStatement(sql2);){
pst2.setInt(1, targetMoney);
pst2.setString(2, targetAccount);
int row2 = pst2.executeUpdate();
System.out.println("转入操作影响行数:" + row2);
}
} catch (Exception e) {
e.printStackTrace();
// 手动回滚事务
con.rollback();
}
// 手动提交事务
con.commit();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
使用JDBC操作数据库的时候,经常会执行一些批量操作。SQL数据库对SQL语句相同,但只有参数不同的若干语句可以作为batch批处理执行,即批量执行,每次执行一条一句之后,addBath(),添加到内存中,等待全部加载完成后,执行executeBatch(),统一进行操作,以插入若干条数据为例,代码如下:
package com.fulian.demo02;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
public class Test03 {
public static void main(String[] args) {
List couponList = Arrays.asList(
new Coupon(CouponType.CASH, 500, new Date(System.currentTimeMillis() + 7*24*60*60*1000)),
new Coupon(CouponType.CASH, 40, new Date(System.currentTimeMillis() + 5*24*60*60*1000)),
new Coupon(CouponType.DISSCOUNT, 0.66, new Date(System.currentTimeMillis() + 8*24*60*60*1000)),
new Coupon(CouponType.CASH, 500, new Date(System.currentTimeMillis() + 14*24*60*60*1000)),
new Coupon(CouponType.SCORE, 5000, new Date(System.currentTimeMillis() + 2*24*60*60*1000)),
new Coupon(CouponType.SCORE, 9999, new Date(System.currentTimeMillis() + 1*24*60*60*1000)),
new Coupon(CouponType.CASH, 100, new Date(System.currentTimeMillis() + 5*24*60*60*1000)),
new Coupon(CouponType.CASH, 200, new Date(System.currentTimeMillis() + 6*24*60*60*1000)),
new Coupon(CouponType.DISSCOUNT, 0.1, new Date(System.currentTimeMillis() + 14*24*60*60*1000)),
new Coupon(CouponType.DISSCOUNT, 0.95, new Date(System.currentTimeMillis() + 14*24*60*60*1000)),
new Coupon(CouponType.SCORE, 23450, new Date(System.currentTimeMillis() + 30*24*60*60*1000)),
new Coupon(CouponType.CASH, 500, new Date(System.currentTimeMillis() + 7*24*60*60*1000))
);
// 数据库连接参数
final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
final String DB_USER_NAME = "用户名";
final String DB_USER_PASS = "密码";
// 数据库连接
try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
String sql = "INSERT INTO coupon_info(type,amount,expires)VALUES(?,?,?)";
// 采用批处理
try(PreparedStatement pst = con.prepareStatement(sql)){
for(Coupon coupon : couponList) {
pst.setNString(1, coupon.getType().name()); // 优惠卷类型
pst.setDouble(2, coupon.getAmount()); // 面额
pst.setDate(3, new java.sql.Date(coupon.getExpries().getTime())); // 过期时间
pst.addBatch(); // 添加至批处理
}
// 整体执行批处理
int[] rows = pst.executeBatch();
System.out.println("影响行数:" + Arrays.toString(rows));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
从上面的例子中,我们可以看到,每次执行JDBC操作时,都要设置数据库连接参数,调用DriverManager.getConnection()方法,因此我们可以封装一个DBUtils1工具类,代码如下:
package com.fulian.demo03;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
// 数据库工具类
public class DBUtils1 {
// 数据库连接参数
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/my_db?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
private static final String DB_USER_NAME = "root";
private static final String DB_USER_PASS = "menghao0624";
// 获取数据库连接
public static Connection getConnection() {
try {
Connection con = DriverManager.getConnection(JDBC_URL,DB_USER_NAME,DB_USER_PASS);
return con;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
但是,在执行JDBC的增删改查的操作时,每一次操作都来必须创建并打开Connection数据库连接,执行SQL语句,最后关闭Connection连接。在这个过程中,Connection连接被频繁创建和关闭,这个操作非常消耗内存与CPU等系统资源。并且,关闭Connection需要手动完成,一旦忘记会造成数据库服务器连接耗尽。在一些高并发访问数据库的场景下,手动的创建和关闭数据库连接,有可能会造成系统性能瓶颈。
因此,我们就可以使用数据库的连接池,数据库连接池负责分配、管理和释放数据库连接,它的核心思想就是连接复用。通过建立一个数据库连接池,并在连接池池中维护若干个连接对象。当用户想要连接数据库,就要先从连接池中获取连接对象,然后操作数据库。
目前主流的数据库连接池有:HikariCP、 C3P0、BoneCP、Druid,这里我们使用C3P0连接池技术,需要添加:
然后,我们就可以使用C3P0再次封装一个DBUtils2工具类(基于java代码实现)和DBUtils3工具类(基于配置文件实现):
DBUtils2:
package com.fulian.demo04;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
// 基于数据库连接池技术的工具类
// C3P0
public class DBUtils2 {
// 数据库连接参数
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
private static final String DB_USER_NAME = "用户名";
private static final String DB_USER_PASS = "密码";
//DataSource接口实现类(C3P0提供)
// 创建连接池对象
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 配置连接池(设置连接池参数)
static {
// 数据库连接参数
dataSource.setJdbcUrl(JDBC_URL);
dataSource.setUser(DB_USER_NAME);
dataSource.setPassword(DB_USER_PASS);
// 连接池参数
dataSource.setInitialPoolSize(10); // 初始化连接数
dataSource.setMaxPoolSize(100); // 最大连接数
dataSource.setMinPoolSize(20); // 最小连接数
dataSource.setMaxIdleTime(30); // 最大空闲时间(单位:秒)
dataSource.setCheckoutTimeout(3000); // 等待时间(单位:毫秒)
dataSource.setIdleConnectionTestPeriod(30); // 检查空闲连接的时间间隔(单位:秒)
}
// 获取数据库连接
public static Connection getConnection() {
// 从连接池中返回一个“空闲”连接
try {
Connection con = dataSource.getConnection();
return con;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
DBUtils3:默认在src目录下读取名称为c3p0-config.xml的配置文件
package com.fulian.demo04;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
// 基于数据库连接池技术的工具类
// C3P0
public class DBUtils3 {
//DataSource接口实现类(C3P0提供)
// 创建连接池对象
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 获取数据库连接
public static Connection getConnection() {
// 从连接池中返回一个“空闲”连接
try {
Connection con = dataSource.getConnection();
return con;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
配置文件
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8
用户名
密码
30000
30
10
30
100
10