目录
第三种方式:使用DriverManager 替代 driver 进行统一管理
第四种方式:使用Class.forName 自动完成注册驱动,简化代码
1. 注册驱动-加载Driver类
2. 获取连接-得到Connection
3. 执行增删改查-发送SQL给mysql执行
4. 释放资源-关闭相关连接
- 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类,动态加载,更加的灵活,减少依赖性
- //使用反射加载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("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");


我使用的版本

- //通过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

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();

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会对其预处理不用,最后和整个程序一起编译


如果放进去还是会有SQL注入的,所以一定不要放


将相同的步骤封装到一个类中,调用时直接使用其方法。
- 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秒左右,一个程序每天有大量的使用者使用,所以这中方式是低效的
我们可以使用连接池的方式获取连接

- //方式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));
- }


- @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));
-
- }
数据量小的时候看不到差距,数据量大的时候很明显
例如连接次数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
testSelectToArrrayList() { - System.out.println("使用 druid完成");
- //1.得到连接
- Connection connection = null;
-
- //2. 组织一个sql
- String sql = "select * from actor";
- PreparedStatement preparedStatement = null;
- ResultSet set = null;
- ArrayList
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;
- }
- }
- }


如果关闭连接,Result就不能正常使用了,如何将获取的内容暂时保留或存储下来呢?马上就会解决
DBUtils的原理 将数据提前存储到集合中
