JAVA EE-JDBC
塑成一個雕像,把生命賦給這個雕像,這是美麗的;創造一個有智慧的人,把真理灌輸給他,這就更美麗。 —— 雨果
什麼是JDBC?
- JDBC 就是由 java提供的一套訪問數據庫的統一api. 使用這套api , 我們在 切換庫時 十分方便. 並且切換庫不會改變代碼.學習成本也降低了.
如何開發一個JDBC程序?
1 導包 ==> 導入廠商提供的數據庫驅動. ==> mysql-connector-java-5.0.8-bin.jar
2> 註冊驅動
3> 連接數據庫
4> 操作數據庫(執行sql)
5> 關閉資源
JDBC中的類
DriverManager 用於註冊驅動,獲得連接
Connection 代表連接 , 獲得Statement對象
Statement 運送sql語句
ResultSet 將運行結果從數據庫運回java端
一個簡單的JDBC連接實例
public class Demo {
@Test
//發送插入語句
public void fun1() throws Exception{
//1 導入驅動類庫
//2 註冊驅動
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//3 連接數據庫
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//4 操作數據庫
Statement st = conn.createStatement();
String sql = " INSERT INTO `t_user` "+
" VALUES (NULL, 'tom', 18)" ;
st.executeUpdate(sql);
//5 關閉資源
st.close();
conn.close();
}
@Test
//發送查詢語句
public void fun2() throws Exception{
//1 導入驅動類庫
//2 註冊驅動
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//3 連接數據庫
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//4 操作數據庫
Statement st = conn.createStatement();
String sql = " select * from t_user " ;
ResultSet rs = st.executeQuery(sql);
//遍歷結果集中的內容並打印
while(rs.next()){
String name = rs.getString("name");
int id = rs.getInt("id");
int age = rs.getInt("age");
System.out.println(name+"==>"+age+"==>"+id);
}
//5 關閉資源
st.close();
conn.close();
}
}
關於DriverManager的細節問題
public class Demo {
@Test
public void fun1() throws Exception{
// 註冊驅動
//註冊方式1:不推薦 => 驅動實現類中 的靜態代碼以及調用過
// DriverManager.registerDriver(driver);
//註冊方式2:推薦
Class.forName("com.mysql.jdbc.Driver");
}
@Test
public void fun2() throws Exception{
// 獲得連接
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day05", "root", "1234");
//url完整 格式: 大協議:子協議://IP地址:端口號/庫名?參數鍵=參數值
//完整: jdbc:mysql://127.0.0.1:3306/day05?useUnicode=true&characterEncoding=utf8
//簡單: jdbc:mysql:///day05?useUnicode=true&characterEncoding=utf8
}
}
1> 註冊驅動的問題.
DriverManager.registDriver(new Driver()); ==> 該種註冊方式,在將來的開發中 不要使用.
使用如下方式:
Class.forName("com.mysql.jdbc.Driver");
2>爲什麼?
在驅動類的代碼中,我們可以看到有一個靜態代碼塊。 靜態代碼塊中已經做了註冊驅動的事情。 所以我們只需要加載
驅動類,就相當於調用了 registDriver 方法。
3>使用 Class.forName有什麼好處?
* 如果調用registDriver 方法, 那麼相當於創建了兩個Driver對象,浪費資源.
* 使用forname的方式. 因爲驅動類的名稱是以字符串的形式填寫,那麼我們把該名稱放到配置文件中,每次從配置文件中讀取.
那麼切換驅動類就非常方便. 也就意味着切換數據庫方便.
關於Statement的細節問題
//Statement細節
public class Demo {
@Test
//execute 原始,增刪改查都可以 返回值 true=> 查詢有結果集 | false=> 查詢沒有結果集
//executeBatch 批量執行sql
//executeUpdate 執行增刪改
//executeQuery 執行查詢
public void fun1() throws Exception{
//1 註冊驅動
Class.forName("com.mysql.jdbc.Driver");
//2 獲得連接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 創建Statement
Statement st = conn.createStatement();
//4 書寫sql
String sql = " INSERT INTO `t_user` "+
" VALUES (NULL, 'jerry', 16)" ;
//5 執行sql
boolean result = st.execute(sql);
System.out.println(result);//false
//6關閉資源
st.close();
conn.close();
}
@Test
public void fun2() throws Exception{
//1 註冊驅動
Class.forName("com.mysql.jdbc.Driver");
//2 獲得連接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 創建Statement
Statement st = conn.createStatement();
//4 書寫sql
String sql = "select * from t_user" ;
//5 執行sql
boolean result = st.execute(sql);
if(result){
ResultSet rs = st.getResultSet();
System.out.println(rs);
}
//6關閉資源
st.close();
conn.close();
}
@Test
//executeUpdate
public void fun3() throws Exception{
//1 註冊驅動
Class.forName("com.mysql.jdbc.Driver");
//2 獲得連接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 創建Statement
Statement st = conn.createStatement();
//4 書寫sql
String sql = " INSERT INTO `t_user` "+
" VALUES (NULL, 'jack', 20)" ;
//5 執行sql
int row = st.executeUpdate(sql);
if(row!=1){
throw new RuntimeException("插入失敗!");
}
System.out.println(row);//1
//6關閉資源
st.close();
conn.close();
}
@Test
//executeQuery
public void fun4() throws Exception{
//1 註冊驅動
Class.forName("com.mysql.jdbc.Driver");
//2 獲得連接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 創建Statement
Statement st = conn.createStatement();
//4 書寫sql
String sql = "select * from t_user" ;
//5 執行sql
ResultSet rs = st.executeQuery(sql);
//遍歷rs
System.out.println(rs);
//6關閉資源
st.close();
conn.close();
}
}
6.Statement 對象
該對象可以理解爲一個 向數據庫運送sql語句的 "小車";
方法:
void addBatch(String sql) 向車上添加語句. (用於批量執行sql語句); insert update delete
int[] executeBatch() 將車上的語句 運送給數據庫執行. 返回值存放每個語句執行後影響的行數. 因爲是多個語句,所以用數組裝.
void clearBatch() 清除車上的語句.
----以上3個方法是批量執行sql相關的(下午最後一節課演示)----------------------
boolean execute(String sql) 執行一個sql語句. 如果該語句返回結果集 返回值爲true(select). 如果該語句不返回結果集 返回false(insert update delete);
ResultSet executeQuery(String sql) 執行一個有結果集的查詢. 會將結果集包裝到resultset對象中.(select)
int executeUpdate(String sql) 執行一個沒有結果集的語句. 會將語句影響的行數返回.(insert update delete)
結論:
執行查詢語句時使用: executeQuery方法
執行增刪改等語句時使用: executeUpdate方法
關於ResultSet的細節
Demo 1
//ResultSet細節
//功能: 封裝結果集數據
//操作: 如何獲得(取出)結果
//結論:
//1. next方法,向下移動並判斷是否有內容
//2. getXXX方法,根據列索引或列名獲得列的內容
public class Demo {
@Test
public void fun1() throws Exception{
//1 註冊驅動
Class.forName("com.mysql.jdbc.Driver");
//2 獲得連接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 創建Statement
Statement st = conn.createStatement();
//4 書寫sql
String sql = "select * from t_user" ;
//5 執行sql
ResultSet rs = st.executeQuery(sql);
//向下移動一行,並判斷
while(rs.next()){
//有數據
//取數據:getXXX
int id = rs.getInt(1);//獲得第一列的值
//int id rs.getInt("id");// 獲得id列的值
String name = rs.getString(2);//獲得第二列的值
int age = rs.getInt(3);//獲得第三列的值
System.out.println(id+"==>"+name+"==>"+age);
}
//6關閉資源
st.close();
conn.close();
}
/* 數據庫類型 java類型
int int
double double
decimal double
char String
varchar String
datetime Date
timestamp Timestamp/Date
*/
}
Demo 2
//ResultSet細節
// 1.結果集的滾動 => 移動結果集的指針就是滾動
// 2.結果集反向修改數據庫
public class Demo2 {
@Test
public void fun1() throws Exception{
//1 註冊驅動
Class.forName("com.mysql.jdbc.Driver");
//2 獲得連接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 創建Statement
Statement st = conn.createStatement();
//4 書寫sql
String sql = "select * from t_user" ;
//5 執行sql
ResultSet rs = st.executeQuery(sql);
//倒着遍歷
//1> 光標移動到最後一行之後
rs.afterLast();
//2> 遍歷=>
while(rs.previous()){//向上移動光標,並判斷是否有數據
int id = rs.getInt("id");// 獲得id列的值
String name = rs.getString("name");//獲得第二列的值
int age = rs.getInt("age");//獲得第三列的值
System.out.println(id+"==>"+name+"==>"+age);
}
//6關閉資源
st.close();
conn.close();
}
/* 數據庫類型 java類型
int int
double double
decimal double
char String
varchar String
datetime Date
timestamp Timestamp/Date
*/
}
Demo 3
//ResultSet細節
// 2.結果集反向修改數據庫
public class Demo3 {
@Test
public void fun1() throws Exception{
//1 註冊驅動
Class.forName("com.mysql.jdbc.Driver");
//2 獲得連接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 創建Statement
Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
//4 書寫sql
String sql = "select * from t_user" ;
//5 執行sql
ResultSet rs = st.executeQuery(sql);
//使用結果集 反向修改數據庫
rs.next();//將光標移動到第一行
rs.updateString("name", "湯姆");// 修改第一行name列的值爲中文湯姆
rs.updateRow();// 確認修改
//6關閉資源
st.close();
conn.close();
}
}
參數問題:
參數1 resultSetType - 結果集類型
ResultSet.TYPE_FORWARD_ONLY、 不支持結果集滾動,只能向前.
ResultSet.TYPE_SCROLL_INSENSITIVE 支持滾動, 遲鈍,不敏感的結果集.
ResultSet.TYPE_SCROLL_SENSITIVE 支持滾動, 敏感的結果集.
參數2 resultSetConcurrency - 結果是否支持修改類型
ResultSet.CONCUR_READ_ONLY 不支持修改
ResultSet.CONCUR_UPDATABLE 支持修改
JDBC中關於釋放資源的一些問題
9.釋放資源
1> 從小到大釋放. resultSet < Statement < Connection
2> 3個都需要釋放.
3>釋放時調用close方法即可. 如果其中一個對象的關閉 出現了異常. 也要保證其他的對象關閉方法被調用.
resultSet.close();
Statement.close();
Connection.close();
以上代碼是無法保證一定都能執行的.
try{
resultSet.close();
}catch(Exception e){
}finally{
try{
Statement.close();
}catch(Exception e){
}
finally{
try{
Connection.close();
}catch(Exception e){
}
}
}
自己封裝的一JDBC工具類
import java.io.FileInputStream;
import java.io.InputStream;
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 String driver;
private static String url;
private static String user;
private static String password;
static{
try {
//0讀取配置文件
Properties prop = new Properties();
InputStream is = new FileInputStream("src/db.properties");
prop.load(is);
is.close();
driver = prop.getProperty("driver");
url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("password");
//1 註冊驅動
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
}
}
//1 獲得連接
public static Connection getConnection(){
Connection conn = null;
try {
//2 獲得連接
conn = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("創建連接失敗!");
}
return conn;
}
//2 釋放資源
//1> 參數可能爲空
//2> 調用close方法要拋出異常,確保即使出現異常也能繼續關閉
//3>關閉順序,需要從小到大
public static void close(Connection conn , Statement st , ResultSet rs){
try {
if(rs!=null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
if(st!=null){
st.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
if(conn!=null){
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
System.out.println(getConnection());
}
}
SQL的注入問題
- 早年登錄邏輯,就是把用戶在表單中輸入的用戶名和密碼 帶入如下sql語句. 如果查詢出結果,那麼 認爲登錄成功.
SELECT * FROM USER WHERE NAME='' AND PASSWORD='xxx';
- 如果將用戶名以及密碼數據寫成如下的形式:
SELECT * FROM USER WHERE NAME='xxx' OR 1=1 -- ' and password='xxx';
- 發現sql語句失去了判斷效果,條件部分成爲了恆等式.
- 導致網站可以被非法登錄, 以上問題就是sql注入的問題.
那麼我們如何解決SQL注入問題?
- 解決辦法:在運送sql時,我們使用的是Statement對象. 如果換成prepareStatement對象,那麼就不會出現該問題.
- sql語句不要再直接拼寫.而要採用預編譯的方式來做.
- 完成如上兩步.即可解決問題.
*爲什麼使用PrepareStatement對象能解決問題?
sql的執行需要編譯. 注入問題之所以出現,是因爲用戶填寫 sql語句 參與了編譯. 使用PrepareStatement對象
在執行sql語句時,會分爲兩步. 第一步將sql語句 "運送" 到mysql上編譯. 再回到 java端 拿到參數 運送到mysql端.
用戶填寫的 sql語句,就不會參與編譯. 只會當做參數來看. 避免了sql注入問題;
PrepareStatement 在執行 母句相同, 參數不同的 批量執行時. 因爲只會編譯一次.節省了大量編譯時間.效率會高.
演示向mysql中存放大文本文件
public class Demo {
@Test
//演示向mysql中存放大文本數據
//存儲大文本必須使用PrepareStatement對象
public void fun1() throws Exception{
//1 獲得連接
Connection conn = JDBCUtils.getConnection();
//2 書寫sql
String sql = "insert into mytext values(null,?)";
//3 創建PrepareStatement
PreparedStatement ps = conn.prepareStatement(sql);
//4 設置參數
//參數1:參數的索引
//參數2:需要保存的文本的流
//參數3:文件長度
File f = new File("src/text.txt");
FileReader reader = new FileReader(f);
ps.setCharacterStream(1, reader, (int)f.length());
//5 執行sql
int result = ps.executeUpdate();
System.out.println(result);
//6關閉資源
JDBCUtils.close(conn, ps, null);
}
}
通過JDBC存儲二進制文件
public class Demo {
@Test
//演示向mysql中存放圖片
//存儲圖片必須使用PrepareStatement對象
public void fun1() throws Exception{
//1 獲得連接
Connection conn = JDBCUtils.getConnection();
//2 書寫sql
String sql = "insert into myblob values(null,?)";
//3 創建PrepareStatement
PreparedStatement ps = conn.prepareStatement(sql);
//4 設置參數
//參數1:參數的索引
//參數2:需要保存的圖片的流
//參數3:圖片文件長度
File f = new File("src/wg.PNG");
InputStream is = new FileInputStream(f);
ps.setBinaryStream(1, is, (int)f.length());
//5 執行sql
int result = ps.executeUpdate();
System.out.println(result);
//6關閉資源
JDBCUtils.close(conn, ps, null);
}
}
批量執行MYSQL語句
public class Demo {
@Test
//1 使用Statement對象批量執行sql
public void fun1() throws Exception{
//1 獲得連接
Connection conn = JDBCUtils.getConnection();
//2 獲得Statement
Statement st = conn.createStatement();
//3 添加多條sql語句到st中
st.addBatch("create table t_stu ( id int primary key auto_increment , name varchar(20) )");
st.addBatch("insert into t_stu values(null,'tom')");
st.addBatch("insert into t_stu values(null,'jerry')");
st.addBatch("insert into t_stu values(null,'jack')");
st.addBatch("insert into t_stu values(null,'rose')");
//4 執行sql
int[] results = st.executeBatch();
System.out.println(Arrays.toString(results));
//5關閉資源
JDBCUtils.close(conn, st, null);
}
@Test
//2 使用PrepareStatement對象批量執行sql
public void fun2() throws Exception{
//1 獲得連接
Connection conn = JDBCUtils.getConnection();
//2 書寫sql語句
String sql = "insert into t_stu values(null,?)";
//3 創建PrepareStatement
PreparedStatement ps = conn.prepareStatement(sql);
//4 循環.添加參數
for(int i=0;i<100;i++){
ps.setString(1, "用戶"+i);
ps.addBatch();
}
//5 批量執行
int[] results =ps.executeBatch();
System.out.println(Arrays.toString(results));
//5關閉資源
JDBCUtils.close(conn, ps, null);
}
}