SQL學習筆記之數據庫專題(四):淺談JDBC用法

數據庫廠商提供的用來操作數據庫用的jar包就是數據庫驅動。各個廠商如果提供各自的數據庫驅動的話會導致開發人員學習成本太高,所以sun公司提供了一套數據庫驅動應該遵循的接口規範,這套規範就叫做JDBC,本質上是很多的接口。簡而言之,JDBC就是一套操作數據庫的接口規範,由於所有的數據庫驅動都遵循JDBC規範,我們在學習和使用數據庫時只要學習JDBC中的接口就可以了。

組成JDBC的2個包:java.sql,javax.sql,開發JDBC應用需要以上2個包的支持外,還需要導入相應JDBC的數據庫實現(即數據庫驅動)。

我們先看看JDCB的使用步驟:

*在數據庫中建立好表
*在程序中導入數據庫驅動包
1.註冊數據庫驅動
DriverManager.registerDriver(new Driver());//缺點一:觀察mysqlDriver源碼發現此方法導致了數據庫驅動被註冊了兩次。缺點二:整個程序域mysql數據庫驅動綁定增加了耦合性
Class.forName(“com.mysql.jdbc.Driver”);
2.獲取連接
DriverManager.getConnection(url, user, password);
~url的寫法:
Oracle寫法:jdbc:oracle:thin:@localhost:1521:sid
SqlServer—jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid
MySql—jdbc:mysql://localhost:3306/sid
~url可以接的參數
user、password
useUnicode=true&characterEncoding=UTF-8


3.獲取傳輸器
createStatement():創建向數據庫發送sql的statement對象。
prepareStatement(sql) :創建向數據庫發送預編譯sql的PrepareSatement對象。
4.利用傳輸器執行sql語句獲取結果集
executeQuery(String sql) :用於向數據發送查詢語句。
executeUpdate(String sql):用於向數據庫發送insert、update或delete語句
execute(String sql):用於向數據庫發送任意sql語句


5.遍歷結果集取出結構
ResultSet以表的樣式在內存中保存了查詢結果,其中還維護了一個遊標,最開始的時候遊標在第一行之前,每調用一次next()方法就試圖下移一行,如果移動成功返回true;
ResultSet還提供了很多個Get方法,用來獲取查詢結果中的不同類型的數據
除了next方法,還有以下方法可以用來遍歷結果集:
next():移動到下一行
Previous():移動到前一行
absolute(int row):移動到指定行
beforeFirst():移動resultSet的最前面。
afterLast() :移動到resultSet的最後面。
6.釋放資源
conn是一個有限的資源,用完立即要釋放表
stat佔用內存,所以使用完後也要釋放
rs佔用內存,所以使用完後也要釋放
釋放時後創建的先釋放
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
rs = null;
}
}
if(stat != null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
stat = null;
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
conn = null;
}
}


再看一個簡單的例子:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class FreedomJDBCDemo1 {
	public static void main(String[] args){
		Connection conn = null;
		Statement stat = null;
		ResultSet rs = null;
		try{
			//1.註冊數據庫驅動
			//--由於mysql在Driver類的實現中自己註冊了一次,而我們又註冊了一次,於是會導致MySql驅動被註冊兩次
			//--創建MySql的Driver對象時,導致了程序和具體的Mysql驅動綁死在了一起,在切換數據庫時需要改動java代碼
			//DriverManager.registerDriver(new Driver());
			Class.forName("com.mysql.jdbc.Driver");
			//2.獲取數據庫連接,這裏使用簡潔寫法,可以省略掉localhost和端口
			conn = DriverManager.getConnection("jdbc:mysql:///freedom?user=root&password=root");
			//3.獲取傳輸器對象
			stat = conn.createStatement();
			//4.利用傳輸器傳輸sql語句到數據庫中執行,獲取結果集對象
			rs = stat.executeQuery("select * from user");
			//5.遍歷結果集獲取查詢結果
			while(rs.next()){
				String name = rs.getString("name");
				System.out.println(name);
			}
		}catch (Exception e) {
			e.printStackTrace();
		}finally{
			//6.關閉資源
			if(rs!=null){
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}finally{
					rs = null;
				}
			}
			if(stat!=null){
				try {
					stat.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}finally{
					stat = null;
				}
			}
			if(conn!=null){
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}finally{
					conn = null;
				}
			}
		}
	}
}

我們可以將上述例子封裝一下,將公共部分比如鏈接數據庫,關閉資源等操作,封裝起來作爲一個工具類。我們可以將路徑、用戶民和密碼用適配好。以後只需要修改配置文件即可。

配置文件內容如下:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///freedom
user=root
password=root


看工具類:

import java.io.FileReader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtils {
	private static Properties prop = null;
	private JDBCUtils() {
	}
	static{
		try{
			prop = new Properties();
			prop.load(new FileReader(JDBCUtils.class.getClassLoader().getResource("config.properties").getPath()));
		}catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 獲取連接
	 * @throws ClassNotFoundException 
	 * @throws SQLException 
	 */
	public static Connection getConn() throws ClassNotFoundException, SQLException{
		// 1.註冊數據庫驅動
		Class.forName(prop.getProperty("driver"));
		// 2.獲取連接
		return DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("user"), prop.getProperty("password"));
		
	}
	/**
	 * 關閉連接
	 */
	public static void close(ResultSet rs, Statement stat,Connection conn){
		if(rs!=null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}finally{
				rs = null;
			}
		}
		if(stat!=null){
			try {
				stat.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}finally{
				stat = null;
			}
		}
		if(conn!=null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}finally{
				conn = null;
			}
		}
	
	}
}

我們再看使用工具類簡化後的操作例子:

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

import org.junit.Test;

import com.itheima.util.JDBCUtils;

public class FreedomJDBC {

	/**
	 * @Title: add
	 * @Description: 增加
	 * @throws
	 */
	@Test
	public void add() {
		Connection conn = null;
		Statement stat = null;
		try {
			// 1.註冊數據庫驅動
			// 2.獲取連接
			conn = JDBCUtils.getConn();
			// 3.獲取傳輸器對象
			stat = conn.createStatement();
			// 4.執行sql語句
			int count = stat
					.executeUpdate("insert into user values (null,'freedom','123456','[email protected]','2012-01-01')");
			// 5.處理結果
			if (count > 0) {
				System.out.println("執行成功!影響到的行數爲" + count);
			} else {
				System.out.println("執行失敗!!");
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 6.關閉資源
			JDBCUtils.close(null, stat, conn);
		}
	}

	/**
	 * @Title: delete
	 * @Description:刪除
	 * @throws
	 */
	@Test
	public void delete() {
		Connection conn = null;
		Statement stat = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			stat = conn.createStatement();
			stat.executeUpdate("delete from user where name='freedom'");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, stat, conn);
		}
	}

	/**
	 * @Title: update
	 * @Description: 更新
	 * @throws
	 */
	@Test
	public void update() {
		Connection conn = null;
		Statement stat = null;
		try {
			conn = JDBCUtils.getConn();
			stat = conn.createStatement();
			stat
					.executeUpdate("update user set password=666 where name='freedom'");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(null, stat, conn);
		}
	}

	/**
	 * @Title: find
	 * @Description: 查詢
	 * @throws
	 */
	@Test
	public void find() {
		Connection conn = null;
		Statement stat = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			stat = conn.createStatement();
			rs = stat.executeQuery("select * from user where name='freedom'");
			while (rs.next()) {
				String name = rs.getString("name");
				String password = rs.getString("password");
				System.out.println(name + ":" + password);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, stat, conn);
		}
	}
}

SQL注入攻擊:
由於dao中執行的SQL語句是拼接出來的,其中有一部分內容是由用戶從客戶端傳入,所以當用戶傳入的數據中包含sql關鍵字時,就有可能通過這些關鍵字改變sql語句的語義,從而執行一些特殊的操作,這樣的攻擊方式就叫做sql注入攻擊

PreparedStatement利用預編譯的機制將sql語句的主幹和參數分別傳輸給數據庫服務器,從而使數據庫分辨的出哪些是sql語句的主幹哪些是參數,這樣一來即使參數中帶了sql的關鍵字,數據庫服務器也僅僅將他當作參數值使用,關鍵字不會起作用,從而從原理上防止了sql注入的問題


PreparedStatement主要有如下的三個優點:
~1.可以防止sql注入
~2.由於使用了預編譯機制,執行的效率要高於Statement
~3.sql語句使用?形式替代參數,然後再用方法設置?的值,比起拼接字符串,代碼更加優雅.

看使用代碼:

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

import com.itheima.util.JDBCUtils;

public class JDBCDemo3 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			ps = conn
					.prepareStatement("select * from user where name=? and password=?");
			ps.setString(1, "freedom");
			ps.setString(2, "666");

			rs = ps.executeQuery();
			while (rs.next()) {
				System.out.println(rs.getString("email"));
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, ps, conn);
		}
	}
}

JDBC其實還支持大文本和大二進制的處理,但是實際開發中我們很少使用到,一般情況下,處理這些大數據可能還需要修改虛擬機的啓動內存大小。這裏就講講大二進制數據組的存儲和讀取吧。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import org.junit.Test;

import com.itheima.util.JDBCUtils;

/*
 * 數據庫中創建一個表
 create table blobdemo(
 id int primary key auto_increment,
 name varchar(100),
 content MEDIUMBLOB
 );
 */
public class BlobDemo1 {

	@Test
	public void addBlob() {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			ps = conn
					.prepareStatement("insert into blobdemo values (null,?,?)");
			ps.setString(1, "高達.mp3");
			File file = new File("1.mp3");
			ps.setBinaryStream(2, new FileInputStream(file), (int) file
					.length());
			ps.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, ps, conn);
		}
	}

	@Test
	public void findBlob() {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			ps = conn.prepareStatement("select * from blobdemo");
			rs = ps.executeQuery();
			while (rs.next()) {
				String name = rs.getString("name");
				InputStream in = rs.getBinaryStream("content");
				OutputStream out = new FileOutputStream(name);

				byte[] bs = new byte[1024];
				int i = 0;
				while ((i = in.read(bs)) != -1) {
					out.write(bs, 0, i);
				}
				in.close();
				out.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, ps, conn);
		}
	}
}

有時候,當需要向數據庫發送一批SQL語句執行時,應避免向數據庫一條條的發送執行,而應採用JDBC的批處理機制,以提升執行效率。JDBC的批處理有兩種方式,各有各的優缺點。我們先看第一種方式:

import java.sql.Connection;
import java.sql.Statement;

import com.itheima.util.JDBCUtils;

/*
	create database day10batch;
	use day10batch;
	create table batchDemo(
		id int primary key auto_increment,
		name varchar(20)
	);
	insert into batchDemo values(null,'aaaa');
	insert into batchDemo values(null,'bbb');
	insert into batchDemo values(null,'cc');
	insert into batchDemo values(null,'d');
 */
/*
	Statement方式執行批處理:
		優點:可以執行多條不同結構的sql語句
		缺點:沒有使用預編譯機制,效率低下,如果要執行多條結構相同僅僅參數不同的sql時,仍然需要寫多次sql語句的主幹
 */
public class StatementBatch {
	public static void main(String[] args) {
		Connection conn = null;
		Statement stat = null;
		try{
			conn = JDBCUtils.getConn();
			stat = conn.createStatement();
			stat.addBatch("create database day10batch");
			stat.addBatch("use day10batch");
			stat.addBatch("create table batchDemo("+
								"id int primary key auto_increment,"+
								"name varchar(20)"+
							")");
			stat.addBatch("insert into batchDemo values(null,'aaaa')");
			stat.addBatch("insert into batchDemo values(null,'bbb')");
			stat.addBatch("insert into batchDemo values(null,'cc')");
			stat.addBatch("insert into batchDemo values(null,'d')");
			
			stat.executeBatch();
		}catch (Exception e) {
			e.printStackTrace();
		}finally{
			JDBCUtils.close(null, stat, conn);
		}
	}
}

再看第二種方式:

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

import com.itheima.util.JDBCUtils;
/*
 	create table psbatch(
 		id int primary key auto_increment,
 		name varchar(30)
 	);
 */
/*
	prparedStatement 方式實現的批處理:
		優點:有預編譯機制,效率比較高.執行多條結構相同,參數不同的sql時,不需要重複寫sql的主幹
		缺點:只能執行主幹相同參數不同的sql,沒有辦法在一個批中加入結構不同的sql
 */
public class PSBatch {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		try{
			conn = JDBCUtils.getConn();
			ps = conn.prepareStatement("insert into psbatch values(null,?)");
			
			for(int i=1;i<=100000;i++){
				ps.setString(1, "name"+i);
				ps.addBatch();
				
				if(i%1000==0){
					ps.executeBatch();
					ps.clearBatch();
				}
			}
			ps.executeBatch();
			
		}catch (Exception e) {
			e.printStackTrace();
		}finally{
			JDBCUtils.close(null, ps, conn);
		}
	}
}

好了,JDBC的基礎用法到此講解完畢,希望能夠幫助到看到此文的人。


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