# JDBC入門
[TOC]
## 導學
JDBC的全稱為:Java DataBase Connectivity(java數據庫連接),這是一門Java連接數據庫的技術,可對多種數據庫進行統一操作,不需要對單個數據庫的驅動進行詳細了解。

【僅需要了解 JDBC 的規范即可】 - 具體驅動會具體實現相應功能
## 開發步驟
1. 選擇數據庫:加載數據庫驅動
2. 連接數據庫
3. 創建數據庫查詢
4. 獲取查詢結果
5. 關閉查詢和連接
在使用 JDBC 的時候,需要關注的幾個問題
* 查詢分為操作類(增加、刪除、修改)和查詢類。
* 要避免重復的創建連接,增加數據庫的連接數。
* 注意異常的處理邏輯,保證沒有未關閉的無效連接存在。
示例:
~~~
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Demo {
public static void main(String[] args) {
getDataFromDataBase();
}
public static void getDataFromDataBase() {
Connection conn = null;//Connection是與特定數據庫連接回話的接口
PreparedStatement ps = null;
try {
//1.注冊驅動(靜態方法)(包名+類名),告知JVM使用的是哪一個數據庫的驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//設置連接地址,時間格式和編碼格式(因為版本升級,所以需要加上時間格式)
String url = "jdbc:mysql://localhost:3306/j121study?serverTimezone=UTC&characterEncoding=UTF-8";
String user = "root";
String password = "123456";
//2.連接數據庫
conn=DriverManager.getConnection(url,user,password);
/**
* DriverManager類用來管理數據庫中的所有驅動程序。
* 是JDBC的管理層,作用于用戶和驅動程序之間,跟蹤可用的驅動程序,并在數據庫的驅動程序之間建立連接。
* 此外,DriverManager類中的方法都是靜態方法,所以在程序中無須對它進行實例化,直接通過類名就可以調用。
* DriverManager類的常用方法有getConnection(String url,String user,String password)方法
*/
//3.獲取語句執行平臺,并創建數據庫查詢
//Statement s = conn.createStatement();Statement接口創建之后,可以執行SQL語句,完成對數據庫的增刪改查。然而查詢略顯復雜。
//與 Statement一樣,PreparedStatement也是用來執行sql語句的,與創建Statement不同的是,需要根據sql語句創建PreparedStatement。
//除此之外,還能夠通過設置參數,指定相應的值,而不是Statement那樣使用字符串拼接。
ps = conn.prepareStatement("select * from school where school_name=?");
//該語句為參數保留一個問號作為占位符
ps.setString(1, "渡課教育躍龍路主校區");
//4.執行查詢語句,并獲取查詢結果
ResultSet rs = ps.executeQuery();
while(rs.next()) {
System.out.println(rs.getInt("id"));
System.out.println(rs.getString("address"));
}//結果集同樣可以釋放,以便于下次更方便的填充數據
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if(null != conn) {
try {
conn.close();conn = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != ps) {
try {
ps.close(); ps = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
~~~
PrepareStatement中方法
~~~
ResultSet executeQuery(String sql) 執行sql中select語句
int executeUpdate(String sql) 執行sql中的update,insert,delete語句,返回影響條數
~~~
把資源關閉放到finally語句中,最后要把每個關閉了的資源對象設置一個null值。因為這些資源是很寶貴的就算被關閉了也不會馬上被回收,設置為null可以讓垃圾回收機制更早的回收
## JDBC工具類的抽取
在使用JDBC時,我們發現會有很多代碼是重復的,那么其實我們可以將這些代碼抽取到工具類中。而且也可以和屬性文件搭配使用!避免修改源代碼。
jdbc.properties:
~~~
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/j123study?serverTimezone=UTC&characterEncoding=UTF-8
username=root
password=abc
~~~
JDBC工具類:
~~~
public class JDBCUtils {
private static final String driverClass;
private static final String url;
private static final String username;
private static final String password;
static{
// 加載屬性文件解析對象:
Properties props = new Properties();
// 如何獲得屬性文件的輸入流?
// 通常情況下使用類的加載器的方式進行獲取:類加載器加載JDBCUtils類,并去使用文件資源作為輸入流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
props.load(is);
} catch (IOException e) {
e.printStackTrace();
}
driverClass = props.getProperty("driverClass");
url = props.getProperty("url");
username = props.getProperty("username");
password = props.getProperty("password");
}
/**
* 注冊驅動的方法
* @throws ClassNotFoundException
*/
public static void loadDriver() throws ClassNotFoundException{
Class.forName(driverClass);
}
/**
* 獲得連接的方法:
* @throws SQLException
*/
public static Connection getConnection() throws Exception{
loadDriver();
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
/**
* 資源釋放
*/
public static void release(Statement stmt,Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs,Statement stmt,Connection conn){
if(rs!= null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
~~~
## SQL注入的漏洞
注入漏洞產生的原因:
**在輸入的時候輸入了sql語句的關鍵字**
~~~
public class JDBCDemo4 {
/**
* 測試SQL注入漏洞的方法
*/
public void demo1(){
boolean flag = JDBCDemo4.login2("aaa' or '1=1", "1fsdsdfsdf");
if(flag == true){
System.out.println("登錄成功!");
}else{
System.out.println("登錄失敗!");
}
}
/**
* 避免SQL注入漏洞的方法
*/
public static boolean login2(String username,String password){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
boolean flag = false;
try{
// 獲得連接:
conn = JDBCUtils.getConnection();
// 編寫SQL:
String sql = "select * from user where username = ? and password = ?";
// 預處理SQL:
pstmt = conn.prepareStatement(sql);
// 設置參數:
pstmt.setString(1, username);
pstmt.setString(2, password);
// 執行SQL:
rs = pstmt.executeQuery();
// 判斷結果街
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn);
}
return flag;
}
/**
* 產生SQL注入漏洞的方法
* @param username
* @param password
* @return
*/
public static boolean login(String username,String password){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
boolean flag = false;
try{
conn = JDBCUtils.getConnection();
// 創建執行SQL語句的對象:
stmt = conn.createStatement();
// 編寫SQL:
String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
// 執行SQL:
rs = stmt.executeQuery(sql);
// 判斷結果集中是否有數據。
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, stmt, conn);
}
return flag;
}
}
~~~
## 數據庫連接池
連接池:
連接池是創建和管理一個連接的緩沖池的技術,這些連接準備好被任何需要它們的線程使用。
在實際應用開發中,特別是在WEB應用系統中,如果JSP、Servlet或EJB使用JDBC直接訪問數據庫中的數據,每一次數據訪問請求都必須經歷建立數據庫連接、打開數據庫、存取數據和關閉數據庫連接等步驟,而連接并打開數據庫是一件既消耗資源又費時的工作,如果頻繁發生這種數據庫操作,系統的性能必然會急劇下降,甚至會導致系統崩潰。數據庫連接池技術是解決這個問題最常用的方法,在許多應用程序服務器中,基本都提供了這項技術,無需自己編程,但是,深入了解這項技術是非常必要的。
數據庫連接池技術的思想非常簡單,將數據庫連接作為對象存儲在一個對象中,一旦數據庫連接建立后,不同的數據庫訪問請求就可以共享這些連接,這樣,通過復用這些已經建立的數據庫連接,可以克服上述缺點,極大地節省系統資源和時間。


用數據庫連接池時,不要再臨時創建到數據庫的連接,而是直接去使用某個容器或者地址中創建好的connection
>[info]常用的有 : DPCP、C3P0
C3P0 jar包下載地址:[https://sourceforge.net/projects/c3p0/?source=navbar](https://sourceforge.net/projects/c3p0/?source=navbar)
示例:
c3p0配置文件-放在src目錄下面
~~~
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/j123study?serverTimezone=UTC&characterEncoding=UTF-8</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
</c3p0-config>
~~~
~~~
public class JDBCUtils2 {
private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();
/**
* 獲得連接的方法:
* @throws SQLException
*/
public static Connection getConnection() throws Exception{
Connection conn = dataSource.getConnection();
return conn;
}
/**
* 資源釋放
*/
public static void release(Statement stmt,Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs,Statement stmt,Connection conn){
if(rs!= null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
~~~
~~~
public class DataSourceDemo1 {
/**
* 使用配置文件的方式
*/
public void demo2(){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
/*// 獲得連接:
ComboPooledDataSource dataSource = new ComboPooledDataSource();*/
// 獲得連接:
// conn = dataSource.getConnection();
conn = JDBCUtils2.getConnection();
// 編寫Sql:
String sql = "select * from user";
// 預編譯SQL:
pstmt = conn.prepareStatement(sql);
// 設置參數
// 執行SQL:
rs = pstmt.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils2.release(rs, pstmt, conn);
}
}
@Test
/**
* 手動設置連接池
*/
public void demo1(){
// 獲得連接:
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
// 創建連接池:
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 設置連接池的參數:
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/j123study?serverTimezone=UTC&characterEncoding=UTF-8");
dataSource.setUser("root");
dataSource.setPassword("abc");
dataSource.setMaxPoolSize(20);//設置最大連接數
dataSource.setInitialPoolSize(3);//設置初始化時連接池中有多少連接
// 獲得連接:從連接池獲得連接
conn = dataSource.getConnection();
// 編寫Sql:
String sql = "select * from user";
// 預編譯SQL:
pstmt = conn.prepareStatement(sql);
// 設置參數
// 執行SQL:
rs = pstmt.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn);
}
}
}
~~~