JDBC学习

JDBC学习

1 JDBC是什么?

Java DataBase Connectivity(Java 数据库连接)

2 JDBC的本质

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

每个数据库的底层实现都不一样,都有自己的实现原理。

JDBC通过编写一套接口,然后各个数据库厂家实现这个接口,这样我们就可以通过JDBC来操作不同的数据库了。

JDBC API提供了一个标准接口,用于与任何关系数据库管理系统(RDBMS)进行交互。
JDBC API包含以下主要组件:
1.JDBC DriverJDBC驱动程序)
2.Connection(连接)
3.Statement(声明)
4.ResultSet(结果集)

3 配置文件及反射

目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AeKQDmLn-1690358526519)(JDBC.assets/image-20220803154108307.png)]

配置文件cus_jdbc.properties

clazzName=MySQl
driver=com.mysql.cj.jdbc.Driver

主类 main.java

import java.util.ResourceBundle;
public class main {
    public static void main(String[] args) {
        ResourceBundle bundle=ResourceBundle.getBundle("cus_jdbc");
        String clazzName =bundle.getString("clazzName");
        String mysqlClazzName =bundle.getString("driver");
         System.out.println(clazzName);
         System.out.println(mysqlClazzName);
    }
}

运行结果:

MySQl
com.mysql.cj.jdbc.Driver
进程已结束,退出代码为 0

配置文件cus_jdbc.properties内容和cus_jdbc2.properties的内容相同

clazzName=MySQl
driver=com.mysql.cj.jdbc.Driver

主类 main.java

import java.util.ResourceBundle;
public class main {
    public static void main(String[] args) {
        //通过resourceBundle对象读取配置文件
        ResourceBundle bundle=ResourceBundle.getBundle("cus_jdbc");
        String clazzName =bundle.getString("clazzName");
        String mysqlClazzName =bundle.getString("driver");

        //通过properties对象读取配置文件
        Properties p = new Properties();
        try {
            p.load(new FileInputStream("src/resource/cus_jdbc2.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        String jdbc2=p.getProperty("clazzName");
        String jdbc22=p.getProperty("driver");

        System.out.println(clazzName);
        System.out.println(mysqlClazzName);

        System.out.println(jdbc2);
        System.out.println(jdbc22);
        //通过配置文件获取类名,若需要更换数据库,则只需要在配置文件中将对应字段进行更改即可
        //这里可以newInstance()通过反射的方式获取类,然后调用类中的方法
        //JDBC操作...
    }
}

运行结果:

MySQl
com.mysql.cj.jdbc.Driver
MySQl222
com.mysql.cj.jdbc.Driver222

进程已结束,退出代码为 0

4 JDBC开发准备

下载对应数据库驱动的jar包,并将其配置刀环境变量classpath中。

classpath = .;D:\JDBCConnetJAR\MYSQL\8.0.19

(IDEA 有自己的配置方式,可加可不加)

5 JDBC编程的步骤

1.注册驱动
2.获取连接
3.获取数据库操作对象
4.执行SQL语句
5.处理查询结果集
6.释放资源

5.1 用法

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class main {
    public static void main(String[] args) {
     Connection connection=null;
    Statement statement=null;
    try {
      /**
       * 1.
       */
      //1.注册驱动
      Driver driver=new com.mysql.cj.jdbc.Driver();
      DriverManager.registerDriver(driver);
      //2.获取连接
      /**
       * 协议:jdbc:mysql://
       * 地址:localhost:3306/sqlstudy?
       * 参数:useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
       */
      String url="jdbc:mysql://localhost:3306/sqlstudy?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT";
      String user="root";
      String password="li1473606768";
      connection=DriverManager.getConnection(url,user,password);
      //打印连接对象:
      System.out.println("连接对象"+connection);

      //3.获取数据库操作对象
      statement=connection.createStatement();
      String sql="show tables";
      //4.执行SQL
      ResultSet resultSet=statement.executeQuery(sql);
      //5.处理结果集
      //随便使用的vector
      Vector<String> vector=new Vector<>();
      while(resultSet.next()){
        vector.add(resultSet.getString(1));
      }
      System.out.println(vector.toString());
    } catch (SQLException e) {
      System.out.println("检测到异常");
      e.printStackTrace();
    } finally{
      //6.释放资源
      if(statement!=null){
        try {
          statement.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }

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

5.2 注册驱动的另外一种方式

打开mysql的Driver.java中可以看到源码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oGWInezH-1690358526524)(JDBC.assets/image-20220803185504924.png)]

可以看到Driver中这个静态代码块已经实现了我们刚开始写的registerDriver()

如何执行这个静态代码块呢?

加载这个类。

如何加载这个类?

使用反射机制。

Class.forName("com.mysql.cj.jdbc.Driver");
//此反射不需要返回值,我们只需要这个类加载动作,也就是static静态代码块。

这种是最常用的JDBC加载方式,因为Class.forName()的方法参数是字符串,我们可以通过读取配置文件将字符串读入其中。

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

public class getConnection_reflect {
    public static void main(String[] args) {
        Connection connection=null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/sqlstudy?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT","root","li1473606768");
            System.out.println("连接对象:"+connection);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally{
            if(connection!=null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5.3 将连接数据库的所有信息配置到配置文件中

将以上代码进行更改

jdbc_reflect.properties

driver=com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/sqlstudy?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
user =root
password = li1473606768

getConnection_reflect.java

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

public class getConnection_reflect {
    public static void main(String[] args) {
        Connection connection=null;
        //使用资源绑定器,绑定properties配置文件
        ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbc_reflect");
        String driver=resourceBundle.getString("driver");
        String url=resourceBundle.getString("url");
        String user=resourceBundle.getString("user");
        String password=resourceBundle.getString("password");
        try {
            Class.forName(driver);
            connection= DriverManager.getConnection(url,user,password);
            System.out.println("连接对象:"+connection);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally{
            if(connection!=null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

此时我们就直接更改.properties文件中的键值对,.java文件都不用重新编译(但是要重新运行)就可以更改数据库的属性。

5.4 处理结果集

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

public class getConnection_reflect {
    public static void main(String[] args) {
        Connection connection=null;
        Statement statement=null;
        ResultSet resultSet=null;
        //使用资源绑定器,绑定properties配置文件
        ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbc_reflect");
        String driver=resourceBundle.getString("driver");
        String url=resourceBundle.getString("url");
        String user=resourceBundle.getString("user");
        String password=resourceBundle.getString("password");
        try {
            Class.forName(driver);
            connection= DriverManager.getConnection(url,user,password);
            System.out.println("连接对象:"+connection);
            String sql= "select e.empno,e.ename,e.sal * 12 from emp e";
            /**
             * emp表中有以下几个字段:
             *   EMPNO    | int
             *   ENAME    | varchar(10)
             *   JOB      | varchar(9)
             *   MGR      | int
             *   HIREDATE | date
             *   SAL      | double(7,2)
             *   COMM     | double(7,2)
             *   DEPTNO   | int
             */
            statement = connection.createStatement();
            resultSet=statement.executeQuery(sql);
            while(resultSet.next()){
                //查询结果集中的列名称
                int empno=resultSet.getInt("empno");
                String ename=resultSet.getString("ename");
                Double sal=resultSet.getDouble("sal");
                System.out.println(empno+"--"+ename+"--"+sal);
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally{
            //注意关闭的顺序
            if(connection!=null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

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

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

        }
    }
}

6 IDEA配置JDBC

1.新建空项目

2.右键项目名称–>Open module settings–>libraries–>加号–>java

3.选择mysql数据库连接jar包

4.choose modules–>ok

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hAljbr9T-1690358526529)(JDBC.assets/image-20220804090212864.png)]

7 模拟登录系统

初始化登录用表

#创建登录用表
drop table if exists t_user;
create table t_user (
    id	bigint auto_increment,
    loginName	varchar(20),
    loginPwd	varchar(65),
    realName	varchar(20),
    primary key(id)
);
#插入登录用户
insert into t_user(loginName,loginPwd,realName)values("maka","123","张三");
insert into t_user(loginName,loginPwd,realName)values("macie","123","李四");
insert into t_user(loginName,loginPwd,realName)values("aaa","123","王五");
insert into t_user(loginName,loginPwd,realName)values("bbb","123","王五");
#查询
select * from t_user;

JDBCLogin.java

package com.mysqlStudy.macie.JDBCTest01Login;

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

/**
 * @Author: yeyulemon
 * @Date: 2022-08-04 09:23
 **/
public class JDBCLogin {
    public static void main(String[] args) {

        //初始化界面
        Map<String,String> userLoginInfo=initUI();
        //验证用户名和密码
        boolean loginResult = Login(userLoginInfo);
        System.out.println(loginResult?"登录成功":"登陆失败");
    }

    /** 
    * @Description: 用户登录
    * @Param: [userLoginInfo] 用户名和密码的map
    * @return: boolean true成功,false失败
    * @Author: MacieSerenity 
    * @Date: 2022/8/4 9:29 
    */
    private static boolean Login(Map<String, String> userLoginInfo) {
        //JDBC代码
        Connection connection=null;
        Statement statement=null;
        ResultSet resultSet=null;

        ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
        String url=resourceBundle.getString("url");
        String driver=resourceBundle.getString("driver");
        String user=resourceBundle.getString("user");
        String password=resourceBundle.getString("password");
        String userName=userLoginInfo.get("userName");
        String userPwd=userLoginInfo.get("userPwd");

        try {
            Class.forName(driver);
            connection=DriverManager.getConnection(url,user,password);
            System.out.println(connection);

            statement=connection.createStatement();
            String sql="select * from t_user where loginName='" + userName + "' and loginPwd='"+ userPwd+"'";
            resultSet =statement.executeQuery(sql);

            return resultSet.next()? true:false;

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            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();
                }
            }
        }


        return false;
    }

    /**
    * @Description: 打印界面,并收集登录账号和密码
    * @Param: [] 
    * @return: java.util.Map 
    * @Author: MacieSerenity
    * @Date: 2022/8/4 9:26 
    */
    private static Map<String, String> initUI() {
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入用户名");
        String userName=scanner.nextLine();

        System.out.println("请输入密码");
        String userPwd=scanner.nextLine();

        Map<String,String> loginInfo=new HashMap<>();
        loginInfo.put("userName",userName);
        loginInfo.put("userPwd",userPwd);
        return loginInfo;
    }
}

8 SQL注入的问题

8.1 什么是SQL语句注入

在以上代码过程中,我们有用户aaa,其密码为123
但是如果我们将密码输入为:
xxx ' or '1' = '1
依然能成功访问
//请输入用户名
aaa
//请输入密码
asdfasdf' or '1' = '1  <==SQL语句注入	
//connect 对象:
com.mysql.cj.jdbc.ConnectionImpl@769a1df5
登录成功	<==显示登录成功

这种现象被称为SQL注入,是一种安全隐患,是黑客经常使用的攻击手段。

这种现象是怎么产生的呢?

在该语句处打个断点,且使用调试模式运行:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rd6jy9zU-1690358526533)(JDBC.assets/image-20220804102957109.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xV8iO2Gf-1690358526538)(JDBC.assets/image-20220804103057678.png)]

可以看到我们的select语句变成了:

select * from t_user where loginName='asdfas' and loginPwd='xxx' or '1' = '1'

我们可以将这个语句放到mysql里执行一下看下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OfWT4Gul-1690358526543)(JDBC.assets/image-20220804103302141.png)]

查询出了所有结果,所以resultSet.next()不为空,所以就能正常登录。

8.2 导致SQL注入的原因

是因为用户输入的数据中有sql语句的关键字
且这些关键字参与了SQL语句的执行,导致SQL语句的原意被扭曲
从而达成SQL注入

8.3 如何解决SQL注入?

只要用户提供的信息不参与SQL语句的编译过程,就可以解决SQL注入。
即使用户提供的信息中含有SQL语句的关键字,但是只要不参与编译,就可以不起作用
要想用户的信息不参与SQL语句的编译,则我们需要使用java.sql.PreparedStatement

PrepareStatement接口继承了java.sql.Statement
PrepareStatement属于预编译的数据库操作对象
PrepareStatement原理是预先对SQL语句的框架进行编译,然后给SQL语句传值

java

package com.mysqlStudy.macie.JDBCTest02Login;

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

/**
 * @Author: yeyulemon
 * @Date: 2022-08-04 09:23
 **/
public class JDBCLogin2 {
    public static void main(String[] args) {

        //初始化界面
        Map<String,String> userLoginInfo=initUI();
        //验证用户名和密码
        boolean loginResult = Login(userLoginInfo);
        System.out.println(loginResult?"登录成功":"登陆失败");
    }

    /**
     * @Description: 用户登录
     * @Param: [userLoginInfo] 用户名和密码的map
     * @return: boolean true成功,false失败
     * @Author: MacieSerenity
     * @Date: 2022/8/4 9:29
     */
    private static boolean Login(Map<String, String> userLoginInfo) {
        //JDBC代码
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;

        boolean loginResult=false;

        ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
        String url=resourceBundle.getString("url");
        String driver=resourceBundle.getString("driver");
        String user=resourceBundle.getString("user");
        String password=resourceBundle.getString("password");
        String userName=userLoginInfo.get("userName");
        String userPwd=userLoginInfo.get("userPwd");

        try {
            Class.forName(driver);
            connection=DriverManager.getConnection(url,user,password);
            System.out.println(connection);
            //获取预编译数据库操作对象,一个?表示一个占位符,且不能用单引号
            String sql="select * from t_user where loginName=? and loginPwd=?";
            //程序执行到这里,会发送SQL的框架,然后DBMS进行SQL语句的预编译
            preparedStatement=connection.prepareStatement(sql);
            //给占位符传值(JDBC中所有下标从1开始)
            preparedStatement.setString(1,userName);
            preparedStatement.setString(2,userPwd);
            //执行SQL  (executeQuery的参数变了)
            //若填写了参数,则会重新编译SQL
            resultSet =preparedStatement.executeQuery();
//            return resultSet.next()? true:false;
            loginResult=resultSet.next();

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

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

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

        return loginResult;
    }
    /**
     * @Description: 打印界面,并收集登录账号和密码
     * @Param: []
     * @return: java.util.Map
     * @Author: MacieSerenity
     * @Date: 2022/8/4 9:26
     */
    private static Map<String, String> initUI() {
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入用户名");
        String userName=scanner.nextLine();

        System.out.println("请输入密码");
        String userPwd=scanner.nextLine();

        Map<String,String> loginInfo=new HashMap<>();
        loginInfo.put("userName",userName);
        loginInfo.put("userPwd",userPwd);
        return loginInfo;
    }
}
//运行结果:
请输入用户名
adsfa
请输入密码
xxx' or '1' = '1'
com.mysql.cj.jdbc.ConnectionImpl@15d9bc04
登陆失败

重新打断点可以看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3Wo1fZX-1690358526547)(JDBC.assets/image-20220804105319366.png)]

sql 被提前编译了,没有发生注入的现象。

8.4 Statement和PreparedStatement

Statement存在SQL注入问题,而PreparedStatement解决了SQL注入问题
Statement的效率没有PreparedStatement高
(原因是数据库执行相同语句时,不会进行二次编译,而Statement每次编译时的用户名和密码都不同,所以每次都需要编译一次,而PreparedStatement每次执行的都是userName=? and userPass=?,所以只需要编译一次)
PreparedStatement在编译阶段会做类型检查。(指定setString则必须是String类型的数据)
综上所述。PreparedStatement使用较多,极少数情况下使用Statement

除开业务要求SQL注入的情况下,其他情况使用PreparedStatement
(如升序降序)

9 PreparedStatement 的 CURD

package com.mysqlStudy.macie.JDBCTest03PreparedStatemen;

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

/**
 * @Author: yeyulemon
 * @Date: 2022-08-04 11:21
 **/
public class JDBCLogin3 {
    public static void main(String[] args) {
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;


        //preparedStatement CURD
        ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
        String DBdriver=resourceBundle.getString("driver");
        String DBurl=resourceBundle.getString("url");
        String DBuser=resourceBundle.getString("user");
        String DBpass=resourceBundle.getString("password");

        try {
            Class.forName(DBdriver);
            connection= DriverManager.getConnection(DBurl,DBuser,DBpass);
            /**
             * 插入
             */
//            String sql="insert into t_user(loginName,loginPwd,realName)values(?,?,?)";
//            preparedStatement=connection.prepareStatement(sql);
//            preparedStatement.setString(1,"bbb");
//            preparedStatement.setString(2,"bbb");
//            preparedStatement.setString(3,"321");

            /**
             * 更新
             */
//            String sql="update t_user set loginName=?,loginPwd=? where id=?";
//            preparedStatement=connection.prepareStatement(sql);
//
//            preparedStatement.setString(1,"ccc");
//            preparedStatement.setString(2,"ccc");
//            preparedStatement.setInt(3,5);

            /**
             * 删除
             */
//            String sql="delete from t_user where loginName=?";
//            preparedStatement=connection.prepareStatement(sql);
//            preparedStatement.setString(1,"bbb");
//            int count =preparedStatement.executeUpdate();
//            System.out.println("count=="+count);
            
            /**
     		* 查询使用resultSet
     		*/
            String sql="select * from emp";
            preparedStatement=connection.prepareStatement(sql);
            resultSet =preparedStatement.executeUpdate();
			while(resultSet.next()){
               String ename = resulet.getString("ename");
               System.out.println("ename:"+ename);
            }
            
        }catch (ClassNotFoundException | SQLException e){
            e.printStackTrace();
        }finally{
            if (resultSet!=null){
                try {
                    resultSet.close();
                }catch (SQLException e){
                    e.printStackTrace();
                }
            }

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

            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

10 JDBC中使用事务Transaction

JDBC 中的事务是自动提交的。
但是实际业务中,通常都是N条DML语句联合使用才能完成。
保证DML语句在同一个事务中同时成功或者同时失效。

创建实验表:

drop table if exists t_act;
create table t_act(
	actno bigint,
	balance double(7,2)
);
insert into t_act(actno,balance)values(1,20000);
insert into t_act(actno,balance)values(2,0);
select * from t_act;

快捷键

选中多行  然后 alt+shift+insert 可以进行批量编辑hhh

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ZWeR7Sh-1690358526552)(JDBC.assets/image-20220804115653970.png)]

详细的事务使用已用注释标明

package com.mysqlStudy.macie;

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

/**
 * @Author: yeyulemon
 * @Date: 2022-08-04 11:42
 **/
public class JDBCTest04Transaction {
    public static void main(String[] args) {
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;

        ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
        String DBurl=resourceBundle.getString("url");
        String DBdriver=resourceBundle.getString("driver");
        String DBuser=resourceBundle.getString("user");
        String DBpassword=resourceBundle.getString("password");

        try {
            Class.forName(DBdriver);
            connection= DriverManager.getConnection(DBurl,DBuser,DBpassword);
            //获取到connect 对象后,将connect对象的自动提交机制关闭
            //开启事务
            connection.setAutoCommit(false);


            String sql="update t_act set balance = ? where actno = ?";
            preparedStatement= connection.prepareStatement(sql);

            preparedStatement.setDouble(1,10000);
            preparedStatement.setInt(2,1);
            preparedStatement.executeUpdate();
            int count =preparedStatement.executeUpdate();

            //模拟程序出现异常
//            String s=null;
//            s.toString();


            preparedStatement.setDouble(1,10000);
            preparedStatement.setInt(2,2);
            count += preparedStatement.executeUpdate();


            System.out.println(count==2?"转账成功":"转账失败");
            //程序执行到这里就说明程序执行成功,手动进行事务的提交
            //提交事务
            connection.commit();

        }catch (ClassNotFoundException | SQLException e){
            //如果捕获到任何异常,为了保证数据安全,直接进行数据的回滚
            //数据回滚
            if (connection!=null){
                try {
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally {
            if(resultSet!=null){
                try {
                    connection.close();
                }catch (SQLException e){
                    e.printStackTrace();
                }
            }

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

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

    }
}
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck

11 制作JDBC工具类

数据库配置文件jdbcConfig.properties

driver=com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/sqlstudy?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
user=root
password=li1473606768
package com.mysqlStudy.macie.DBUtils;

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


public class DButils {
    /** 
    * @Description:  工具类中的构造方法都是私有的,工具类中的方法都是静态的,不需要new对象,直接采用类名调用
    * @Param: [] 
    * @return:  
    * @Author: MacieSerenity 
    * @Date: 2022/8/4 12:16 
    */
    private DButils(){}


    /**
     * 静态代码代码块在类加载时就会执行,且只会执行一次
     *  防止Class.forName()多次执行
     */
    static {
        ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
        String DBDriver=resourceBundle.getString("driver");
        try {
            Class.forName(DBDriver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * @discription 获取连接对象
     * @author Macieserenity 
     * @Params []
     * @updateTime 2022/8/4 12:33 
     * @return java.sql.Connection
     * @throws 
     */
    public static Connection getConnection() throws SQLException{
        ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
        String DBurl=resourceBundle.getString("url");
        String DBuser=resourceBundle.getString("user");
        String DBpassword=resourceBundle.getString("password");
        return DriverManager.getConnection(DBurl, DBuser, DBpassword);
    }


    /**
     * @discription 关闭数据流
     * @author Macieserenity 
     * @param: connection
     * @param: statement
     * @param: resultSet
     * @updateTime 2022/8/4 12:58 
     * @return 
     * @throws 
     */
    public static void closeConnection(Connection connection, Statement  statement, ResultSet resultSet){

        if(resultSet!=null){
            try {
                connection.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();
            }
        }

    }
}

工具类的使用

package com.mysqlStudy.macie.JDBCTest04Transaction;

import com.mysqlStudy.macie.DBUtils.DButils;

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

/**
 * @Author: yeyulemon
 * @Date: 2022-08-04 11:42
 **/
public class JDBCTest04Transaction {
    public static void main(String[] args) {
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;

        try {
            connection=DButils.getConnection();
            String sql="select ename from emp where ename like ?";
            preparedStatement=connection.prepareStatement(sql);

            preparedStatement.setString(1,"_A%");
            resultSet= preparedStatement.executeQuery();

            while (resultSet.next()){
                System.out.println(resultSet.getString("ename"));
            }


        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButils.closeConnection(connection,preparedStatement,resultSet);
        }
    }
}

12 乐观锁和悲观锁

12.1 什么是行级锁?

行级锁,又被称为悲观锁:事务必须排队执行,数据被锁住,无法进行并发
乐观锁:支持并发,事务也不需要排队,只不过需要一个版本号来记录是否被更改

悲观锁

#在我们执行SQL语句的过程中:
select ename,job,sal from emp where job='manager';
#若在后方加一个for update
select ename,job,sal from emp where job='manager' for update;
#就说明job=manager的这几行记录被加上了行级锁,在当前事务没有结束之前,无法被其它语句修改

可以写两个不同的JDBC 的 update 语句,对同一行数据进行修改测试,在第一个程序中的commit()处打上断点。
然后执行第二个程序,会发现第二个程序的返回结果卡住了,然后这时候将第一个程序中的断点继续执行。
第二个程序立刻就返回了结果,这就是悲观锁的基本测试过程。

乐观锁

有两个事务进行并发
事务1:读取到版本号1
事务2:读取到版本号2

#此时,若事务1率先读取到数据,进行修改之后版本号是1.1,然后在提交修改的时候,将版本号设置为1.2
#	事务2页读取到了版本1,修改后准备提交时,发现版本号为1.2,和读取的版本号不相同,则进行回滚。

这就是乐观锁的基本原理。

你可能感兴趣的:(JDBC)