JDBC 源码模拟

JDBC全面复习

1.0什么是JDBC?

Java DataBase Connectivity(java语言联结数据库)

1. JDBC本质是什么?

JDBC是sun公司制定的一套接口(interface)

接口都有调用者和实现者

我们可以有面向接口调用,面向接口写实现类.这都属于面向对象编程

  • 复习知识点:为什么要有面向接口编程?

解耦合:降低程序的耦合性,提高程序的扩展力

多态机制就是非常典型:面向抽象编程(不要面向具体编程)

建议:

Animal a=new Cat();
Animal b=new Dog();
//写一个具体方法
public void feed(Animal a)//面向父类型编程
    
   不建议使用具体编程

2.思考:为什么sun制定一套JDBC接口呢?

因为每一个oracle数据库有自己的原理

MySQL也有自己的底层原理

MS sqlserver数据库也有自己的原理

.....

每一个数据库产品都有自己独特的实现原理

2.0编写程序模拟JDBC本质

2.1设计一个接口模拟JDBC

package com.rango;
/*SUN公司负责制定这套JDBC接口
* */
public interface JDBC_SUN {
    /*
    * 连接数据库的方法
    * */
    void getConnection();
}

几家数据库公司对数据库进行实现

mysql公司:

package com.rango;
/*mysql厂家负责编写JDBC接口的实现类
* 这就是一个MySQL驱动文件
* */
public class MySql_Implements implements JDBC_SUN {

    @Override
    /*编写这些代码和我们java程序员来说没有关系
    * 这里的代码涉及到数据库的实现原理
    * */
    public void getConnection() {
        System.out.println("连接Mysql数据库成功!");
    }
}

oracle公司:

package com.rango;
/*这就是一个Oracle驱动
* */
public class Oracle_Implement implements JDBC_SUN{
    public void getConnection() {

        System.out.println("连接Oracle数据库成功!");
    }
}

sqlserver实现:

package com.rango;
/*实现类就是一个驱动
* 很多.class文件,打成一个jar包
* 这就是一个Sqlserver驱动
*
* */
public class Sqlserver implements JDBC_SUN {
    public void getConnection() {

        System.out.println("连接Sqlserver数据库成功!");
    }
}

程序员需要面向接口进行编程

package com.rango;
/*java程序员角色
* 不需要关心具体是哪个品牌的数据库,只需要面向JDBC接口写代码
* 面向接口编程,面向抽象编程,不用面向具体编程
* */


public class JavaProgram {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//             JDBC_SUN jdbc=new MySql_Implements();
//             jdbc.getConnection();
//                             创建对象可以使用反射机制
        Class v = Class.forName("com.rango.MySql_Implements");

        JDBC_SUN jdbc=(JDBC_SUN) v.newInstance();
        //下面代码是面向接口调用方法
        jdbc.getConnection();
    }
}

2.2使用Resource Bundle进行读取className

前提:可以要解决一些路径问题:ResourceBundle来读取配置文件及路径问题

此时我们Resource Bundle的配置可以这么写

jdbc_.properties

className=com.rango.MySql_Implements
package com.rango;
/*java程序员角色
* 不需要关心具体是哪个品牌的数据库,只需要面向JDBC接口写代码
* 面向接口编程,面向抽象编程,不用面向具体编程
* */


import java.util.Enumeration;
import java.util.ResourceBundle;

public class JavaProgram {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//             JDBC_SUN jdbc=new MySql_Implements();
//             jdbc.getConnection();
 /*                           创建对象可以使用反射机制
        Class v = Class.forName("com.rango.MySql_Implements");

        JDBC_SUN jdbc=(JDBC_SUN) v.newInstance();
        下面代码是面向接口调用方法
        jdbc.getConnection();*/
    //创建对象还可以使用资源绑定器
        ResourceBundle budle = ResourceBundle.getBundle("jdbc_");
        String className = budle.getString("className");
        // String className="???";
        Class<?> v = Class.forName(className);
        JDBC_SUN jdbc =(JDBC_SUN)  v.newInstance();
        jdbc.getConnection();
    }
}

3.0JDBC编程六步骤(要背)

JDBC本质是一套接口

JDBC开发前的准备工作.下载架包配置到,环境变量里面去

JDBC编程需要会被

  1. 第一步:注册驱动(作用:告诉java程序,即将要连接的是哪个品牌的数据库)
  2. 第二步:获取连接(表示JVM的进程,和数据库进程之间的通道,打开了,这属于进程之间的通信,重量级的,使用完成之后,一定要关闭)
  3. 第三步:获取数据库操作对象(专门执行sql语句的对象)
  4. 第四步:执行SQL语句,(DQL,DML)
  5. 第五步:处理查询结果集(只有当第四步执行的是select语句的时候)
  6. 第六步;释放资源(使用完资源之后一定要关闭资源,java和数据库属于进程间的通信,开启之后一定要关闭)

3.1注册驱动

简单测试


public class JDBCTest01 {

//        第一步:注册驱动,设置一个驱动的类名字,利用反射机制申请对象
    //这里我们需要到JDBC实现类的包下面找
        private static final String DBDriver  ="com.mysql.jdbc.Driver" ;

        public static void main(String[] args){
//         第二步获取连接
            try{
                Class.forName(DBDriver);
                System.out.println("测试成功");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
//         获取数据库操作对象
//         执行SQL语句
//         处理结果集
//         关闭通道

    }
}

驱动常常使用的两个类

public interface Driver

每个驱动程序类必须实现的接口。
Java SQL框架允许多个数据库驱动程序。

每个驱动程序都应该提供一个实现Driver接口的类

另外一个

Class DriverManager
java.lang.Object
java.sql.DriverManager

常常使用的几个方法

修饰符和类型(Modifier and Type) Method and Description
static Driver getDriver(String url)
尝试查找了解给定URL的驱动程序。
static Connection getConnection(String url)
尝试建立与给定数据库URL的连接。
static Connection getConnection(String url, Properties info)
尝试建立与给定数据库URL的连接。
static Connection getConnection(String url, String user, String password)
尝试建立与给定数据库URL的连接。
static void registerDriver(Driver driver)
注册与给定的驱动程序 DriverManager 。

getConnection(String url, Properties info) 注意:如果一个属性被指定为的一部分url和在还指定Properties对象,它是实现定义哪个值将优先。 为了最大可移植性,应用程序应仅指定一次属性。

3.2驱动代表的意义

  try{
//            1.注册驱动
//            多态父类型指向子类型对象
            Driver driver = new Driver();
            DriverManager.registerDriver(driver);
//            获取连接
            /*url:统一资源定位符(网络中资源的绝对路径)
            *http://61.135.169.121:80/index.html
            * 一个url包括什么?
            * 协议http://通信协议
            * IP服务器IP地址
            * 端口服务器软件上的端口
            * 资源名index.html
            * jdbc:mysql://127.0.0.1:3306/test_db
            * 协议:jdbc:mysql://
            * IP:127.0.0.1也可以写localhost文件
            * 3306位数据库端口号
            * 具体 数据库名字
            * 什么是通信协议?有什么用?
            * 通信协议是通信之前就提前定好的数据传送格式
            * 数据表具体怎么传数据,格式提前定好
            * 数据包怎么传输,格式提前定好
            * */
       String url="jdbc:mysql://127.0.0.1:3306/test_db";
            String user="root";
            String password="123456";
            Connection connection = DriverManager.getConnection(url,user,password);

4.0执行sql与释放资源

这里使用一个接口

java.sql
Interface Connection

我们可以使用Connection返回的一个方法

修饰符和类型 方法和描述
Statement Statement createStatement()
创建一个 Statement对象,用于将SQL语句发送到数据库。
Statement createStatement(int resultSetType, int resultSetConcurrency)
创建一个 Statement对象,该对象将生成具有给定类型和并发性的 ResultSet对象。
Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
创建一个 Statement对象,将产生 ResultSet对象具有给定类型,并发性和可保存性。

创建一个Statement对象,用于将SQL语句发送到数据库。 没有参数的SQL语句通常使用Statement对象执行。 如果相同的SQL语句执行了很多次,那么使用PreparedStatement对象可能会更有效。

4.1mysql jdbc url具体参数全解

mysql jdbc url具体参数全解。mysql jdbc url具体参数详解。mysql jdbc url具体参数什么意思。

MySQL的 JDBC URL格式:

jdbc:mysql://localhost:3306/easonjim?profileSQL=true

对应中文环境,通常MySQL连接URL可以设置为:

jdbc:mysql://localhost:3306/test?user=root&password=&useUnicode=true&characterEncoding=gbk&autoReconnect=true&failOverReadOnly=false

需要注意的是,在XML配置文件中,URL中的&符号需要转义。比如在Tomcat的server.xml中配置数据库连接池时,MySQL JDBC URL样例如下:

jdbc:mysql://localhost:3306/test?user=root&amp;password=&amp;useUnicode=true&amp;characterEncoding=gbk
&amp;autoReconnect=true&amp;failOverReadOnly=false

常用具体参数:

参数名称 参数说明 缺省值
useUnicode 是否使用Unicode字符集,如果参数characterEncoding设置为gb2312或gbk,本参数值必须设置为true false
characterEncoding 当useUnicode设置为true时,指定字符编码。比如可设置为gb2312或gbk,utf8 false
autoReconnect 当数据库连接异常中断时,是否自动重新连接? false
autoReconnect 当数据库连接异常中断时,是否自动重新连接? false

5.0类加载方式注册驱动

使用Driver方式进行驱动注册

1.注册驱动
//            多态父类型指向子类型对象
            Driver driver = new Driver();
            DriverManager.registerDriver(driver);

我们查看Driver中的源码

 static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }

这是我们自己写的静态代码块

  
        try {//执行一个类中的静态代码块    
           //1.0注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.0获取连接
            Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3360/test_db?useUnicode=true" +
                    "&characterEncoding=utf8","root","123456");
        }catch (ClassNotFoundException | SQLException e){
            e.printStackTrace();
        }

5.0从属性资源文件中读取连接数据库信息

5.1使用资源绑定器绑定属性文件

我们先编写一个jdbc.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=utf8
user=root
password=123456

在开始测试

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ResourceBundle;

public class JDBCTest03 {
    public static void main(String[] args){

        try {//执行一个类中的静态代码块
           //1.0注册驱动
            ResourceBundle bundle = ResourceBundle.getBundle("jdbc_");
            String driver = bundle.getString("driver");
            String url = bundle.getString("url");
            String password = bundle.getString("password");
            String user = bundle.getString("user");
            Class.forName(driver);
            Connection connection = DriverManager.getConnection(url, user, password);
            //
            System.out.println("连接数据库成功!");
        }catch (ClassNotFoundException | SQLException e){
            e.printStackTrace();
        }
    }
}

6.0处理结果集

这里我们使用ResultSet对象返回查询结果

executeQuery
ResultSet executeQuery(String sql)
throws SQLException

这里我们知道

int executeUpdate(insert/delete/update)

ResultSet executeQuery(select)

我们先研究ResultSet 的一些方法

修饰符和类型 方法和描述 使用
boolean next() 将光标从当前位置向前移动一行 将光标从当前位置向前移动一行。 ResultSet光标最初位于第一行之前; 第一次调用方法next使第一行成为当前行; 第二个调用使第二行成为当前行,依此类推。
boolean previous() 将光标移动到此 ResultSet对象中的上一行。 当调用previous方法返回false时,光标位于第一行之前。 任何调用需要当前行的ResultSet方法将导致抛出SQLException 。
int getRow() 检索当前行号。 检索当前行号。 第一行是第1行,第2行等等。
注意:对于ResultSet s,支持getRow方法是可选的,结果集类型为TYPE_FORWARD_ONLY
String getString(int columnIndex) String getString(int columnIndex) throws SQLException throws SQLException这个检索的当前行中指定列的值 ResultSet对象为 String的Java编程语言。
String getString(String columnLabel) 这个检索的当前行中指定列的值 ResultSet对象为 String的Java编程语言。 这个检索的当前行中指定列的值 ResultSet对象为 String的Java编程语言。参数
columnLabel - 使用SQL AS子句指定的列的标签。 如果未指定SQL AS子句,则该标签是列的名称

6.1如何取数据

如何获取查询

ResultSetMetaData

获取有关ResultSet对象中列的类型和属性的信息的对象

import java.sql.*;
import java.util.ResourceBundle;

public class JDBCTest04 {
    //打开数据库连接通道,使用数据库查询对象
    public static Connection connection=null;
    public static Statement statement=null;
    public static ResultSet resultSet=null;
    //关闭数据库查询对象,以及结果集对象的时候
    public static void main(String[] args){
        //获取数据库连接属性
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc_");
        String driver = bundle.getString("driver");
        String url = bundle.getString("url");
        String password = bundle.getString("password");
        String user = bundle.getString("user");
            //获取完毕
        try{
            Class.forName(driver);
            connection = DriverManager.getConnection(url, user, password);
             //获取数据操纵对象
             statement = connection.createStatement();
             String sql="SELECT `uid`,`uname`,`upwd` \n" +
                     "FROM `t_user`";
             resultSet=statement.executeQuery(sql);
            ResultSetMetaData md=resultSet.getMetaData();
            int cloumSize=md.getColumnCount();
            System.out.println("数据库连接成功!");
            //我们先打印字段名
            for (int i = 1; i <=cloumSize ; i++) {
                System.out.printf("%-20s",md.getColumnName(i));
            }
            System.out.println();
             while (resultSet.next()){
                 for (int i = 1; i <=cloumSize ; i++) {
                     System.out.printf("%-20s",resultSet.getObject(i));
                 }
                 System.out.println();
             }
            System.out.println("查询结束");
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            //逆向关闭jdbc对象
                if(resultSet!=null){
                    try{
                        resultSet.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            if(statement!=null){
                try{
                    resultSet.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try{
                    resultSet.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

7.0 JDBC实践

7.1用户需求设计

/*实现功能:
*       1.需求:模拟用户登录功能的实现
*       2.业务描述:
*           程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码
*           用户输入用户名和密码之后,提交信息,java程序收集到用户信息
*           java程序连接数据库验证用户名,和密码是否合法
*           合法:显示登录成功
*           不合法:显示登录成功
*       3.数据的准备:
*           在实际开发中,表的设计会使用专业的建模工具,我们在这里安装一个建模工具,powerDesign
*           使用PD工具来进行数据库的设计
* */

设计用户登录表t_user



/*==============================================================*/
/* Table: t_user                                                */
/*==============================================================*/
DROP TABLE t_user IF exists
create table t_user
(
   uid                  bigint auto_increment,
   uname                varchar(255),
   upwd                 varchar(255),
   realname             varchar(255),
   primary key (uid)
);
insert into t_user ( uname , upwd , realname ) values ('zs1', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs2', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs3', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs4', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs5', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs6', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs7', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs8', '123','张三1');
insert into t_user ( uname , upwd , realname ) values ('zs9', '123','张三1');

7.2设计用户登录页面

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

/*
    模拟实现用户登录功能
 */
public class JDBCLogin {
    public static void main(String[] args) {
        // 初始化界面
        Map<String,String> userLoginInfo = initUI();
        // 验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);
        // 输出最后结果
        System.out.println(loginSuccess ? "登录成功" : "登录失败");
    }

    /**
     * 用户登录
     * @param userLoginInfo 用户登录信息
     * @return true表示登录成功,false表示登录失败
     */
    private static boolean login(Map<String, String> userLoginInfo) {
        boolean loginSuccess = false;
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 2、获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_db","root","123456");
            // 3、获取数据库操作对象
            stmt = conn.createStatement();
            // 4、执行sql语句
            String sql = "select * from t_user where uname = '"+ userLoginInfo.get("userName")+ "' and upwd = '" + userLoginInfo.get("userPassword")+ "'";
            rs = stmt.executeQuery(sql);
            // 5、处理结果集
            if(rs.next()) {
                loginSuccess = true;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            // 6、释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }


    /**
     * 初试化界面
     * @return 用户输入的用户名和密码等登录信息
     */
    private static Map<String, String> initUI() {
        Scanner s = new Scanner(System.in);

        System.out.print("请输入用户:");
        String userName = s.nextLine();
        System.out.print("请输入密码:");
        String userPassword = s.nextLine();

        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("userName",userName);
        userLoginInfo.put("userPassword",userPassword);

        return userLoginInfo;
    }
}

7.3SQL注入问题

当前程序存在的问题
    用户名:fdsa
    密码:fdsa' or '1'='1 
     
       
```![](https://img2020.cnblogs.com/blog/1448883/202010/1448883-20201025144558365-1265547573.png)







这种现象叫做sql注入,我们设置程序断点

```java
String sql = "select * from t_user where uname = '"+ userLoginInfo.get("LoginName")+ "' and upwd = '" + userLoginInfo.get("Loginpwd")+ "'";
//这一段是将sql语句发送给DBMS,DBMS进行sql编译
//正好将用户的"非法语句"执行

导致sql注入问题的根本原因是什么?

用户输入信息中含有sql语句中的关键字,并且这些关键字参数sql的编译过程

导致sql语句的原意被扭曲,进而达到sql注入

7.4解决sql注入问题

只要用户提供的信息参与sql语句的编译过程,问题就解决了

即时用户提供的信息包含sql语句关键字,但是没有参与编译,不起作用

所有要想用户信息不参与sql语句编译,必须使用

public interface PreparedStatement
extends Statement

属于预编译的数据库操作对象

PreparedStatement的原理是:预先对SQL语句的框架进行编译,再给SQL语句传值

8.0 statement和PrepareStatement比较

效率方面,由于sql语句在执行过程中如果第一条语句执行之后,第二条语句与第一条完全相等,则第二条不用编译就可执行,速度快,

Statement是每次都传入新的sql语句,每次都要编译速度慢

PrepareStatement是编译一次,下面传值进去,所以速度快

PrepareStatement会在编译阶段进行类型的安全检查

Statement使用较少,PrepareStatement使用较多

8.1PrepareStatement的增删该查

增加一条数据

//            3.获取数据库预编译操作对象
//            增加一条数据
//            String sql="INSERT INTO t_user ( uname , upwd , realname ) VALUES (?,?,?);";
//        preparedStatement=connection.prepareStatement(sql);
//        preparedStatement.setString(1,"wangwu");
//        preparedStatement.setString(2,"8888");
//        preparedStatement.setString(3,"王五");

修改一条数据

//            修改一条数据
            String sql="UPDATE `t_user` SET `uname` =?,`realname`=? WHERE `uid`=?";
            preparedStatement=connection.prepareStatement(sql);
            preparedStatement.setString(1,"wuwu");
            preparedStatement.setString(2,"王五");
            preparedStatement.setInt(3,11);
            int cnt=preparedStatement.executeUpdate();
            System.out.println("执行成功:"+cnt+" 条数据");

删除一条数据

  String sql="DELETE FROM t_user WHERE `uid`=?";
            preparedStatement =connection.prepareStatement(sql);
            preparedStatement.setInt(1,2);

9.0 JDBC的事务自动提交机制的演示

/*JDBC事务机制
*       1.jdbc中的事务都是自动提交的,什么是自动提交?
*           只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为
*           但是实际的业务中,通常都是N条DML语句共同联合才能完成,
*           必须保证他们这些DML语句在同一个事务中同时成功或者同时失败
*       2.一下程序验证JDBC事务自动提交机制
* */

我们先验证jdbc事务机制,插入两条数据,在一条数据之前设置断点,观察数据库的变化

/*JDBC事务机制
*       1.jdbc中的事务都是自动提交的,什么是自动提交?
*           只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为
*           但是实际的业务中,通常都是N条DML语句共同联合才能完成,
*           必须保证他们这些DML语句在同一个事务中同时成功或者同时失败
*       2.一下程序验证JDBC事务自动提交机制
* */

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JDBCTest {
    public static void main(String[] args) {
        //搭架子
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
//            1.获取驱动
            Class.forName("com.mysql.jdbc.Driver");
//            2.进行连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8",
                    "root", "123456");

            String sql="INSERT INTO t_user ( uname , upwd , realname ) VALUES (?,?,?);";
            preparedStatement=connection.prepareStatement(sql);
            preparedStatement.setString(1,"wangwu");
            preparedStatement.setString(2,"11220");
            preparedStatement.setString(3,"王五");

            int cnt=preparedStatement.executeUpdate();
            System.out.println("执行成功: "+cnt);

            preparedStatement.setString(1,"laoliu");
            preparedStatement.setString(2,"8888");
            preparedStatement.setString(3,"老刘");


        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
                if (preparedStatement!=null){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (connection!=null){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }




}

9.1账户转账演示事务

创建数据表

drop table if exists t_act;

/*==============================================================*/
/* Table: t_act  ,                             */
/*==============================================================*/
create table t_act
(
   actno                bigint(0) not null,
   balance              double(7,2), 
   primary key (actno)
);
INSERT INTO t_act (actno,balance) VALUES (111,2223);
INSERT INTO t_act (actno,balance) VALUES (222,4000);
commit;
SELECT *FROM t_act;

执行脚本:

9.2使用Connection中的一个方法setAutoCommit

void setAutoCommit(boolean autoCommit)
throws SQLException

手动打开事务提交机制

简单来说就三行经典代码:

//关闭自动提交事务
connection.setAutoCommit(false);
//        在此处说明执行成功
//            提交事务
connection.commit();
  //遇到数据异常,必须回滚事务
    if (connection!=null){
         try{
              connection.rollback();
            } catch (SQLException ex) {
               ex.printStackTrace();
          }

     }

转账代码

/*JDBC事务机制
*       1.jdbc中的事务都是自动提交的,什么是自动提交?
*           只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为
*           但是实际的业务中,通常都是N条DML语句共同联合才能完成,
*           必须保证他们这些DML语句在同一个事务中同时成功或者同时失败
*       2.一下程序验证JDBC事务自动提交机制
*
* */

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JDBCTest {
    public static void main(String[] args) {
        //搭架子
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
//            1.获取驱动
            Class.forName("com.mysql.jdbc.Driver");
//            2.进行连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8",
                    "root", "123456");
//        将自动提交机制改为手动提交机制
            connection.setAutoCommit(false);
        String sql="UPDATE `t_act` SET `balance`=? WHERE `actno`=?";
        preparedStatement=connection.prepareStatement(sql);
        preparedStatement.setDouble(1,1223);
        preparedStatement.setInt(2,111);
        int cnt=preparedStatement.executeUpdate();

            preparedStatement.setDouble(1,5000);
            preparedStatement.setInt(2,222);

        cnt+=preparedStatement.executeUpdate();
            System.out.println(cnt==2?"转账成功":"转账失败");

//        在此处说明已经提交成功了
//            提交事务
            connection.commit();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            //遇到数据异常,必须回滚事务
            if (connection!=null){
                try{
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }

            }
            e.printStackTrace();
        }finally {
                if (preparedStatement!=null){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (connection!=null){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }




}

执行成功:

10.JDBC工具类的封装

package Jdbc;

/*JDBC工具类,简化编程
*
* */

import java.sql.*;


public class DBUtil {
    /*
     * 工具类中的构造方法都是私有的
     * 因为工具类当中的方法都是静态的,不需要太new对象,直接采用类名调用
     *
     * */
    private DBUtil() {
    }

    //    静态代码块在类加载时执行,并且只执行一次
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
//    获取连接对象

    /**
     * @return 返回连接对象
     * @throws SQLException 方法跑出异常,主方法里接收异常
     */
    private static Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8",
                "root", "123456");
    }

    /**close方法为什么在方法里面跑出异常,我们可以知道关闭类到最后执行,而且一定执行
     * 方法重载一个
     * @param connection
     * @param statement
     * @param resultSet
     */
    private static void close(Connection connection, Statement statement, ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     *
     * @param connection
     * @param preparedStatement
     */
    private static void close(Connection connection,PreparedStatement preparedStatement){
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }


}

10.1通过模糊查询验证我们的工具类

mysql下的like关键字的使用

字符 说明
% 匹配任何数目的字符,甚至包括零字符
_ 只能匹配一种字符
LIKE'Mc%' 将搜索以字母 Mc 开头的所有字符串(如 McBadden)。
LIKE'%inger' 将搜索以字母 inger 结尾的所有字符串(如 Ringer、Stringer)
LIKE'%en%' 将搜索在任何位置包含字母 en 的所有字符串(如 Bennet、Green、McBadden)
LIKE'_heryl' 将搜索以字母 heryl 结尾的所有六个字母的名称(如 Cheryl、Sheryl)。

11.0悲观锁和乐观锁

我们先使用mysql select语句添加悲观锁

 String sql="SELECT `uid`,`uname`,`realname` FROM `t_user` WHERE `uname`=? FOR UPDATE";
package JDBC;

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

/**
 * @program: JDBC学习
 * @description: 开启一个查询专门进行查询, 使用行级锁/悲观锁,锁住记录
 * @author: JiaYadong
 * @create: 2020-08-05 17:30
 **/
public class JDBCTest12 {
        public static void main(String[] args){
            Connection connection=null;
            PreparedStatement preparedStatement=null;
            ResultSet resultSet=null;
            try{
                connection=DBUtil.getConnection();
//                手动开启事务
                connection.setAutoCommit(false);

                String sql="SELECT `uid`,`uname`,`realname` FROM `t_user` WHERE `uname`=? FOR UPDATE";
               preparedStatement= connection.prepareStatement(sql);
                preparedStatement.setString(1,"zs3");

                resultSet=preparedStatement.executeQuery();

                while(resultSet.next()){
                    System.out.println(resultSet.getString("uid")+","+
                            resultSet.getString("uname")+","+resultSet.getString("realname"));
                }
                //sql语句执行完毕,关闭事务
                connection.commit();
            } catch (SQLException e) {
                //如果出现异常,回滚事务
                if (connection!=null){
                    try {
                        connection.rollback();
                    } catch (SQLException ex) {
                        ex.printStackTrace();
                    }
                }
                e.printStackTrace();
            }finally {
                DBUtil.close(connection, preparedStatement,resultSet);
            }


        }


}

此处设置断点,再执行另外一个更新操作

package JDBC;

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

/**
 * @program: JDBC学习
 * @description: 通过修改数据来测试悲观锁
 * @author: JiaYadong
 * @create: 2020-08-05 17:49
 **/
public class JDBCTest13 {
    public static void main(String[] args){
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;
        try{
            connection=DBUtil.getConnection();
            connection.setAutoCommit(false);

            String sql="UPDATE `t_user` SET `uname` =? WHERE `uid`=?";
            preparedStatement=connection.prepareStatement(sql);
            //填充数据
            preparedStatement.setString(1,"马前卒");
            preparedStatement.setInt(2,1);
            int cnt=preparedStatement.executeUpdate();
            System.out.println("执行影响行数: "+cnt);


        connection.commit();
        } catch (SQLException e) {

            //如果事务有异常回滚事务
            if(connection!=null){
                try{
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,preparedStatement);
        }
    }
}

会发现插入数据的程序会被卡着

等待时间过长的时间

java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction

此时终止调试,执行commit

修改事务成功

posted @ 2020-10-25 14:50  刺客伍六七  阅读(123)  评论(0编辑  收藏  举报