JDBC笔记详解

一、前言

这篇技术博客是关于动力节点JDBC教程做的笔记总结,方便大家的学习同时也方便自己。

二、JDBC本质理解

1.什么是JDBC?

Java DataBase Connective(Java语言连接数据库)

2.JDBC的本质是什么?

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

java.sal.*; (这个软件包下有很多接口)

接口都有调用者和实现者。
面向接口调用、面向接口写实现类,这都属于面向接口编程

为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展里。
多态机制就是非常典型的:面向接口编程。(不要面向具体编程)

Animal a = new Cat();
Animal a = new Dog();
//喂养方法
public void feed(Animal a) {
	//面向父类型编程
}

思考:为什么SUN指定一套JDBC接口?

因为每一个数据库的底层实现原理都不一样。
Oracle数据库有自己的原理
MySQL数据库有自己的原理
MS SqlServer数据也有自己的原理。

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

JDBC的本质是一套接口!
在这里插入图片描述

驱动jar包中,有很多字节码文件。这些字节码文件就是对SUN公司JDBC接口的实现类
一个程序如果只有接口,没有接口的实现类,程序是永远无法运行!

我们可以把驱动jar包理解为JDBC接口实现类的字节码文件

三、使用代码模拟JDBC本质

为了加深大家对JDBC本质的理解,接下来我们使用Java代码编写一个程序,来模拟JDBC的本质!

首先在项目下创建一个jdbc.properties配置文件:

className=com.xu9.test8.Oracle

代码展示:

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

==========================================================================================
/*
MySQL数据库厂家负责编写JDBC接口的实现类
 */
public class MySQL implements JDBC {
    @Override
    public void getConnection() {
        //具体这里的代码怎么写,对于我们Java程序员来说没关系
        //这段代码涉及到MySQL底层数据库的实现原理
        System.out.println("连接MySQL数据库成功!");
    }
}

//实现类被称为驱动(MySQL驱动)
//xxx.jar 当中有很多.class字节码文件,都是对JDBC接口进行的实现

==========================================================================================
/*
Oracle数据库厂家负责编写JDBC接口的实现类
 */
public class Oracle implements JDBC {
    @Override
    public void getConnection() {
        //具体这里的代码怎么写,对于我们Java程序员来说没关系
        //这段代码涉及到Oracle底层数据库的实现原理
        System.out.println("连接Oracle数据库成功!");
    }
}

//实现类被称为驱动(Oracle驱动)

==========================================================================================
/*
SqlServer数据库厂家负责编写JDBC接口的实现类
 */
public class SqlServer implements JDBC {
    @Override
    public void getConnection() {
        //具体这里的代码怎么写,对于我们Java程序员来说没关系
        //这段代码涉及到SqlServer底层数据库的实现原理
        System.out.println("连接SqlServer数据库成功!");
    }
}

//实现类被称为驱动(SqlServer驱动)

==========================================================================================
import java.io.FileInputStream;
import java.util.Properties;

/*
Java程序员角色
不需要关心具体是哪一个品牌的数据库,只需要面向JDBC接口写代码
面向接口编程,面向抽象编程,不要面向具体编程
 */
public class JavaProgrammer {
    public static void main(String[] args) throws Exception{
        //JDBC jdbc = new MySQL(); //连接MySQL数据库成功!
        //JDBC jdbc = new Oracle(); //连接Oracle数据库成功!
        //JDBC jdbc = new SqlServer(); //连接SqlServer数据库成功!

        //创建对象可以通过反射机制
        Properties pros = new Properties(); //常用来处理配置文件
        FileInputStream fis = new FileInputStream("jdbc.properties");
        pros.load(fis);  //加载流对应文件
        String className = pros.getProperty("className");
        Class c = Class.forName(className);
        JDBC jdbc = (JDBC) c.newInstance();

        //以下代码都是面向接口调用方法,不需要修改
        jdbc.getConnection();

        //记得关闭流
        fis.close();
    }
}

四、JDBC编程六步骤

JDBC开发前的准备工作:需要从官网下载对应的驱动jar包,然后将其配置到idea中。

1.注册驱动

作用:告诉Java程序,即将连接的是哪个品牌的数据库

2.获取连接

作用:表示JVM进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完了一定要关闭通道!

3.获取数据库操作对象

该对象是专门用于执行sql语句的对象

4.执行sql语句

执行的sql语句主要是:DQL和DML语句

5.处理查询结果集(第六章讲解)

只有当第四步执行的是DQL语句时,才有该步骤查询结果集

6.释放资源

使用完资源之后,一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭

代码展示

import java.sql.*;

/*
JDBC编程六步代码展示
 */
public class JDBCTest {
    public static void main(String[] args) {
        Statement stmt = null;
        Connection conn = null;
        try {
            //1.注册驱动
            //注册驱动的第一种方式
            //Driver driver = new com.mysql.jdbc.Driver(); //多态,父类型引用指向子类型对象
            //DriverManager.registerDriver(driver);
            
            //注册驱动的第二种方式:类加载方式(常用)
            //为什么这种方式常用?因为参数是一个字符串,可以写到properties配置文件中
            //以下方法不需要接收返回值,因为我们只想用它的类加载动作
            Class.forName("com.mysql.jdbc.Driver");

            //2.获取连接
            /*
            url:统一资源定位符(网络中某个资源的绝对路径)
            https://www.baidu.com/ 这就是url
            url包括哪几部分?
                协议
                IP
                PORT
                资源名

            http://182.61.200.7:80/index.html
                http://         通信协议
                182.61.200.7    服务器IP地址
                80              服务器上软件的端口号
                index.html      服务器上的资源名

            jdbc:mysql://127.0.0.1:3306/bjpowernode
                jdbc:mysql://   协议
                127.0.0.1       IP地址
                3306 mysql      数据库端口号
                bjpowernode     具体的数据库实例名

            说明:localhost和127.0.0.1都是本机IP

            什么是通信协议,有什么用?
                通信协议是通信之间就提前决定好的数据传送格式
                数据包具体怎么传数据,格式提前定好
             */
            String url = "jdbc:mysql://127.0.0.1:3306/bjpowernode";
            String username = "root";
            String password = "root";
            conn = DriverManager.getConnection(url, username, password);

            //3.获取数据库操作对象
            stmt = conn.createStatement();

            //4.执行sql语句
            String sql = "insert into dept(deptno,dname,loc) values(50,'人事部','北京')";
            //该方法专门执行DML语句,返回值是影响数据库中的记录条数
            int count = stmt.executeUpdate(sql);
            System.out.println(count == 1 ? "保存成功" : "保存失败");

            //5.处理查询结果集
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e){
        	e.printStackTrace();
        } finally {
            //6.释放资源
            //为了保证资源一定释放,在finally语句块中关闭资源
            //并且要遵循从小到大依次关闭
            //分别对其try-catch
            if (stmt != null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

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

五、从配置文件中读取连接数据库信息

首先我们先创建一个jdbc.properties配置文件,放到src目录下

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/bjpowernode
username=root
password=root

代码如下:

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

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

实际开发中,不建议把连接数据库的信息写死在Java程序中
 */
public class JDBCTest3 {
    public static void main(String[] args){
        //使用资源绑定器绑定属性配置文件
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String driver = bundle.getString("driver");
        String url = bundle.getString("url");
        String username = bundle.getString("username");
        String password = bundle.getString("password");


        Connection conn = null;
        Statement stmt = null;
        try {
            //1.注册驱动
            Class.forName(driver);
            //2.获取连接
            conn = DriverManager.getConnection(url,username,password);
            //3.获取数据库操作对象
            stmt = conn.createStatement();
            //4.执行sql语句
            int count = stmt.executeUpdate("delete from dept where deptno = 50");
            System.out.println(count == 1 ? "删除成功" : "删除失败");
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally{
            //6.释放资源
            if (stmt != null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

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

六、处理查询结果集

代码演示:

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

/*
处理查询结果集(遍历结果集)
 */
public class JDBCTest4 {
    public static void main(String[] args){
        //使用资源绑定器绑定属性配置文件
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String driver = bundle.getString("driver");
        String url = bundle.getString("url");
        String username = bundle.getString("username");
        String password = bundle.getString("password");


        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //1.注册驱动
            Class.forName(driver);
            //2.获取连接
            conn = DriverManager.getConnection(url,username,password);
            //3.获取数据库操作对象
            stmt = conn.createStatement();
            //4.执行sql语句
            String sql = "select empno,ename,sal from emp";
            //该方法专门执行DQL语句
            rs = stmt.executeQuery(sql);
            //5.处理查询结果集
            while (rs.next()){
                //光标指向的行有数据
                //取数据
                //getString()方法的特点:不管数据库中的数据类型是什么,都以String的形式取出
                //以下参数的1,2,3指的是第几列
                /*
                String empno = rs.getString(1); //JDBC中所有下标从1开始,不是从0开始
                String ename = rs.getString(2);
                String sal = rs.getString(3);
                System.out.println(empno + "," + ename + "," + sal);
                */

                /*
                //这个不是以列的下标获取,以列的名字获取
                //重点注意:列名称不是表中的列名称,是查询结果集的列名称
                String empno = rs.getString("empno");
                String ename = rs.getString("ename");
                String sal = rs.getString("sal");
                System.out.println(empno + "," + ename + "," + sal);
                */

                //除了可以以String类型取出之外,还可以以特定的类型取出
                /*
                int empno = rs.getInt(1);
                String ename = rs.getString(2);
                double sal = rs.getDouble(3);
                System.out.println(empno + "," + ename + "," + (sal + 100));
                */

                int empno = rs.getInt("empno");
                String ename = rs.getString("ename");
                double sal = rs.getDouble("sal");
                System.out.println(empno + "," + ename + "," + (sal + 200));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            //6.释放资源
            if (rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

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

七、用户登录案例

  1. 需求:模拟用户登录功能实现
  2. 业务描述:程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码。用户输入用户名密码之后,提交信息,java程序员收集到用户信息。java程序连接数据库验证用户名和密码是否合法。合法:显示登陆成功,不合法:显示登陆失败
  3. 数据的准备:参见下方user-login.sql脚本
drop table if exists t_user;

create table t_user
(
	id			bigint auto_increment,
	loginName	varchar(255),
	loginPwd	varchar(255),
	realName	varchar(255),
	primary key(id)
);

insert into t_user(loginName,loginPwd,realName) values('zhangsan','123','张三');
insert into t_user(loginName,loginPwd,realName) values('jack','123','杰克');
commit;
select * from t_user;


================================================================
+----+-----------+----------+----------+
| id | loginName | loginPwd | realName |
+----+-----------+----------+----------+
|  1 | zhangsan  | 123      | 张三     |
|  2 | jack      | 123      | 杰克     |
+----+-----------+----------+----------+

代码展示:

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

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

    //用户登录方法
    private static boolean login(Map<String, String> userLoginInfo) {
        //打标记
        boolean loginSuccess = false;
        //单独定义变量获取用户输入的用户名和密码
        String loginName = userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get("loginPwd");
        //JDBC代码
        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/bjpowernode","root","root");
            //3.获取数据库操作对象
            stmt = conn.createStatement();
            //4.执行sql
            String sql = "select * from t_user where loginName = '" + loginName + "' and loginPwd = '" + loginPwd + "'";
            rs = stmt.executeQuery(sql);
            //5.处理结果集
            if (rs.next()){
                //登录成功
                loginSuccess = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6.释放资源
            if (rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

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

        System.out.println("用户名:");
        String loginName = s.nextLine();

        System.out.println("密码:");
        String loginPwd = s.nextLine();

        HashMap<String, String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName",loginName);
        userLoginInfo.put("loginPwd",loginPwd);

        return userLoginInfo;
    }
}

八、SQL注入现象及解决

1.SQL注入现象

我们发现用户登录案例代码有个问题:即使我们输入的用户名密码不对,也可以显示登陆成功!
在这里插入图片描述
这种现象被称为:SQL注入(安全隐患)(黑客经常使用)

2.导致SQL注入的根本原因

用户输入的信息中含有sql语句的关键字,并且这些关键字参与了sql语句的编译过程,导致sql语句的原意被扭曲,进而达到SQL注入
在这里插入图片描述

3.解决SQL注入

只要用户提供的信息不参与SQL语句的编译过程,问题就解决了
即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用
要想用户信息不参与SQL语句的编译,必须使用java.sql.PreparedStatement
PreparedStatement接口继承了java.sql.Statement
PreparedStatement是属于 预编译的数据库操作对象
PreparedStatement原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传值

代码展示:

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

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

    //用户登录方法
    private static boolean login(Map<String, String> userLoginInfo) {
        //打标记
        boolean loginSuccess = false;
        //单独定义变量获取用户输入的用户名和密码
        String loginName = userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get("loginPwd");
        //JDBC代码
        Connection conn = null;
        PreparedStatement ps = null; //这里使用PreparedStatement(预编译的数据库操作对象)
        ResultSet rs = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.获取链接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","root");
            //3.获取预编译的数据库操作对象
            //sql语句的框子,其中一个?表示一个占位符,一个?将来接收一个值。注意:占位符不能使用单引号括起来
            String sql = "select * from t_user where loginName = ? and loginPwd = ?";
            //程序执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译
            ps = conn.prepareStatement(sql);
            //给占位符?传值(第一个?下标是1,第二个?下标是2,JDBC中所有下标从1开始)
            ps.setString(1,loginName);
            ps.setString(2,loginPwd);
            //4.执行sql
            rs = ps.executeQuery();
            //5.处理结果集
            if (rs.next()){
                //登录成功
                loginSuccess = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6.释放资源
            if (rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

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

        System.out.println("用户名:");
        String loginName = s.nextLine();

        System.out.println("密码:");
        String loginPwd = s.nextLine();

        HashMap<String, String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName",loginName);
        userLoginInfo.put("loginPwd",loginPwd);

        return userLoginInfo;
    }
}

测试结果:解决了SQL注入
在这里插入图片描述
解决SQL注入的关键是什么?
用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用

九、Statement和PreparedStatement对比

  • Statement存在SQL注入问题,PreparedStatement解决了SQL注入问题
  • Statement是编译一次执行一次,PreparedStatement是编译一次,可执行N次。PreparedStatement效率较高
  • PreparedStatement会在编译阶段做类型的安全检查

综上所述:PreparedStatement使用较多。只有极少数的情况下需要使用Statement

那么什么情况下必须使用Statement?
业务方面要求必须支持SQL注入的时候,Statement支持SQL注入,凡是业务方面要求是需要进行SQL语句拼接的,必须使用Statement!

代码展示:

package com.xu.jdbc;

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

public class JDBCTest5 {
    public static void main(String[] args) {
        /*
		不能使用PreparedStatement!
		
        //用户在控制台输入desc就是降序,输入asc就是升序
        Scanner s = new Scanner(System.in);
        System.out.println("请输入desc或asc,desc表示降序,asc表示升序");
        System.out.println("请输入:");
        String keyWords = s.nextLine();

        //执行SQL
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //获取链接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","root");
            //获取预编译数据库操作对象
            String sql = "select * from emp order by ename ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,keyWords);
            //执行SQL
            rs = ps.executeQuery();
            //处理查询结果集
            while (rs.next()){
                System.out.println(rs.getString("ename"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        */

        //用户在控制台输入desc就是降序,输入asc就是升序
        Scanner s = new Scanner(System.in);
        System.out.println("请输入desc或asc,desc表示降序,asc表示升序");
        System.out.println("请输入:");
        String keyWords = s.nextLine();

        //执行SQL
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //获取链接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","root");
            //获取数据库操作对象
            stmt = conn.createStatement();
            //执行SQL
            String sql = "select * from emp order by ename " + keyWords;
            rs = stmt.executeQuery(sql);
            //处理查询结果集
            while (rs.next()){
                System.out.println(rs.getString("ename"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

十、JDBC事务自动提交机制

JDBC事务机制:

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

以下代码证明一下JDBC事务是否是自动提交机制:

import java.sql.*;

public class JDBCTest6 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","root");
            //3.获取预编译的数据库操作对象
            String sql = "update dept set dname = ? where deptno = ?";
            ps = conn.prepareStatement(sql);

            //第一次给占位符传值
            ps.setString(1,"x部门");
            ps.setInt(2,30);
            //4.执行sql语句
            int count = ps.executeUpdate(); //执行第一条update语句
            System.out.println(count);

            //重新给占位符传值
            ps = conn.prepareStatement(sql);
            ps.setString(1,"y部门");
            ps.setInt(2,20);
            count = ps.executeUpdate(); //执行第二条update语句
            System.out.println(count);
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally{
            //6.释放资源
            if (ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

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

测试结果:我们打断点调试,证明JDBC中只要执行任意一条DML语句,就提交一次事务

十一、账户转账演示事务

演示事务之前,我们先创建一个sql脚本,代码如下:

drop table if exists t_act;
create table t_act(
	actno int,
	balance double(7,2) //注意:7表示有效数字的个数,2表示小数位的个数
);

insert into t_act(actno,balance) values(111,20000);
insert into t_act(actno,balance) values(222,0);
commit;
select * from t_act;

==================================================
+-------+----------+
| actno | balance  |
+-------+----------+
|   111 | 20000.00 |
|   222 |     0.00 |
+-------+----------+

演示事务中,最重要的三行代码:

重点三行代码:
conn.setAutoCommit(false);
conn.commit();
conn.rollback();

代码展示:

import java.sql.*;

public class JDBCTest7 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","root");
            //将自动提交机制修改为手动提交
            conn.setAutoCommit(false); //开启事务
            //3.获取预编译的数据库操作对象
            String sql = "update t_act set balance = ? where actno = ?";
            ps = conn.prepareStatement(sql);

            //给?传值
            ps.setDouble(1,10000);
            ps.setInt(2,111);
            int count = ps.executeUpdate();

            //让上下传值代码中间出现异常
            String s = null;
            s.toString();

            //给?传值
            ps.setDouble(1,10000);
            ps.setInt(2,222);
            count += ps.executeUpdate();

            System.out.println(count == 2 ? "转账成功" : "转账失败");

            //程序能够走到这里,说明以上程序没有异常,事务结束,手动提交数据
            conn.commit(); //提交事务
        } catch (Exception e) {
            //回滚事务
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally{
            //6.释放资源
            if (ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

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

十二、JDBC工具类的封装

我们每次写JDBC六步骤非常的繁琐!我们可以将冗余代码封装成一个工具类!

import java.sql.*;

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

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

    //获取数据库连接对象
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","root");
    }

    //关闭资源
    public static void close(Connection conn, Statement ps, ResultSet rs){
        if (rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null){
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

十三、JDBC实现模糊查询

代码展示:

import com.xu.jdbc.utils.DBUtil;

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

/*
测试
 */
public class JDBCTest8 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            //获取链接
            conn = DBUtil.getConnection();
            //获取预编译数据库操作对象

            //错误写法
            /*
            String sql = "select ename form emp where ename like '_?%'";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"A");
            */

            String sql = "select ename from emp where ename like ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"_A%");
            rs = ps.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("ename"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            DBUtil.close(conn,ps,rs);
        }
    }
}

十四、乐观锁与悲观锁

在这里插入图片描述

代码展示:

import com.xu.jdbc.utils.DBUtil;

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

/*
这个程序开启一个事务,这个事务专门进行查询,并且使用行级锁(悲观锁),锁住相关的记录
 */
public class JDBCTest01 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            conn = DBUtil.getConnection();
            //开启事务
            conn.setAutoCommit(false);

            String sql = "select ename,job,sal from emp where job = ? for update";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"MANAGER");

            rs = ps.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("ename") + "," + rs.getString("job")
                + "," + rs.getDouble("sal"));
            }

            //提交事务(事务结束)
            conn.commit();
        } catch (SQLException e) {
            //回滚事务(事务结束)
            if (conn != null){
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            DBUtil.close(conn,ps,rs);
        }
    }
}

========================================================================
import com.xu.jdbc.utils.DBUtil;

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

/*
这个程序负责修改被锁定的记录
 */
public class JDBCTest02 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = DBUtil.getConnection();
            conn.setAutoCommit(false);

            String sql = "update emp set sal = sal * 1.1 where job = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,"MANAGER");
            int count = ps.executeUpdate();
            System.out.println(count);

            conn.commit();
        } catch (SQLException e) {
            if (conn != null){
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            DBUtil.close(conn,ps,null);
        }
    }
}

给第一段代码的conn.commit()打上断点,进行Debug调试。不让其提交事务。然后执行第二段代码发现并不能输出count。

当我们打开Debugger控制台点击Resume Program按钮,第二段代码才执行!说明行级锁确实生效!

一、概述: JDBC从物理结构上说就是Java语言访问数据库的一套接口集合。从本质上来说就是调用者(程序员)和实现者(数据库厂商)之间的协议。JDBC的实现由数据库厂商以驱动程序的形式提供。JDBC API 使得开发人员可以使用纯Java的方式来连接数据库,并进行操作。 ODBC:基于C语言的数据库访问接口。 JDBC也就是Java版的ODBC。 JDBC的特性:高度的一致性、简单性(常用的接口只有4、5个)。 1.在JDBC中包括了两个包:java.sql和javax.sql。 ① java.sql 基本功能。这个包中的类和接口主要针对基本的数据库编程服务,如生成连接、执行语句以及准备语句和运行批处理查询等。同时也有一些高级的处理,比如批处理更新、事务隔离和可滚动结果集等。 ② javax.sql 扩展功能。它主要为数据库方面的高级操作提供了接口和类。如为连接管理、分布式事务和旧有的连接提供了更好的抽象,它引入了容器管理的连接池、分布式事务和行集等。 注:除了标出的Class,其它均为接口。 API 说明 java.sql.Connection 与特定数据库的连接(会话)。能够通过getMetaData方法获得数据库提供的信息、所支持的SQL语法、存储过程和此连接的功能等信息。代表了数据库java.sql.Driver 每个驱动程序类必需实现的接口,同时,每个数据库驱动程序都应该提供一个实现Driver接口的类。 java.sql.DriverManager (Class) 管理一组JDBC驱动程序的基本服务。作为初始化的一部分,此接口会尝试加载在”jdbc.drivers”系统属性中引用的驱动程序。只是一个辅助类,是工具。 java.sql.Statement 用于执行静态SQL语句并返回其生成结果的对象。 java.sql.PreparedStatement 继承Statement接口,表示预编译的SQL语句的对象,SQL语句被预编译并且存储在PreparedStatement对象中。然后可以使用此对象高效地多次执行该语句。 java.sql.CallableStatement 用来访问数据库中的存储过程。它提供了一些方法来指定语句所使用的输入/输出参数。 java.sql.ResultSet 指的是查询返回的数据库结果集。 java.sql.ResultSetMetaData 可用于获取关于ResultSet对象中列的类型和属性信息的对象。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值