10-JDBC

JDBC

准备动作

  • 问题1:你知道的存储数据的方式有哪些?

    • 变量,数组,集合:只能对数据进行临时性存储,程序执行结束后,数据就丢失了.
    • IO流:可以把数据进行永久存储,但是不方便用户进行精细化管理.
    • 数据库: 指的就是存储数据的仓库, 本质就是一个文件系统, 可以有规律的对数据存储, 方便用户进行CURD.
      • C:(Create: 增), U:(Update: 改), R: (Read: 查), D: (Delete:删)
      • 数据库才是实际开发中, 我们真正存储数据的地方.
  • 问题2:我们常说的数据库指的是数据库管理系统(DBMS, DataBase Management System), 那么数据库和数据库管理系统之间有什么关系?

    • 数据库管理系统(DBMS):
      • 可以用来管理多个 数据库, 实现数据库的CURD等操作.
      • 类似于: IDEA, 它就是一个工具, 软件, 可以创建多个 项目.
    • 数据库:这个才是真正存储数据表的仓库, 类似于: Java中的项目.
    • 数据表:这个才是真正存储数据的地方, 类似于: Java中的类.
    • 表数据:一条表数据其实就对应一个JavaBean对象.
  • 问题3:我们常说的DBMS大多数都是关系型数据库,那什么是关系型数据库?

    • 所谓的关系型数据库:指的是数据表数据表之间的关系,例如:有一对一,一对多,多对多等…
    • 补充:非关系型数据库:指的是数据表数据表之间没有关系,甚至大多数的非关系型数据库连数据表的概念都没有,采用行列键值对的方式存储数据.
  • 问题4:常见的关系型数据库有哪些?

    • 关系型:MySQL,Oracle,SQLServer,DB2,SyBase,SQLite…
    • 非关系型:MongoDB,Redis,HBase
  • 在windows系统中安装MySQL软件

    • 安装
      1. 安装之前最好关闭防火墙.
      2. 傻瓜式安装, 下一步下一步即可, 详见安装文档.
      3. 安装路径最好不要出现中文, 空格, 特殊符号等.
      4. 最好不要直接安装到盘符目录下.
    • 卸载:
      1. 卸载之前, 先去备份你的数据.
      2. 从控制面板卸载.
      3. 用CCleaner软件清理注册表.
    • 登陆:
      • 方式1: 明文.
        windows徽标键 + 字母R -> cmd -> mysql -uroot -p密码 -> 回车
      • 方式2: 暗文.
        windows徽标键 + 字母R -> cmd -> mysql -u root -p -> 回车 -> 输入密码 -> 回车
    • 两个小问题:
      • 问题1: 10061, MySQL服务没开, 开启此服务即可.
        • 方式1: windows徽标键 + 字母R -> services.msc -> 打开服务列表, 找到mysql服务, 手动开启.
        • 方式2: dos指令实现
          • net start mysql服务名
          • net stop mysql服务名
      • 问题2: using password yes, 说明用户名或者密码错误.
  • MySQL的可视化工具: SQLYog

    • 它类似于你们前边的 DataGrip, 但是DataGrip是重量级的软件, 依赖比较多, 效率相对较慢, 所以我们用: SQLYog.

JDBC相关知识

  • 概述:

    • 全称叫Java DataBase Connectivity, 即: Java数据库连接, 就是通过Java代码操作数据库的技术. 本质就是一坨接口.
  • 回顾:

    • 集合: 就是Java用来存储多个数据的容器, 本质就是一坨类和接口.

      • 接口: Collection, Map, List, Set
      • 类: ArrayList, HashSet, HashMap
    • IO流: 就是Java用来进行数据传输的技术, 本质就是一坨类和接口.

      • 接口: Serializable
      • 类: Reader, Writer, InputStream, OutputStream
  • 驱动:就是设备与设备之间通信的桥梁.

  • 原理:

    • 大白话:
      • 由Sun公司提供统一规范(就是一些类和接口, 就是JDBC规范), 具体的体现和实现交给各个数据库厂商来实现.
    • 详细版:
      • 我们知道, 不同的数据库, 生厂商也不同, 导致驱动规范也不尽相同. 例如: 我们要使用MySQL数据库, 就要安装MySQL的驱动,我们要装Oracle数据库, 就要装Oracle驱动, 因为各个数据库厂商驱动规范不同, 就导致我们要: 用什么数据库,就要学习对应的驱动,
      • 本身Java已经提供了大量的API供我们学习, 我们再额外记忆大量的驱动类, 就会导致学习Java难度增加, 针对于这个情况,
      • Sun公司就和各个数据库厂商协商规定, 由Sun公司提供统一的规则(规范), 具体的实现交给各个数据库厂商来做.
      • 我们不需要关心具体的类是什么, 只要关心Sun公司提供的规范怎么用即可, 这就是: 面向接口编程思想

JDBC的作用:

  1. 连接数据库.
  2. 向数据库发送指令(SQL语句), 获取结果集.
  3. 操作结果集.

JDBC的核心步骤

0. 导入驱动,  其实就是导入jar包, 只要做一次就行.
1. 注册驱动.
2. 获取连接对象.
3. 根据连接对象, 获取可以执行SQL语句的对象.
4. 执行SQL语句, 获取结果集.
5. 操作结果集.
6. 释放资源.

JDBC的案例:

1. 快速入门.
2. JDBC相关API详解.
3. JDBCCURD操作.
4. JDBCUtils工具类的定义和使用.
5. SQL注入攻击问题演示.
6. PreparedStatement预编译功能解决SQL注入攻击问题.
7. PreparedStatementCURD操作.
8. 数据库连接池入门.
9. C3P0Utils工具类的定义和使用.
10. JDBC操作事务.
JDBC入门案例
package com.itheima.jdbc;

import com.mysql.jdbc.Driver;

import java.sql.*;

public class Demo01 {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动.
        DriverManager.registerDriver(new Driver());
        //2. 获取连接对象.
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = conn.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "select * from users;";
        ResultSet rs = stat.executeQuery(sql);
        //5. 操作结果集.
        while (rs.next()) {
            int uid = rs.getInt("uid");
            String username = rs.getString("username");
            String password = rs.getString("password");
            System.out.println(uid + "," + username + "," + password);
        }
        //6. 释放资源.
        conn.close();
        stat.close();
        rs.close();
    }
}
DriverManager类
  • 概述:它表示 驱动管理者类, 主要作用是: 注册驱动 以及 获取连接对象.

  • 作用(成员方法):

    • public static void registerDriver(Driver d);
    • 注册驱动, 连接什么数据库, 就注册谁的驱动, 但是这个方法实际开发不用, 因为该方式会导致驱动注册两次, 具体源码分析如下:
  • com.mysql.jdbc.Driver:

    • 数据库厂商提供的具体驱动类
  • java.sql.Driver:

  • Sun公司提供的JDBC的规范, Driver接口: 表示驱动, 所有的驱动类都得实现它, 这个叫: 面向接口编程.

  • public class com.mysql.jdbc.Driver implements java.sql.Driver {
        static {
            //静态代码块, 一般用来注册驱动的, 这里已经把驱动注册了.
            DriverManager.registerDriver(new Driver());
        }
    }
    
  • public static Connection getConnection(String url, String username, String password);

    • 获取连接对象, 三个参数分别是: 数据库连接字符串, 要连接的数据库的账号, 要连接的数据库的密码.

    • 数据库连接字符串解释:

      • 格式协议:子协议://要连接的主机的ip:端口号/具体的要连接的数据库

      • 例如:jdbc:mysql://127.0.0.1:3306/day16

      • 如果是操作本机, 则可以改为:

        • jdbc:mysql://127.0.0.1:3306/day16
        • jdbc:mysql://localhost:3306/day16
        • jdbc:mysql:**///**day16
DriverManager类详解
package com.itheima.jdbc;

import com.mysql.jdbc.Driver;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Demo02 {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动.
        //DriverManager.registerDriver(new Driver());
        //反射的方式,加载字节码文件进内存
        Class.forName("com.mysql.jdbc.Driver");

        //2. 获取连接对象.
        //Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
        //Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day16", "root", "root");
        Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");

        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = conn.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "select * from users;";
        ResultSet rs = stat.executeQuery(sql);
        //5. 操作结果集.
        while (rs.next()) {
            int uid = rs.getInt("uid");
            String username = rs.getString("username");
            String password = rs.getString("password");
            System.out.println(uid + "," + username + "," + password);
        }
        //6. 释放资源.
        conn.close();
        stat.close();
        rs.close();
    }
}
Connection接口
  • 概述:表示连接对象, 负责Java和数据库的连接的, 也可以获取: 可以执行SQL语句的对象, 以及进行 事务管理
  • 作用(成员方法):
    1. 获取可以执行SQL语句的对象.
      • public Statement createStatement();
        • 获取可以执行SQL语句的对象, 不具有预编译功能, 不能解决SQL注入攻击问题.
      • public PreparedStatement prepareStatement(String sql);
        • 获取可以执行SQL语句的对象, 具有预编译功能, 能解决SQL注入攻击问题.
        • PreparedStatement接口是 Statement接口的 子接口.
    2. 事务管理
      • public void setTransactionIsolation(int level); 设置事物的隔离级别
      • public void commit(); 提交事务
      • public void rollback(); 事务回滚
      • public void setAutoCommit(boolean flag); 开启事务
Statement接口
  • 概述:表示可以执行SQL语句的对象, 主要功能有: 执行SQL语句, 获取结果集; 进行批处理管理.
  • 作用(成员方法):
    1. 获取可以执行SQL语句的对象.
      • public int executeUpdate(String sql);
        • 执行SQL语句, 获取结果集, 执行的是: 更新语句, 结果集是 受影响的行数
      • public ResultSet executeQuery(String sql);
        • 执行SQL语句, 获取结果集, 执行的是: 查询语句, 结果集是 虚拟的一张表
    2. 批处理 注意: 批处理只针对于 更新语句(增删改) 有效
      • public void addBatch(String sql); 添加批处理
      • public int[] executeBatch(); 执行批处理, 获取结果集
      • public void clearBatch(); 清空批处理
ResultSet接口
  • 概述:表示 执行查询语句后的结果集对象, 可以把它看做一张虚拟的表.
  • 作用(成员方法):
    1. 获取可以执行SQL语句的对象.
      • public boolean next();
        • 判断结果集中是否有下一条数据, 类似于: Iterator#hasNext()方法.
      • public Xxx getXxx(String columnName);
        • 根据列名, 获取该列的信息, 掌握, 因为它更通用.
      • public Xxx getXxx(int columnIndex);
        • 根据列的编号, 获取该列的信息, 编号从 1 开始.
ResultSet接口详解
package com.itheima.jdbc;

import com.mysql.jdbc.Driver;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Demo02 {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动.
        //反射的方式,加载字节码文件进内存
        Class.forName("com.mysql.jdbc.Driver");

        //2. 获取连接对象.
        Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");

        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = conn.createStatement();

        //4. 执行SQL语句, 获取结果集.
        String sql = "select uid, username, password from users;";
        //String sql = "select username, uid, password from users;";
        ResultSet rs = stat.executeQuery(sql);
        
        //5. 操作结果集.
        while (rs.next()) {
            //根据列名获取元素
            /*int uid = rs.getInt("uid");
            String username = rs.getString("username");
            String password = rs.getString("password");*/

            //根据列的编号获取
            int uid = rs.getInt(1);
            String username = rs.getString(2);
            String password = rs.getString(3);
            System.out.println(uid + "," + username + "," + password);
        }
        //6. 释放资源.
        conn.close();
        stat.close();
        rs.close();
    }
}
JDBC的CURD操作
package com.itheima.jdbc;

import org.junit.Test;

import java.sql.*;

public class Demo03 {
    //JDBC的 查 的动作
    @Test
    public void show1() throws Exception {
        //1. 注册驱动.
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接对象.
        Connection coon = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = coon.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "select * from users;";
        ResultSet rs = stat.executeQuery(sql);
        //5. 操作结果集.
        while (rs.next()) {
            System.out.println(rs.getInt("uid") + "," + rs.getString("username") + "," + rs.getString("password"));
        }
        //6. 释放资源.
        coon.close();
        stat.close();
        rs.close();
    }

    //JDBC的 增 的动作
    @Test
    public void show2() throws Exception {
        //1. 注册驱动.
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接对象.
        Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day16", "root", "root");
        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = conn.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "insert into users values(null,'admin_04','pw4444');";
        int i = stat.executeUpdate(sql);
        //5. 操作结果集.
        System.out.println(i > 0 ? "添加成功" : "添加失败");
        //6. 释放资源.
        conn.close();
        stat.close();

    }

    //JDBC的 改 的动作
    @Test
    public void show3() throws Exception {
        //1. 注册驱动.
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接对象.
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = conn.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "update users set username = '刘亦菲' , password = 'pw_lyf' where uid = 4 ;";
        int i = stat.executeUpdate(sql);
        //5. 操作结果集.
        System.out.println(i>0?"修改成功":"修改失败");
        //6. 释放资源
        conn.close();
        stat.close();
    }

    //JDBC的 删 的动作
    @Test
    public void show4() throws Exception {
        //1. 注册驱动.
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接对象.
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = conn.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "delete from users where username = '刘亦菲';";
        int i = stat.executeUpdate(sql);
        //5. 操作结果集.
        System.out.println(i>0?"删除成功":"删除失败");
        //6. 释放资源
        conn.close();
        stat.close();
    }
}
JDBCUtils工具类的CURD操作

JDBCUtils类

package com.itheima.jdbc_utils;

import java.sql.*;

//自定义的JDBCUtils工具类,用来操作:JDBC代码的
public class JDBCUtils {
    //1.构造方法私有化
    private JDBCUtils() {
    }

    //成员都是静态的
    //2.通过静态代码块的方式注册驱动
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //3.定义方法,用来获取连接对象
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }

        //如果有问题,返回null
        return null;
    }

    //4.释放资源
    public static void release(Connection conn, Statement stat, ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
                rs = null;      //GC会优先回收null对象
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                if (stat != null) {
                    stat.close();
                    stat = null;
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                try {
                    if (conn != null) {
                        conn.close();
                        conn = null;
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
}

测试类

package com.itheima.jdbc;

import com.itheima.jdbc_utils.JDBCUtils;
import org.junit.Test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class Demo04 {
    //JDBC的 查 的动作
    @Test
    public void show1() throws Exception {
        //1. 注册驱动.
        //2. 获取连接对象.
        Connection coon = JDBCUtils.getConnection();
        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = coon.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "select * from users;";
        ResultSet rs = stat.executeQuery(sql);
        //5. 操作结果集.
        while (rs.next()) {
            System.out.println(rs.getInt("uid") + "," + rs.getString("username") + "," + rs.getString("password"));
        }
        //6. 释放资源.
        JDBCUtils.release(coon, stat, rs);
    }

    //JDBC的 增 的动作
    @Test
    public void show2() throws Exception {
        //1. 注册驱动.
        //2. 获取连接对象.
        Connection conn = JDBCUtils.getConnection();
        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = conn.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "insert into users values(null,'admin_06','pw4666');";
        int i = stat.executeUpdate(sql);
        //5. 操作结果集.
        System.out.println(i > 0 ? "添加成功" : "添加失败");
        //6. 释放资源.
        JDBCUtils.release(conn, stat, null);
    }

    //JDBC的 改 的动作
    @Test
    public void show3() throws Exception {
        //1. 注册驱动.
        //2. 获取连接对象.
        Connection conn = JDBCUtils.getConnection();
        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = conn.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "update users set username = '刘亦菲' , password = 'pw_lyf' where uid = 5 ;";
        int i = stat.executeUpdate(sql);
        //5. 操作结果集.
        System.out.println(i > 0 ? "修改成功" : "修改失败");
        //6. 释放资源
        JDBCUtils.release(conn, stat, null);
    }

    //JDBC的 删 的动作
    @Test
    public void show4() throws Exception {
        //1. 注册驱动.
        //2. 获取连接对象.
        Connection conn = JDBCUtils.getConnection();
        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        Statement stat = conn.createStatement();
        //4. 执行SQL语句, 获取结果集.
        String sql = "delete from users where username = '刘亦菲';";
        int i = stat.executeUpdate(sql);
        //5. 操作结果集.
        System.out.println(i > 0 ? "删除成功" : "删除失败");
        //6. 释放资源
        JDBCUtils.release(conn, stat, null);
    }
}

SQL注入攻击

  • 概述:
    • 所谓的SQL注入攻击,指的是SQL语句中的部分内容是要求用户键盘录入的,此时如果用户录入一些非法值,从而改变了我们SQL语句的结构,就有可能引发一系列的安全问题,这个问题就叫:SQL注入攻击,个别人会叫"猪肉攻击".
案例:
	模拟用户登陆.
"标准"SQL语句:
	select * from users where username = '用户录入的账号' and password = '用户录入的密码';
听话的用户:
	账号: 刘亦菲
	密码: pw11112
"小屌丝"用户:
	账号: sdkfsd
	密码: skdfs ' or '1=1
"老屌丝"用户:
	账号: sldfs
	密码: sdkfs '; drop database day17; '
  • 产生原因:

    • 之所以会产生SQl注入攻击问题,原因是因为用户录入的非法值或者特殊符号被我们的SQL语句给识别成关键字或者特殊符号了, 从而改变了我们SQL语句的结构, 引发的问题.
  • 解决思路:

    1. 预先对SQL语句进行 预编译, 即: 在此时已经决定了SQL语句中各部分的内容(即: SQL语句的结构), 把一会儿需要用户录入的地方先用 占位符 处理.
    2. 预编译之后, 不管用户录入什么数据, 我们都只是当做普通的字符串处理.
    3. SQL语句的预编译功能, 我们可以通过 PreparedStatement接口实现

SQL注入攻击问题_代码演示

package com.itheima.jdbc;

import com.itheima.jdbc_utils.JDBCUtils;

import java.sql.*;
import java.util.Scanner;

public class Demo05 {
    public static void main(String[] args) throws Exception {
        //1. 提示用户录入账号和密码,并接收
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入您的账号:");
        String uname = sc.nextLine();
        System.out.println("请输入您的密码:");
        String pwd = sc.nextLine();

        //2. 获取连接对象.
        Connection conn = JDBCUtils.getConnection();

        //3. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "select * from users where username = ? and password = ? ;";
        PreparedStatement ps = conn.prepareStatement(sql);

        //4. 执行SQL语句, 获取结果集.
        //给占位符设置值
        ps.setString(1,uname);		//qweretw
        ps.setString(2,pwd);		//dasd'or'1=1	登录成功

        //执行sql语句
        ResultSet rs = ps.executeQuery();

        //5. 操作结果集.
        System.out.println(rs.next() ? "登录成功" : "登录失败");
        
        //6. 释放资源.
        JDBCUtils.release(conn, ps, rs);
    }
}

prepareStatement的CURD操作

package com.itheima.jdbc;

import com.itheima.jdbc_utils.JDBCUtils;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class Deno06 {
    //查
    @Test
    public void show1() throws Exception {
        //1. 获取连接对象.
        Connection conn = JDBCUtils.getConnection();
        //2. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "select * from users where uid = ?;";
        PreparedStatement ps = conn.prepareStatement(sql);
        //3. 执行SQL语句, 获取结果集.
        ps.setInt(1, 4);
        ResultSet rs = ps.executeQuery();
        //4. 操作结果集.
        while (rs.next()) {
            System.out.println("uid" + "," + rs.getString("username") + "," + rs.getString("password"));
        }
        //5. 释放资源.
        JDBCUtils.release(conn, ps, rs);
    }

    //增
    @Test
    public void show2() throws Exception {
        //1. 获取连接对象.
        Connection coon = JDBCUtils.getConnection();
        //2. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "insert into users values(null,?,?)";
        PreparedStatement ps = coon.prepareStatement(sql);
        //3. 执行SQL语句, 获取结果集.
        ps.setString(1, "李四");
        ps.setString(2, "pw_465465");
        int i = ps.executeUpdate();
        //4. 操作结果集.
        System.out.println(i > 0 ? "添加成功" : "添加失败");
        //5. 释放资源.
        JDBCUtils.release(coon, ps, null);
    }

    //改
    @Test
    public void show3() throws Exception {
        //1. 获取连接对象.
        Connection conn = JDBCUtils.getConnection();
        //2. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "update users set username = ? where uid = ?;";
        PreparedStatement ps = conn.prepareStatement(sql);
        //3. 执行SQL语句, 获取结果集.
        ps.setString(1, "佟丽娅");
        ps.setInt(2, 4);
        int i = ps.executeUpdate();
        //4. 操作结果集.
        System.out.println(i > 0 ? "修改成功" : "修改失败");
        //5. 释放资源.
        JDBCUtils.release(conn, ps, null);
    }

    //删
    @Test
    public void show4() throws Exception {
        //1. 获取连接对象.
        Connection conn = JDBCUtils.getConnection();
        //2. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "delete from users where uid = ?;";
        PreparedStatement ps = conn.prepareStatement(sql);
        //3. 执行SQL语句, 获取结果集.
        ps.setInt(1, 3);
        int i = ps.executeUpdate();
        //4. 操作结果集.
        System.out.println(1 > 0 ? "删除成功" : "删除失败");
        //5. 释放资源.
        JDBCUtils.release(conn, ps, null);
    }
}

数据库连接池

简介:

  • 概述:

    • 实际开发中,当我们需要使用大量生命周期短的连接对象时,每次频繁创建销毁非常消耗系统资源的,针对于这种情况。
    • 我们可以创建一个池子出来,里边放写一些连接对象,用的时候从里边拿,用完之后再放回去,这样的好处是:节约资源,提高效率,而这个池就叫:数据库连接池,DataBase,Connection pool(简称:DBCP)
  • 分类:

    • DBCP:属于Apache软件基金组织,不能自动回收空闲连接.
    • C3P0:属于Apache软件基金组织,能自动回收空闲连接.
      细节:JAVA框架的底层部分只要涉及到数据库连接池了,用的都是 C3P0
    • Druid:属于阿里巴巴,中文名叫:德鲁伊,采用分布式事务的方式实现的,能够更好的兼容分布式框架.
数据库连接池的 操作步骤:C3P0为例
  1. 导入c3po-config.xml文件,该文件必须放到:src目录下,因为C3P0数据库连接池对象会自动读取改配置文件的信息.
  2. 导入C3P0的jar包,最短的那个(c3p0-0.9.1.2.jar
  3. 创建数据库连接池对象.
    方式1:读取配置文件中,默认标签的信息.
    方式2:读取配置文件中,指定标签的信息.
  4. 从数据库连接池中获取连接对象
  5. 关闭连接对象,即:Connection#close()方法,comm.colse(),解释如下:
    • 虽然都是调用conn.close(),来 “关闭” 连接对象,但是:
      • 如果连接对象是我们自己创建的,调用conn.close(),
        • 就是:销毁连接对象.
      • 如果连接对象是我们从数据库连接池中获取的,调用conn.close(),
        就是:自动归还连接对象到数据库连接池中,因为连接池已经重写了Connection#close()方法.
数据库连接池入门
package com.itheima.jdbc;

import com.itheima.jdbc_utils.JDBCUtils;
import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class Demo07 {
    public static void main(String[] args) throws Exception {
        //查询uid为1的账号信息
        //1. 获取连接对象.
        //方式1:自己创建,这个连接对象是我们自己创建的,调用conn.close()方法的时候,就是:销毁连接对象
        //Connection conn = JDBCUtils.getConnection();

        //方式2:从数据库连接池中获取
        //创建数据库连接池,读取:默认参数信息,即:空参构造
        //ComboPooledDataSource cpds = new ComboPooledDataSource();

        //创建数据库连接池,读取:指定参数信息,即:带参构造
        ComboPooledDataSource cpds = new ComboPooledDataSource("otherc3p0");

        //从数据库连接池中获取连接对象
        Connection conn = cpds.getConnection();

        //2. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "select * from users;";
        PreparedStatement ps = conn.prepareStatement(sql);

        //3. 执行SQL语句, 获取结果集.
        ResultSet rs = ps.executeQuery();

        //4. 操作结果集.
        while (rs.next()) {
            System.out.println(rs.getInt("uid") + "," + rs.getString("username") + "," + rs.getString("password"));
        }

        //5. 释放资源.
        JDBCUtils.release(conn, ps, rs);

        /* 虽然都是自己conn.close(),但是如果是自己创建的连接对象,就是:销毁,
        如果是从池子中获取的,就是:自动归还 */
    }
}
  • 问题:

    • 虽然我们目前已经实现了数据库连接池的使用, 但是这种方式的效率并不高, 因为目前的数据库连接池对象没有实现共享,
    • 即: 在这里我们创建了一个数据库连接池, 内置5个连接对象, 但是我们就用1个, 其它4个就浪费掉了, 这样做, 还没有之前的方式效率高, 如何解决?
  • 答案:

    • 把C3P0数据库连接池定义成共享的, 即: 自定义C3P0Utils工具类.
C9P0Utils工具类的定义和使用
package com.itheima.jdbc;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

//自定义的C3P0Utils工具类
public class C3P0Utils {
    //1.构造方法私有化
    private C3P0Utils() {
    }

    //成员都是静态的
    //2.定义一个私有的,静态的成员常量表示数据库连接池
    //DataSource:是JDBC指定的规范,即所有的数据库连接池对象都是它的子类
    public static final DataSource ds = new ComboPooledDataSource();

    //3.对外提供方法,用来获取连接对象
    public static DataSource  getDataSource(){
       return ds;
    }

    //4.对外提供方法,用来获取连接对象
    public static Connection getConnection(){
        try {
            return ds.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    //5.释放资源
    public static void release(Connection conn, Statement stat, ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
                rs = null;      //GC会优先回收null对象
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                if (stat != null) {
                    stat.close();
                    stat = null;
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                try {
                    if (conn != null) {
                        conn.close();
                        conn = null;
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
}
package com.itheima.jdbc;

import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class Demo06 {
    //查
    @Test
    public void show1() throws Exception {
        //1. 获取连接对象.
        //方式1:自己创建
        //Connection conn = JDBCUtils.getConnection();

        //方式2:先获取数据库连接池,再从池子中获取连接对象
        /*DataSource ds = C3P0Utils.getDataSource();
        Connection conn = ds.getConnection();*/

        //方式3:直接从数据库连接池中获取连接对象
        Connection conn = C3P0Utils.getConnection();

        //2. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "select * from users where uid = ?;";
        PreparedStatement ps = conn.prepareStatement(sql);

        //3. 执行SQL语句, 获取结果集.
        ps.setInt(1, 1);
        ResultSet rs = ps.executeQuery();
        //4. 操作结果集.
        while (rs.next()) {
            System.out.println("uid" + "," + rs.getString("username") + "," + rs.getString("password"));
        }
        //5. 释放资源.
        C3P0Utils.release(conn, ps, rs);
    }

    //增
    @Test
    public void show2() throws Exception {
        //1. 获取连接对象.
        Connection coon = C3P0Utils.getConnection();
        //2. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "insert into users values(null,?,?)";
        PreparedStatement ps = coon.prepareStatement(sql);
        //3. 执行SQL语句, 获取结果集.
        ps.setString(1, "李四");
        ps.setString(2, "pw_465465");
        int i = ps.executeUpdate();
        //4. 操作结果集.
        System.out.println(i > 0 ? "添加成功" : "添加失败");
        //5. 释放资源.
        C3P0Utils.release(coon, ps, null);
    }

    //改
    @Test
    public void show3() throws Exception {
        //1. 获取连接对象.
        Connection conn = C3P0Utils.getConnection();
        //2. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "update users set username = ? where uid = ?;";
        PreparedStatement ps = conn.prepareStatement(sql);
        //3. 执行SQL语句, 获取结果集.
        ps.setString(1, "佟丽娅");
        ps.setInt(2, 4);
        int i = ps.executeUpdate();
        //4. 操作结果集.
        System.out.println(i > 0 ? "修改成功" : "修改失败");
        //5. 释放资源.
        C3P0Utils.release(conn, ps, null);
    }

    //删
    @Test
    public void show4() throws Exception {
        //1. 获取连接对象.
        Connection conn = C3P0Utils.getConnection();
        //2. 根据连接对象, 获取可以执行SQL语句的对象.
        String sql = "delete from users where uid = ?;";
        PreparedStatement ps = conn.prepareStatement(sql);
        //3. 执行SQL语句, 获取结果集.
        ps.setInt(1, 3);
        int i = ps.executeUpdate();
        //4. 操作结果集.
        System.out.println(1 > 0 ? "删除成功" : "删除失败");
        //5. 释放资源.
        C3P0Utils.release(conn, ps, null);
    }
}

事务

简介:

  • 概述:事务指的是逻辑上的一组操作,组成该操作的各个逻辑单元,要么全部执行成功,要么全部执行失败.

    • 大白话理解:同生共死.
  • 特点:

    • 原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位.事务中的操作要么都发生,要么都不发生.
    • 一致性(Consistency) 事务前后 数据的完整性必须保持一致.
    • 隔离性(Isolation) 事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其他用的的事务所干扰多个并发事务之间数据相互隔离.
    • 持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障不应该对其有任何影响.
  • 如果不考虑隔离性,可能发生如下的问题:

    • 关于读:
      • 脏读:也叫:读-未提交
        指的是一个事务读取到了另一个事务还没有来得及提交的数据,导致多次查询结果不一致.
      • 不可重复读:也叫:读-已提交(修改)
        指的是一个事务读取到了另一个事务已经提交的(修改)数据,导致多次查询结果一致.
      • 虚读:也叫:读-已提交(插入)
        指的是一个事务读取到了另一个事务已经提交的(插入)数据,导致多次查询结果不一致.
    • 关于写:丢失更新.
  • 所谓的考虑隔离性, 就是所谓的 隔离级别, 具体如下:

隔离级别从小到大分别是: 
		read uncommitted < read committed < repeatable read < serializable
    安全级别从小到大分别是: 
		read uncommitted < read committed < repeatable read < serializable
    效率从大到小分别是:    
		read uncommitted > read committed > repeatable read > serializable
  • 隔离级别详解:
    read uncommitted:
        会发生问题: 3, 脏读, 不可重复读, 虚读.
        已解决问题: 0.
    read committed:
        会发生问题: 2, 不可重复读, 虚读.
        已解决问题: 1, 脏读.
    repeatable read:
        会发生问题: 1, 虚读.
        已解决问题: 2, 脏读, 不可重复读.
    serializable:
        会发生问题: 0.
        已解决问题: 3, 脏读, 不可重复读, 虚读.
  • 细节:
  1. MySQL 和 Oracle的区别如下:
    • MySQL:默认隔离级别是 repeatable read, 且默认开启了事务的提交功能, 每一个SQL语句都是一个单独的事务, 端口号是: 3306
    • Oracle:默认隔离级别是: read committed, 没有开启事务的自动提交功能, 需要手动设置. 端口号是1521
  2. SQL语句中, 关于事务的SQL语句如下:
    show variables like ‘%auto%’ 查看是否开启事务的自动提交功能.
    select @@tx_isolation; 查看事务的隔离级别
    begin 开启事务
    start transaction 开启事务
    commit 提交事务, 即: 把执行后的结果持久性的写到数据表中.
    rollback 事务回滚, 即: 把数据还原事务还没有执行之前的状态.
    set session transaction isolation level 隔离级别; 临时的设置事务的隔离级别为指定的级别.
JDBC操作事务,模拟转账
package com.itheima.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo08 {
    public static void main(String[] args) {
        //需求:张三 -> 李四 转 1000
        //方式1:  不采用事务
        //method01();

        //方式2: 采用事务
        Connection conn = null;
        Statement stat = null;
        try {
            //1. 获取连接对象.
            conn = C3P0Utils.getConnection();
            //2. 根据连接对象, 获取可以执行SQL语句的对象.
            stat = conn.createStatement();
            //3. 执行SQL语句, 获取结果集.
            String sql1 = "update account set money = money - 1000 where aid = 1;";
            String sql2 = "update account set money = money + 1000 where aid = 2;";

            //开启事务,关闭事务的自动提交功能,相当于开启事务,什么时候我提交或者回滚事务了,说明事务结束
            conn.setAutoCommit(false);

            //扣钱的动作
            int a = stat.executeUpdate(sql1);

            //System.out.println(1 / 0);

            //价钱的动作
            int b = stat.executeUpdate(sql2);

            //4. 操作结果集.
            if (a == b && a == 1) {
                conn.commit();      //提交事务
                System.out.println("转账成功");
            }
        } catch (Exception e) {
            try {
                conn.rollback();       //事务回滚
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            System.out.println("转账失败");
        } finally {
            //5. 释放资源.
            C3P0Utils.release(conn, stat, null);
        }

    }

    private static void method01() {
        Connection conn = null;
        Statement stat = null;
        try {
            //1. 获取连接对象.
            conn = C3P0Utils.getConnection();
            //2. 根据连接对象, 获取可以执行SQL语句的对象.
            stat = conn.createStatement();
            //3. 执行SQL语句, 获取结果集.
            String sql1 = "update account set money = money - 1000 where aid = 1;";
            String sql2 = "update account set money = money + 1000 where aid = 2;";

            //扣钱的动作
            int a = stat.executeUpdate(sql1);

            System.out.println(1 / 0);

            //价钱的动作
            int b = stat.executeUpdate(sql2);

            //4. 操作结果集.
            if (a == b && a == 1) {
                System.out.println("转账成功");
            }
        } catch (Exception e) {
            System.out.println("转账失败");
        } finally {
            //5. 释放资源.
            C3P0Utils.release(conn, stat, null);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值