前言:“只有自己強大,纔不會被別人踐踏。”
你好,我是夢陽辰,讓我們輕鬆玩編程,一起走進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符合條件的行進行加鎖。
關注公衆號【輕鬆玩編程】後臺回覆“計算機資源”,即可獲得學習資源。
“一朵花凋零荒蕪不了整個春天,一次跌倒也荒廢不了整段旅程。走下去,憑着耐心和拼搏,生活自會給予你全部答案。”
我是夢陽辰,期待與你相遇!看到這裏素質三連一下唄!