目录
第三种方式:使用DriverManager 替代 driver 进行统一管理
第四种方式:使用Class.forName 自动完成注册驱动,简化代码
JDBC程序编写步骤
1. 注册驱动-加载Driver类
2. 获取连接-得到Connection
3. 执行增删改查-发送SQL给mysql执行
4. 释放资源-关闭相关连接
获取数据库连接的5种方式
第一种方式:静态加载,灵活性差,依赖性强
Driver driver = new Driver(); // 创建driver对象
String url = "jdbc:mysql://localhost:3306/cs_db02";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "xxx");// 密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);
第二种方式:使用反射机制
//使用反射加载Driver类,动态加载,更加的灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/cs_db02";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "xxx");// 密码
Connection connect = driver.connect(url, properties);
System.out.println("方式2=" + connect);
使用反射加载Driver类,动态加载,更加的灵活,减少依赖性
第三种方式:使用DriverManager 替代 driver 进行统一管理
//使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/cs_db02";
//将 用户名和密码放入到Properties 对象
String user = "root";
String password = "xxx";
DriverManager.registerDriver(driver);//注册Driver驱动
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式3=" + connection);
第四种方式:使用Class.forName 自动完成注册驱动,简化代码
这种方式获取连接时使用最多的
Class.forName("com.mysql.jdbc.Driver");
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/cs_db02";
String user = "root";
String password = "xxx";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式3=" + connection);
一定要看
//使用反射加载了 Driver类
//在加载 Driver类时,完成注册
/*
源码:1. 静态代码块,在类加载时,会执行一次
2. DriverManager.registerDriver(new Driver());
3. 因此注册driver的用作已经完成
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
Driver源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
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!");
}
}
}
不用写下面代码也可以执行
Class.forName("com.mysql.jdbc.Driver");
我使用的版本
第五种方式:改进方式4,添加配置文件
//通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式5=" + connection);
配置文件位置
user=root
password=xxx
url=jdbc:mysql://localhost:3306/cs_db02?rewriteBatchedStatements=true
driver=com.mysql.jdbc.Driver
ResultSet
debug找到数据存储再ResultSet的位置
实例
//通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//1. 注册驱动
Class.forName(driver);
//2. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式5=" + connection);
//3. 得到Statement
Statement statement = connection.createStatement();
//4. 组织SQL
String sql = "select id, name, sex, borndate from actor";
//执行给定的SQL语句,该语句返回单个 ResultSet对象
ResultSet resultSet = statement.executeQuery(sql);
//5. 使用while取出数据
while (resultSet.next()) {// 让光标向后移动,如果没有更多行,则返回false
int id = resultSet.getInt(1);//获取改行改行的第1列数据
String name = resultSet.getString(2);//获取到改行的第2列
String sex = resultSet.getString(3);//获取到改行的第3列
String date = resultSet.getString(4);//获取到改行的第4列
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
}
//6. 关闭连接
resultSet.close();
statement.close();
connection.close();
Statement
什么是SQL注入?
SQL 注入(SQL Injection) 是发生在 Web 程序中数据库层的安全漏洞,是网站存在最多也是最简单的漏洞。 主要原因是程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在 Web 应用程序中事先定义好的 SQL 语句中添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步获取到数据信息。
简单的案例
-- SQL
-- 输入用户名 为1' or
-- 输入密码 为 or '1'='1
SELECT *
FROM admin
WHERE `name` = '1' OR' AND pwd = 'OR '1'='1';
输入这样的用户名和密码可以直接绕过检查,这是早期黑客使用的SQL注入。
测试一下
当然现在的技术早就预防了这种简单的SQL注入
下一个知识点就是使用PreparedStatement预防SQL注入
PreparedStatement
类图
使用PreparedStatement会对其预处理不用,最后和整个程序一起编译
注意
如果放进去还是会有SQL注入的,所以一定不要放
封装JDBCUtils(第一代)
将相同的步骤封装到一个类中,调用时直接使用其方法。
public class JDBCUtils {
//定义相关的属性(4个),因为只需要一份,因此,我们做出static
private static String user; //用户名
private static String password; //密码
private static String url; //url
private static String driver; //驱动名
//在static代码块去初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关的属性
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
} catch (IOException e) {
//在实际开发中,我们可以这样处理
//1. 将编译异常转成运行异常
//2. 这里调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便
//
throw new RuntimeException(e);
}
}
//连接数据库,返回Connection
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入空
*/
public static void close(ResultSet set, Statement statement, Connection connection) {
//判断是否为null
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void close(ResultSet set) {
//判断是否为null
try {
if (set != null) {
set.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void close(Statement statement) {
//判断是否为null
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void close(Connection connection) {
//判断是否为null
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
后面的案例中会直接使用工具类的方法来获取连接和关闭资源
事务
JDBC自动开启事务,也自动开启自动提交
处理事务的案例
@Test
//事务来解决
public void useTransaction() {
//操作转账的事务
//1.得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
PreparedStatement preparedStatement1 = null;
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtils.getConnection();//在默认情况下,connection是默认自动提交
//将connection 设置为不自动提交
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql);
//执行
preparedStatement.executeUpdate();
// int i = 1 / 0; // 抛出异常
preparedStatement1 = connection.prepareStatement(sql2);
preparedStatement1.executeUpdate();
//这里提交事务
connection.commit();
} catch (Exception e) {
//这里我们可以进行回滚,即撤消执行的SQL
//默认回滚到事务开始的状态
System.out.println("执行发生了异常,回滚撤消执行的SQL");
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null,preparedStatement,connection);
}
}
批处理
效率对比
不使用批处理,即一条一次执行
//传统方法1,添加5000条数据到admin3
@Test
public void noBatch() throws Exception{
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values (null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行了");
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666" + i);
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("传统方式 耗时=" + (end - start));//5956
//关闭连接
JDBCUtils.close(null,preparedStatement, connection);
}
使用批处理
@Test
//使用批量方式添加数据
public void batth() throws Exception{
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values (null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行了");
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666" + i);
//将SQL 语句加入到批处理包 -> 看源码
/*
//1. 第一次创建 ArrayList - elementData => Object[]
//2. elementData => Object[] 就会存放我们预处理的sql语句
//3. 当elementData满后,就按照1.5倍扩容
//4. 当添加到指定的值后,会executeBatch
//5. 批量处理会减少我们发送sql语句的网络开销,而且减少编译次数,因此效率提高
public void addBatch() throws SQLException {
synchronized(this.checkClosed().getConnectionMutex()) {
if (this.batchedArgs == null) {
this.batchedArgs = new ArrayList();
}
for(int i = 0; i < this.parameterValues.length; ++i) {
this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
}
this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
}
}
*/
preparedStatement.addBatch();
//当有一天条记录时,再批量执行
if ((i + 1) % 1000 == 0) { //满1000条
preparedStatement.executeBatch();
//清空一把
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("批量方式 耗时=" + (end - start));//72
//关闭连接
JDBCUtils.close(null,preparedStatement, connection);
}
注意要想让批处理发挥作用需要再url里面加入
?rewriteBatchedStatements=true
否则速度和没有使用批处理没两样
批处理底层使用的是ArrayList,元素是SQL语句
主要代码
数据库连接池
为什么要用连接池?传统方式会有什么影响?
例如需要连接5000次数据库
传统方式,即
@Test
//代码 连接mysql 5000次
public static void main(String[] args) {
//看看看连接-关闭 connection 会好用多久
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
//使用传统jdbc方式及,得到廉连接
Connection connection = JDBCUtils.getConnection();
//做一些工作,比如得到PreparedStatement发送sql
//.......
//关闭
JDBCUtils.close(null,null,connection);
}
long end = System.currentTimeMillis();
System.out.println("传统方式5000次 耗时=" + (end - start));//传统方法耗时 5186
}
5000千次就使用了5秒左右,一个程序每天有大量的使用者使用,所以这中方式是低效的
我们可以使用连接池的方式获取连接
连接池
C3P0
//方式1:相关参数,在程序中指定user,url,password等
@Test
public void testC3P0_01() throws Exception {
//1. 创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2. 通过配置文件mysql.properties获取相关的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关的属性
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//给数据源 comboPooledDataSource 设置相关的参数
//注意:连接管理是由 comboPooledDataSource 来管理
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
comboPooledDataSource.setMaxPoolSize(50);
//测试连接池的效率,测试对mysql 5000次操作
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection();//这个方法就是从 DataSource 接口实现
// System.out.println("连接成功");
connection.close();
}
long end = System.currentTimeMillis();
//c3p0 5000次连接mysql 耗时=226
System.out.println("c3p0 5000次连接mysql 耗时=" + (end - start));
}
查看类图
方式1:相关参数,在程序中指定user,url,password。耗时:226ms
第二种方式 使用配置文件模板来完成。耗时:400ms
@Test
//第二种方式 使用配置文件模板来完成
//1. 将c3p0 提供的 c3p0.config.xml 拷贝到 src目录下
//2. 该文件指定了连接数据库
public void testc3p0_02() throws SQLException {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("cs_student");
//测试5000次连接mysql
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Connection connection = comboPooledDataSource.getConnection();
// System.out.println("连接成功");
connection.close();
}
long end = System.currentTimeMillis();
//c3p0的第二种方式 耗时=400
System.out.println("c3p0的第二种方式(500000) 耗时=" + (end - start));
}
Druid
@Test
public void testDruid() throws Exception {
//1. 加入 Druid jar 包
//2. 加入 配置文件 druid.properties,将文件文件宝贝到项目的src目录
//3. 创建PreparedStatement,读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//4. 创建一个指定参数的数据库,Druid连接池
DataSource dataSource =
DruidDataSourceFactory.createDataSource(properties);
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Connection connection = dataSource.getConnection();
// System.out.println("连结成功");
connection.close();
}
//druid连接池 操作5000次 耗时=388
long end = System.currentTimeMillis();
System.out.println("druid连接池 操作500000次 耗时=" + (end - start));
}
C3P0(配置文件)与Druid对比
数据量小的时候看不到差距,数据量大的时候很明显
例如连接次数50万次
C3P0:1109ms
Druid:295ms
利用德鲁伊改进工具类
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成 ds初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//潘泻getConnection方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭连接,老师再次强调:在数据库连接池技术中,close 不是真的断掉连接
//而是把使用的Connection对下个放回连接池
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException();
}
}
}
测试案例
放心食用
public class JDBCUtilsByDruid_USE {
@Test
public void testSelect() {
System.out.println("使用 druid完成");
//1.得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "select * from actor";
PreparedStatement preparedStatement = null;
ResultSet set = null;
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtilsByDruid.getConnection();
System.out.println(connection.getClass());//class com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
//执行
ResultSet resultSet = preparedStatement.executeQuery();
JDBCUtilsByDruid.close(set,preparedStatement,connection);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String sex = resultSet.getString("sex");
Date borndate = resultSet.getDate("borndate");
String phone = resultSet.getString("phone");
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtilsByDruid.close(set,preparedStatement,connection);
}
}
//使用老师的土方法来解决ResultSet =封装=> ArrayList
@Test
public ArrayList<Actor> testSelectToArrrayList() {
System.out.println("使用 druid完成");
//1.得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "select * from actor";
PreparedStatement preparedStatement = null;
ResultSet set = null;
ArrayList<Actor> list = new ArrayList<>();//创建ArrayList对象,存储actor对象
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtilsByDruid.getConnection();
System.out.println(connection.getClass());//class com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
//执行
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String sex = resultSet.getString("sex");
Date borndate = resultSet.getDate("borndate");
String phone = resultSet.getString("phone");
// System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
//把得到的resultset 的记录,封装到 Actor对象,放入到list集合
list.add(new Actor(id, name, sex, borndate, phone));
}
System.out.println("list集合数据=" + list);
for (Actor actor : list) {
System.out.println("id=" + actor.getId() + "\tname=" + actor.getName());
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtilsByDruid.close(set,preparedStatement,connection);
return list;
}
}
}
Apache——DBUtils
如果关闭连接,Result就不能正常使用了,如何将获取的内容暂时保留或存储下来呢?马上就会解决
DBUtils的原理 将数据提前存储到集合中