一文帶你搞定JDBC

前言:“只有自己強大,纔不會被別人踐踏。”
你好,我是夢陽辰,讓我們輕鬆玩編程,一起走進JDBC的世界吧!文章較長建議收藏再看!

在這裏插入圖片描述

1.JDBC概述

1.JDBC是什麼?
Java DataBase connectivity(java數據庫連接)

Java數據庫連接是Java語言中用來規範客戶端程序如何來訪問數據庫的應用程序接口,提供了諸如查詢和更新數據庫中數據的方法。

2.JDBC的本質是什麼?
JDBC是sun公司制定的一套接口(interface)

  • java.sql包下。

爲什麼java要制定一套JDBC接口?
因爲每一個數據庫的底層實現原理都不一樣。
oracle數據庫都有自己的原理。
MySQL數據庫也有自己的原理。
MS Sqlserver數據庫也有自己的原理。

這意味着如果沒有JDBC接口的話,使用不同的數據庫,就要編寫一套代碼,這樣擴展性不強,增加了程序員的負擔,爲了解決這個問題,sun公司就編寫了一套JDBC接口,不同的數據庫廠家,對此編寫了一套實現接口的類,也成爲驅動。
在這裏插入圖片描述

接口都由調用者和實現者完成。
面向接口調用,面向接口寫實現類,這都是屬於面向接口編程。

1.調用者爲程序員;不需要關心具體是哪個廠家的數據庫,只需要面向JDBC接口寫代碼。

2.實現者爲數據庫廠家,也被稱爲驅動,對JDBC進行實現(爲class文件)。

3.如果沒有實現類驅動可以編譯,但無法執行。

爲什麼要面向接口編程呢?
解耦合:降低程序的耦合度,提高程序的擴展力。
多態機制就是非常典型的:面向抽象編程。
即建議:
Animal a = new cat();
public void feed(Animal a) //面向父類型編程。

3.JDBC開發前的準備工作,先從官網下載對應的驅動jar包,然後將其配置到環境變量classpath當中。

如果是用IDEA開發,需要在IDEA中導入驅動jar包,步驟爲:點擊file–>Project Structure–>"+"–>java。然後選擇你在數據庫官網下載的驅動jar包,最後選擇要導入的項目(module)即可。如果你還在用eclipse,建議換一下IDEA,哈哈。

classpath=.;+下載的驅動包的路徑及文件名。
"."代表當前路徑,加了這個,JVM會在當前路徑下查找類,如果沒找到再到指定位置查找。

注意:雖然在jdk5之後不用配置class環境變量,但是當我們用到JDBC,或者開發JavaEE應用時,則必須要設置classpath環境變量,JVM通過classpath查找類。JDK 1.5 版本以上的JDK,會自動搜索當前路徑下的類文件,而且使用java的編譯和運行工具時,系統可以自動加載dt.jar和tools.jar文件中的java類,因此不再需要設置classpath環境變量。

2.JDBC編程六步(重點)

第一步:註冊驅動(作用:告訴Java程序,即將要連接的是哪個品牌的數據庫)

第二步:獲取連接(表示JVM的進程和數據庫進程之間的通道打開了,這屬於進程之間的通信,重量級的,使用完之後一定要關閉)

第三步:獲取數據庫操作對象(專門執行sql語句的對象)

第四步:執行SQL語句。(DQL,DML)

第五步:處理查詢結果集。(當第四步是執行select語句時纔有這一步)

第六步:釋放資源。即關閉數據庫(使用資源之後一定要關閉資源。Java和數據庫屬於進程間的通信,開啓後一定要關閉。)

1.例如:(注意註解)

/*
JDBC編程六步
*/
import java.sql.Driver;//接口
import java.sql.DriverManager;//類
import java.sql.SQLException;//異常類
import java.sql.Connection;//接口
import java.sql.Statement;//類
public class JavaJDBC 
{
	public static void main(String[] args) 
	{
		Connection conn =null;
		Statement stat = null;
		
		try{
		//1.註冊驅動
		Driver driver = new com.mysql.jdbc.Driver();//多態
		//Driver driver = new oracle.jdbc.driver.OracleDriver();//oracle的驅動
		//注意前面的Driver是接口,後面的Driver是其實現類,因爲在不同的包中,故可以這樣命名
		DriverManager.registerDriver(driver);//靜態方法
		//2.獲取連接(建立通道,數據庫和java)
		/*
		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 是服務器上某個資源名
		*/
		String url="jdbc:mysql://127.0.0.1:3306/xingkong";//或者localhost
		/*
		jdbc:mysql:// 協議
		127.0.0.1 IP地址
		3306 mysql數據庫端口號
		xingkong 具體的數據庫實例名
		說明:localhost與129.0.0.1都是本機的IP地址。
		更改地址就可以訪問其他服務器的內容。

		什麼是通信協議,有什麼用?
			通信協議是通信之前就提前定好的數據庫傳送格式。
			數據包具體怎麼傳數據,格式提前定好了。
		
		oracle的URL:
			jdbc:oracle:thin:@localhost:1521:xingkong
		*/
		String user="root";
		String password = "1234";
		conn =DriverManager.getConnection(url,user,password);//靜態方法
		System.out.println("數據庫連接對象="+conn);

		//3.獲取數據庫操作對象(Statement專門執行sql語句)
		 stat = conn.createStatement();
		//4.執行sql
		String sql = "insert into dept(deptno,dname,loc )values(50,'fgagad','sfd')";
		//專門執行DML語句的(insert ,delete,update)
		//返回值是“影響數據庫的記錄條數”
		int count = stat.executeUpdate(sql);
		System.out.println(count==1?"保存成功":"保存失敗");
		//5.處理查詢結果集(這裏不是查詢,所以不需要這一步)
		}catch(SQLException e){
		e.printStackTrace();
		}finally{
			//6.釋放資源
			//要遵循從小大以此關閉
			//分別對其try...catch
			try{
			if(stat!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
				stat.close();
			}
			}catch(SQLException e){
				e.printStackTrace();
			}
			try{
			if(conn!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
				conn.close();
			}
			}catch(SQLException e){
				e.printStackTrace();
			}
	
		}
		
	}
}

url解釋:
url:統一資源定位符(網絡中某個資源的絕對路徑)
如:https://www.baidu.com/ 這就是一個URL
URL包括哪幾個部分?
協議,IP,PORT,資源名
如:http://182.61.200.7:80/index.html
http://通信協議
182.61.200.7 服務器IP地址
80 服務器上軟件的端口
ndex.html 是服務器上某個資源名

因此:
jdbc:mysql:// 協議
127.0.0.1 IP地址
3306 mysql數據庫端口號
xingkong 具體的數據庫實例名

說明:localhost與129.0.0.1都是本機的IP地址。
更改地址就可以訪問其他服務器的內容。

什麼是通信協議,有什麼用?
通信協議是通信之前就提前定好的數據庫傳送格式。
數據包具體怎麼傳數據,格式提前定好了。

oracle的URL:
jdbc:oracle:thin:@localhost:1521:xingkong

2.簡化代碼並執行刪除和更新操作:

import java.sql.*;
public class JDBCTest2{
	public static void main(String[] args){
	Connection conn = null;
	Statement stat = null;
	try{
	//1註冊驅動
	DriverManager.registerDriver(new com.mysql.jdbc.Driver());
	//2獲取連接
	conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/xingkong","root","0910");
	//3獲取數據庫操作對象
	stat =conn.createStatement();
	//4執行SQL語句
	//String sql ="delete from dept where deptno = 40";
	String sql ="update dept set dname = 'xin',loc='beijin' where deptno =20";
	int count = stat.executeUpdate(sql);
	System.out.println(count==1?"修改成功":"修改失敗");

	}catch(SQLException e){
		e.printStackTrace();
	}finally{
			try{
			if(stat!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
				stat.close();
			}
			}catch(SQLException e){
				e.printStackTrace();
			}
			try{
			if(conn!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
				conn.close();
			}
			}catch(SQLException e){
				e.printStackTrace();
			}
	}
	}
}

3.將連接數據庫的所有信息配置到,配置文件中:(使用資源綁定器)
這裏爲重點,因爲這種方式非常靈活,這很常用。注意用這種方式需要配置,jdbc.properties文件哦。

如果這裏看不懂,這篇文章也許會幫到你:
java的反射機制,看完這篇輕鬆應對高級框架(超詳細總結)

import java.sql.*;
import java.util.*;
public class  JDBCTest3
{
	public static void main(String[] args) 
	{
		//使用資源綁定器綁定屬性配置文件
		ResourceBundle bundle  = ResourceBundle.getBundle("jdbc");
		String driver = bundle.getString("driver");//driver=com.mysql.jdbc.Driver
		String url = bundle.getString("url");
		String user = bundle.getString("user");
		String password= bundle.getString("password");
		Connection conn = null;
		Statement stat = null;
		try{
			//1.註冊驅動,這裏爲註冊驅動的另一種方式
			Class.forName(driver);//反射獲取類,這是會在類加在之前執行靜態代碼塊
			//2.獲取連接
			conn = DriverManager.getConnection(url,user,password);
			//3.獲取數據庫操作對象
			stat=conn.createStatement();
			//4.執行SQL語句
			String sql="update dept set dname ='sfaf',loc ='shenzhen' where deptno =20";
			int count = stat.executeUpdate(sql);
			System.out.println(count==1?"更新成功":"更新失敗");
			
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			//6.釋放資源
			//要遵循從小大以此關閉
			//分別對其try...catch
			try{
			if(stat!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
				stat.close();
			}
			}catch(SQLException e){
				e.printStackTrace();
			}
			try{
			if(conn!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
				conn.close();
			}
			}catch(SQLException e){
				e.printStackTrace();
			}
		}
	}
}

4.處理查詢結果集
遍歷結果集分析圖:
在這裏插入圖片描述

import java.util.*;
import java.sql.*;
public class JDBCTest4 
{
	public static void main(String[] args) 
	{
		//使用資源綁定器綁定屬性配置文件
		ResourceBundle bundle  = ResourceBundle.getBundle("jdbc");
		String driver = bundle.getString("driver");//driver=com.mysql.jdbc.Driver
		String url = bundle.getString("url");
		String user = bundle.getString("user");
		String password= bundle.getString("password");
		Connection conn = null;
		Statement stat =null;
		ResultSet rs =null;
		try{
		//1.註冊驅動
		Class.forName(driver);

		//2.獲取連接
		conn=DriverManager.getConnection(url,user,password);

		//3.獲取數據庫操作對象
		stat=conn.createStatement();

		//4.執行sql語句
		String sql="select empno,ename,sal from emp";
		//int executeUpdate(insert/delete/update)
		//ResultSet executeQuery(select)
		rs= stat.executeQuery(sql);//專門執行DQL語句的方法

		//5.處理查詢結果集
		//boolean flag = rs.next();
		/*if(rs.next()){
			//不管數據庫中的數據類型是什麼,都以String的形式取出。
			String ss = rs.getString(1);//JDBC中所有下標從1開始。不是從0開始。
			String ename = rs.getString(2);//第二列中的那個數據
			String sal = rs.getString(3);//第三列的那個數據
			System.out.println(ss+","+ename+","+sal);
		}*/
		while(rs.next()){
			//不管數據庫中的數據類型是什麼,都以String的形式取出,如果想以Int類型取出,改爲getInt方法,其它類似。
			String ss = rs.getString("empno");
			String ename = rs.getString("ename");//如果重命名,這裏寫重命名後的名字
			String sal = rs.getString("sal");
			System.out.println(ss+","+ename+","+sal);
		}
		}catch(Exception e){
			
		}finally{
			//6.釋放資源
			try{
			if(rs!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
				rs.close();
			}
			}catch(SQLException e){
				e.printStackTrace();
			}
			try{
			if(stat!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
				stat.close();
			}
			}catch(SQLException e){
				e.printStackTrace();
			}
			try{
			if(conn!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
				conn.close();
			}
			}catch(SQLException e){
				e.printStackTrace();
			}
		}
	}
}

*不管數據庫中的數據類型是什麼,都以String的形式取出,如果想以Int類型取出,改爲getInt方法,並且,用getInt取出的數據可以參加數學運算,其它類似。

3.用數據庫實現用戶登錄功能

模擬用戶登入:
1.程序運行時的時候,提供一個輸入的入口,可以讓用戶輸入用戶名和密碼。

2.用戶輸入用戶名和密碼之後,提交信息,java程序收集到用戶信息

  • java程序連接數據庫驗證用戶名和密碼是否合法
  • 合法登陸成功,否則登入失敗

3.數據的準備:

  • 在實際開發中,表的設計會使用專業的建模工具,這裏使用:PowerDesigner
  • 使用PowerDesigner工具進行數據表的設計

4.存在的問題(表中並沒有這個用戶,但是顯示登錄成功)

  • 用戶:fdas
  • 密碼:fdas’ or ‘1’='1
  • 登陸成功
  • 這種現象被稱爲SQL注入(安全隱患).

5.導致sql注入的根本原因是什麼?

  • 用戶輸入的信息中含有sql語句的關鍵字,並且這些關鍵字參與sql語句的編譯過程,
  • 導致sql語句的原意被扭曲,進而達到sql注入。

6.詳情請看源代碼:

package Day1;
//模擬用戶登入功能
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Test1 {
    public static void main(String[] args) {
        //初始化界面
        Map<String,String> userInfromation =initUi();
        //驗證用戶名和密碼
       boolean loginSuccess= login(userInfromation);
       //輸出結果
        System.out.println(loginSuccess?"登陸成功":"登錄失敗");
    }
/**
* 用戶登錄
 * 用戶登錄信息
 * false表示失敗,true表示成功*/
    private static boolean login(Map<String, String> userInfromation) {
        boolean falg =false;
        String loginName= userInfromation.get("username");//用戶名
        String passcode=userInfromation.get("passcode");//用戶密碼
        //JDBC代碼
        Connection conn = null;
        Statement stat= null;
        ResultSet rs= null;
        try {
            //1.註冊驅動
            Class.forName("com.mysql.jdbc.Driver");
            //2.獲取連接
            conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/newdata?useSSL=false","root","123456");
            //3.獲取數據庫操作對象
            stat = conn.createStatement();
            //4.執行sql
            String sql = "select * from t_users where loginName='"+loginName+"'and passcode='"+passcode+"'";
           //以上正好完成了sql語句的拼接,以下代碼是,發送sql語句給DBMS進行編譯。
            //正好將用戶提供的”非法信息“編譯進去。導致了原sql語句的含義被扭曲了。
            rs=stat.executeQuery(sql);
            //處理結果集
            if(rs.next()){
                falg =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(stat!=null){
                try {
                    stat.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if(conn!=null){
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }


        return  falg;
    }

    /**
* 初始化用戶界面
* 返回用戶名和密碼等信息*/
    private static Map<String, String> initUi() {
        Scanner sc = new Scanner(System.in);
        System.out.println("用戶名:");
        String username=sc.nextLine();
        System.out.println("密碼:");
        String passcode = sc.nextLine();
        Map<String,String>userInfromation= new HashMap<>();
        userInfromation.put("username",username);
        userInfromation.put("passcode",passcode);
        return userInfromation;
    }

}

*你會發現某些安全性不高的網站用這種SQL注入的方式可以成功登錄,但是可能會顯示封號或者報出警告等。

7.那麼我們該怎麼解決SQL注入的問題呢?(重點,安全性高常用)
問題關鍵在於我們提供的密碼參加了編譯。因此我們可以從這裏下手。我們可以將提供的信息不參與SQL語句的編譯,因此我們需要使用java.sql.PreparedStatement接口。

1.將Statement接口換成PrepareStatement。PrepareStatement是Statement子接口。
PrepareStatement屬於預編譯的數據庫操作對象。
其原理是:預先對SQL語句的框架進行編譯,然後再給SQL語句傳“值”。

2.將createStatemet();方法,改爲:prepareStatement(sql);sql語句提前。並將sql語句的值改爲佔位符。

3.給佔位符“?”傳值,(第一個問號下表是1,第二個問號下標是2,JDBC中所有下表從1開始。)

詳細請看源代碼:

package Day1;
//模擬用戶登入功能
* */

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

public class Test1 {
    public static void main(String[] args) {
        //初始化界面
        Map<String,String> userInfromation =initUi();
        //驗證用戶名和密碼
       boolean loginSuccess= login(userInfromation);
       //輸出結果
        System.out.println(loginSuccess?"登陸成功":"登錄失敗");
    }
/**
* 用戶登錄
 * 用戶登錄信息
 * false表示失敗,true表示成功*/
    private static boolean login(Map<String, String> userInfromation) {
        boolean falg =false;
        String loginName= userInfromation.get("username");//用戶名
        String passcode=userInfromation.get("passcode");//用戶密碼
        //JDBC代碼
        Connection conn = null;
        PreparedStatement prep= null;
        ResultSet rs= null;
        try {
            //1.註冊驅動
            Class.forName("com.mysql.jdbc.Driver");
            //2.獲取連接
            conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/newdata?useSSL=false","root","0910");
            //3.獲取預編譯的數據庫操作對象
            String sql = "select * from t_users where loginName=? and passcode=?";//?表示佔位符,只能填充值,
            //程序執行到這,會發送sql語句框子給DBMS,然後DBMS進行sql語句的預先編譯。
            prep = conn.prepareStatement(sql);
            //給佔位符?傳值,(第一個問號下表是1,第二個問號下標是2,JDBC中所有下表從1開始。
            prep.setString(1,loginName);//會自動加單引號,如果是數字用setInt()方法。
            prep.setString(2,passcode);
            //4.執行sql
           //以上正好完成了sql語句的拼接,以下代碼是,發送sql語句給DBMS進行編譯。
            //正好將用戶提供的”非法信息“編譯進去。導致了原sql語句的含義被扭曲了。
            rs=prep.executeQuery();
            //處理結果集
            if(rs.next()){
                falg =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(prep!=null){
                try {
                    prep.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if(conn!=null){
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }


        return  falg;
    }

    /**
* 初始化用戶界面
* 返回用戶名和密碼等信息*/
    private static Map<String, String> initUi() {
        Scanner sc = new Scanner(System.in);
        System.out.println("用戶名:");
        String username=sc.nextLine();
        System.out.println("密碼:");
        String passcode = sc.nextLine();
        Map<String,String>userInfromation= new HashMap<>();
        userInfromation.put("username",username);
        userInfromation.put("passcode",passcode);
        return userInfromation;
    }

}

運行結果可以看出解決了SQL語句的注入問題。在這裏插入圖片描述
8.Statement和PrepareStatement接口對比:
1.PrepareStatement接口解決了SQL注入的問題。

2.Statement第一次已經編譯的SQL語句,後面出現一模一樣的不會再編譯,直接執行,一但多了一個空格就會重新編譯。而PrepareStatement沒有這個問題只編譯一次。因此後者效率較高。

3.PrepareStatement會在編譯的時候進行安全檢查。

9.因此可以看出PrepareSatement使用較多(單純傳值。當業務需要SQL注入的時候,使用Statement.
比如:在升序和降序的時候需要使用SQL注入。

因爲setString(1,desc)會自動加單引號,但是我們不需要單引號。

4.用PreparedStatement完成增刪改查

直接看代碼:注意得配置jdbc.properties文件。

package Day1;
import java.util.*;
import java.sql.*;
public class PreparedStatementTest1 {
    public static void main(String[] args) {
        //使用資源綁定器綁定屬性配置文件
        ResourceBundle bundle  = ResourceBundle.getBundle("jdbc");
        String driver = bundle.getString("driver");//driver=com.mysql.jdbc.Driver
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password= bundle.getString("password");
        Connection conn = null;
        PreparedStatement prep =null;
        try{
            //1.註冊驅動
            Class.forName(driver);

            //2.獲取連接
            conn=DriverManager.getConnection(url,user,password);

            //3.獲取預編譯的數據庫操作對象
            //增
            /*String sql = "insert  into dept(deptno,dname,loc)values(?,?,?)";
            prep=conn.prepareStatement(sql);
            prep.setInt(1,60);
            prep.setString(2,"xing");
            prep.setString(3,"shenzhen");*/
            //改
           /* String sql = "update dept set dname = ?,loc =? where deptno =?";
            prep=conn.prepareStatement(sql);
            prep.setString(1,"asdf");
            prep.setString(2,"ShangHai");
            prep.setInt(3,60);*/
            //刪除
            String sql = "delete  from dept where deptno =?";
           prep=conn.prepareStatement(sql);
            prep.setInt(1,60);

            //4.執行sql語句
            //int executeUpdate(insert/delete/update)
            //ResultSet executeQuery(select)
            int count=prep.executeUpdate();
            System.out.println(count);
        }catch(Exception e){

        }finally{
            //6.釋放資源
            try{
                if(prep!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
                    prep.close();
                }
            }catch(SQLException e){
                e.printStackTrace();
            }
            try{
                if(conn!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
                    conn.close();
                }
            }catch(SQLException e){
                e.printStackTrace();
            }
        }
    }
}

5.事務問題

JDBC中的事務是自動提交的:
1.只要執行任意一條DML語句,則自動提交一次。這是JDBC默認的事務行爲。

  • 但是在實際業務中,同常都是N條DML語句共同聯合才能完成的,必須保證他們這些DML
  • 在同一個事物中同時成功或者同時失敗。

自動提交存在的問題:
賬戶轉賬演示:(單機事務)

package Day1;

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

//JDBC事務機制
public class JDBCTest2 {
    public static void main(String[] args) {
        //使用資源綁定器綁定屬性配置文件
        ResourceBundle bundle  = ResourceBundle.getBundle("jdbc");
        String driver = bundle.getString("driver");//driver=com.mysql.jdbc.Driver
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password= bundle.getString("password");
        Connection conn = null;
        PreparedStatement prep =null;
        try{
            //1.註冊驅動
            Class.forName(driver);

            //2.獲取連接
            conn= DriverManager.getConnection(url,user,password);
            //將自動提交改爲手動提交
            conn.setAutoCommit(false);

            //3.獲取預編譯的數據庫操作對象
            //賬戶向另一個賬戶轉賬
           String sql = "update t_acount set balance = ? where acount =?";
            prep=conn.prepareStatement(sql);
            prep.setDouble(1,333);//轉333
            prep.setInt(2,123);
            int count = prep.executeUpdate();
            //假設遇到異常
            String s =null;
            s.toString();

            prep.setDouble(1,334);//收333
            prep.setInt(2,1234);

            //4.執行sql語句
            //int executeUpdate(insert/delete/update)
            //ResultSet executeQuery(select)
            count+=prep.executeUpdate();
            System.out.println(count==2?"轉賬成功":"轉賬失敗");
            conn.commit();
        }catch(Exception e){
            if(conn!=null){
                try {
                    conn.rollback();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally{
            //6.釋放資源
            try{
                if(prep!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
                    prep.close();
                }
            }catch(SQLException e){
                e.printStackTrace();
            }
            try{
                if(conn!=null){//不能一起try,如果一起try,假如一個出問題,其他也出問題
                    conn.close();
                }
            }catch(SQLException e){
                e.printStackTrace();
            }
        }
    }

}

重點代碼:
conn.setAutoCommit(flase);
conn.commit();
conn.rollback();
分佈式事務(目前瞭解即可)

6.JDBC工具類的封裝

引出問題:你有木有發現前面寫的JDBC的六步太繁瑣了,代碼太多了,那可不可以簡化呢?

當然可以,我們可以將其封裝解決這個問題

知識準備:
工具類的構造方法都是私有的。
因爲工具類當中的方法都是靜態的,不需要new對象,直接採用類名調用。

自己封裝的工具類:

package Day1;

import java.sql.*;

public class JDBCTest3 {
    private JDBCTest3(){
    }
    //靜態代碼塊在類加載的時候只執行一次
    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/newdata?useSSL=false","root","123456");
        }

    /*conn 連接對象
    * prep 數據庫操作對象
    * rs  結果集
    * */
    public static void close (Connection conn, Statement prep, ResultSet rs){
        //6.釋放資源
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(prep!=null){
            try {
                prep.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

用自己封裝的類實現模糊查詢:

package Day1;

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

public class JDBCTest4 {
    public static void main(String[] args) {
        Connection conn =null;
        PreparedStatement prep =null;
        ResultSet rs = null;
        try {
            //獲取連接
            conn=JDBCTest3.getConnection();
            //獲取預編譯的數據庫操作對象
            String sql ="select name from t_student where name like ?";
            prep=conn.prepareStatement(sql);
            prep.setString(1,"_e%");
            rs=prep.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("name"));
            }

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JDBCTest3.close(conn,prep,rs);
        }
    }
}

7.行級鎖(悲觀鎖和樂觀鎖)

1.樂觀鎖:多線程併發,都可以對一行數據修改,只不過需要設置一個版本號。
版本號:比如事務1和事務2開始讀到版本號爲1.

其中事務1先對數據進行了修改,修改後查找版本號爲1,於是提交修改的數據,將版本號改爲2.

然後事務2後修改,修改之後查詢版本號爲2,和最初的版本號不相同,進行回滾操作。
思考:根據上述描述發現,如果兩個事務對數據同時修改就會造成,前者數據更新的丟失。

*利用數據庫中的數據和已經取出的數據的一致性做爲“鎖”,與for update相比,樂觀鎖機制是等到更改數據的時候纔去校驗,悲觀鎖是讀取數據就開始做了校驗,從這個角度來看,樂觀鎖是對數據庫沒有額外開銷,那麼效率相對是高的。

*需要更改的字段可以作爲樂觀鎖的驗證字段;或者表裏建立version版本號,每更新一次數據版本號+1;或者加lastupdatedate(最後更新時間)

2.悲觀鎖(行級鎖):事務必須進行排隊,數據被鎖住了,不允許併發。
在select最後面加上 for update;

單表for update:

沒有在for update 時加nowait時,事務需要等待其他事務執行。加上後就不用等待其他事務執行了,一判斷有事務,立馬拋出異常。

1.select * from table where name= ‘MengYangChen’ for update 鎖住了這條數據,那麼另一個事務對該數據進行DML操作或者也執行同樣的for update操作時,會檢測到這筆數據上有行級鎖,那麼就會等待着鎖釋放;

2.select * from table where name= ‘MengYangChen’ nowait,意思就是如果這筆數據上本身加了鎖,另一個事務去執行這句SQL的時候,發現加了鎖,就會直接拋出異常(ORA-00054:資源正忙),不會等待這筆數據的鎖釋放。

3.select * from table where name= ‘MengYangChen’ wait 5;意思就是如果這筆數據被鎖住,另外一個人如果執行這句SQL後,會等待5秒,如果5秒後這句SQL還沒有得到這筆數據的鎖,就會拋出異常。

4.skip locked
例如: 我們如果先執行 A語句:select * from table where name=‘MengYangChen’ for update 這樣就會把MengYangChen這行加上鎖,然後再執行 B語句的時候:select * from table where name = ‘MengYangChen’ and name=‘XingKong’ for update;這時候肯定查不出來,因爲A已經把B要加鎖的數據鎖了,這樣B語句,連XingKong改行的數據都查不出來。

我們可以用skip locked解決該問題:
把B語句改爲:select * from table where name = ‘MengYangChen’ and name=‘XingKong’ for update skip locked;意思就是執行的時候如果發現要查詢的數據有鎖,就把加了鎖的數據跳過,把剩下未加鎖的數據加鎖,然後查詢出來!

關聯表for update:

現在大部分業務都是聯表查詢,如果用for update 的話,就會把所有關聯表查詢出來的列所在的行全部加鎖。
例如: select * from t1,t2 where t1.id = t2.id and t1.age = ‘20’ for update;就會把t1和t2兩個表中符合條件的行鎖定。

那如果我只想鎖住一個表該怎麼辦呢?
在for update後面添加 of column_name(你要加鎖的字段名)。
例子: select * from t1,t2 where t1.id = t2.id and t1.age = ‘20’ for update of t1.id;這樣只會對t1符合條件的行進行加鎖。

關注公衆號【輕鬆玩編程】後臺回覆“計算機資源”,即可獲得學習資源。

“一朵花凋零荒蕪不了整個春天,一次跌倒也荒廢不了整段旅程。走下去,憑着耐心和拼搏,生活自會給予你全部答案。”
我是夢陽辰,期待與你相遇!看到這裏素質三連一下唄!

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章