一、JDBC介紹
JDBC(Java Data Base Connectivity,java數(shù)據(jù)庫連接)寻行。簡單說:就是可以直接通過java語言,去操作數(shù)據(jù)庫。
jdbc是一套標準,它是由一些接口與類組成的廷臼。
學習中涉及到的類與接口:(它們主要在兩個包下)
java.sql
類:DriverManger
接口 Connection Statement ResultSet PreparedStatement
CallableStatement(它是用于調(diào)用存儲過程)
javax.sql
接口 DataSource
二、快速入門
1. 創(chuàng)建數(shù)據(jù)庫
create table user(
id int primary key auto_increment,
username varchar(20) unique not null,
password varchar(20) not null,
email varchar(40) not null
);
INSERT INTO USER VALUES(NULL,'tom','123','tom@163.com');
INSERT INTO USER VALUES(NULL,'fox','123','fox@163.com');
2. 下載驅(qū)動
將驅(qū)動jar包復制到lib下
3. 創(chuàng)建一個JdbcDemo類
import java.sql.*;
// import com.mysql.jdbc.Driver;
public class JdbcDemo{
public static void main(String[] args) throws SQLException, ClassNotFoundException{
// 1.注冊驅(qū)動
// DriverManager.registerDriver(new Driver());
Class.forName("com.mysql.jdbc.Driver");
// 2.獲取連接對象
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc?useSSL=false", "root", "pengxiaoye");
// 3.通過連接對象獲取操作sql語句Statement
Statement st = con.createStatement();
// 4.操作sql語句
String sql = "select * from user";
// 操作sql語句(select語句),會得到一個ResultSet結(jié)果集
ResultSet rs = st.executeQuery(sql);
// 5.遍歷結(jié)果集
// boolean flag = rs.next(); // 向下移動,返回值為true中剩,代表有下一條記錄.
// int id = rs.getInt("id");
// String username=rs.getString("username");
// System.out.println(id);
// System.out.println(username);
while(rs.next()){
int id=rs.getInt("id");
String username=rs.getString("username");
String password=rs.getString("password");
String email=rs.getString("email");
System.out.println(id+" "+username+" "+password+" "+email);
}
//6.釋放資源
rs.close();
st.close();
con.close();
}
}
JDBC操作詳解:
1.注冊驅(qū)動
DriverManager.registDriver(new Driver());
1.DriverManager類
它是java.sql包下的一個驅(qū)動管理的工具類,可以理解成是一個容器(Vector),可以裝入很多數(shù)據(jù)庫驅(qū)動
它的registDriver方法分析
public static synchronized void registerDriver(java.sql.Driver driver)
參數(shù):java.sql.Driver
我們傳遞的是 com.mysql.jdbc.Driver;
在com.mysql.jdbc.Driver類中有一段靜態(tài)代碼塊:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
上述代碼的問題:
1.在驅(qū)動管理器中會裝入兩個mysql驅(qū)動.
解決方案:使用反射
Class.forName("com.mysql.jdbc.Driver");
分析:使用反射的方式來加載驅(qū)動有什么好處?
1.只加載一次忌穿,裝入一個驅(qū)動對象.
2.降低耦合,不依賴于驅(qū)動.
2.可以通過DriverManager來獲取連接對象
Connection con=DriverManager.getConection(String url,String user,String password);
url作用:就是用于確定使用哪一個驅(qū)動.
mysql url: jdbc:mysql://localhsot:3306/數(shù)據(jù)庫名.
oralce url: jdbc:oracle:thin:@localhost:1521:sid
總結(jié):DriverManager作用:
1.注冊驅(qū)動
2.獲取連接Connection
-----------------------------------------------------------
關(guān)于url
url格式
主協(xié)議 子協(xié)議 主機 端口 數(shù)據(jù)庫
jdbc : mysql ://localhost:3306/day17
mysql的url可以簡寫:
前提:主機是localhost 端口是3306
jdbc:mysql:///day17
了解:在url后面可以帶參數(shù)
useUnicode=true&characterEncoding=UTF-8
------------------------------------------------------------------
2.Connection詳解
java.sql.Connection结啼,它代表的是一個連接對象掠剑。簡單說,就是我們程序與數(shù)據(jù)庫連接郊愧。
Connection作用:
1.可以通過Connection獲取操作sql的Statement對象朴译。
Statement createStatement() throws SQLException
示例:
Statement st=con.createStatement();
了解:
1.可以獲取執(zhí)行預處理的PreparedStatement對象.
PreparedStatement prepareStatement(String sql) throws SQLException
2.可以獲取執(zhí)行存儲過程的 CallableStatement
CallableStatement prepareCall(String sql) throws SQLException
2.操作事務
setAutoCommit(boolean flag);開啟事務
rollback();事務回滾
commit();事務提交
------------------------------------------------------------------
3.Statement詳解
java.sql.Statement用于執(zhí)行sql語句.
Statement作用:
1.執(zhí)行sql
DML:insert update delete
int executeUpdate(String sql)
利用返回值判斷非0來確定sql語句是否執(zhí)行成功。
DQL:select
ResultSet executeQuery(String sql)
可以通過execute方法來執(zhí)行任何sql語句.
execute(String sql):用于向數(shù)據(jù)庫發(fā)送任意sql語句
2.批處理操作
addBatch(String sql); 將sql語句添加到批處理
executeBatch();批量執(zhí)行
clearBatch();清空批處理.
---------------------------------------------------------------------
4.ResultSet詳解
java.sql.ResultSet它是用于封裝select語句執(zhí)行后查詢的結(jié)果属铁。
常用API
1.next()方法
public boolean next();
用于判斷是否有下一條記錄眠寿。如果有返回true,并且讓游標向下移動一行。
如果沒有返回false.
2.可以通過ResultSet提供的getXxx()方法來獲取當前游標指向的這條記錄中的列數(shù)據(jù)焦蘑。
常用:
getInt()
getString()
getDate()
getDouble()
參數(shù)有兩種
1.getInt(int columnIndex);
2.getInt(String columnName);
如果列的類型不知道盯拱,可以通過下面的方法來操作
getObject(int columnIndex);
getObject(String columnName);
----------------------------------------------------------------
5.關(guān)閉資源
Jdbc程序運行完后,切記要釋放程序在運行過程中例嘱,創(chuàng)建的那些與數(shù)據(jù)庫進行交互的對象狡逢,這些對象通常是ResultSet, Statement和Connection對象。
特別是Connection對象拼卵,它是非常稀有的資源奢浑,用完后必須馬上釋放,如果Connection不能及時腋腮、正確的關(guān)閉雀彼,極易導致系統(tǒng)宕機。Connection的使用原則是盡量晚創(chuàng)建即寡,盡量早的釋放徊哑。
為確保資源釋放代碼能運行,資源釋放代碼也一定要放在finally語句中嘿悬。
對異常進行try-catch-finally:
Connection con = null;
Statement st = null;
ResultSet rs = null;
try{
......
} catch(ClassNotFoundException e){
e.printStackTrace();
} catch(SQLException e){
e.printStackTrace();
} finally{ //必須要釋放資源
try{
if(rs!=null) rs.close();
} catch(SQLException e){
e.printStackTrace();
}
try{
if(st!=null) st.close();
} catch(SQLException e){
e.printStackTrace();
}
try{
if(con!=null) con.close();
} catch(SQLException e){
e.printStackTrace();
}
}
三实柠、JDBC api詳解(重點)
只抽取到Connection
```java
public static Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
// 2.獲取連接
Connection con = DriverManager.getConnection("jdbc:mysql:///day17","root", "abc");
return con;
}
```
JbdcUtils抽取
- 將關(guān)于連接數(shù)據(jù)庫的信息定義到配置文件中:
driverClass=com.mysql.jdbc.Driver url=jdbc:mysql:///day17 username=root password=abc #driverClass=oracle.jdbc.driver.OracleDriver #url=jdbc:oracle:thin:@localhost:1521:XE #username=system #password=system
- 讀取配置文件進行加載:
public class JdbcUtils { private static final String DRIVERCLASS; private static final String URL; private static final String USERNAME; private static final String PASSWORD; static { DRIVERCLASS = ResourceBundle.getBundle("jdbc").getString("driverClass"); // ResourceBundle可直接獲取配置文件名 URL = ResourceBundle.getBundle("jdbc").getString("url"); USERNAME = ResourceBundle.getBundle("jdbc").getString("username"); PASSWORD = ResourceBundle.getBundle("jdbc").getString("password"); } static { try { // 1.將加載驅(qū)動操作,放置在靜態(tài)代碼塊中.這樣就保證了只加載一次. Class.forName(DRIVERCLASS); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { // 2.獲取連接 Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD); return con; } // 3.關(guān)閉操作 public static void closeConnection(Connection con) throws SQLException{ if(con!=null){ con.close(); } } public static void closeStatement(Statement st) throws SQLException{ if(st!=null){ st.close(); } } public static void closeResultSet(ResultSet rs) throws SQLException{ if(rs!=null){ rs.close(); } } }
1.查詢
查詢?nèi)浚?br> 條件查詢:根據(jù)id
public void findByIdTest() {
// 定義sql
String sql = "select * from user where id=1";
Connection con = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.注冊驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 2.獲取連接
con = DriverManager.getConnection("jdbc:mysql:///day17", "root", "abc");
// 3.獲取操作sql語句對象Statement
st = con.createStatement();
// 4.執(zhí)行sql
rs = st.executeQuery(sql);
// 5.遍歷結(jié)果集
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
String email = rs.getString("email");
System.out.println(id + " " + username + " " + password + " " + email);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally { // 6.釋放資源
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (st != null)
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2.修改
public void update() {
// 將id=3的人的password修改為456
String password = "456";
String sql = "update user set password='" + password + "' where id=3";
// 1.得到Connection
Connection con = null;
Statement st = null;
try {
con = JdbcUtils.getConnection();
// 3.獲取操作sql語句對象Statement
st = con.createStatement();
// 4.執(zhí)行sql
int row = st.executeUpdate(sql);
if (row != 0) {
System.out.println("修改成功");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally { // 關(guān)閉資源
try {
if (st != null)
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.刪除
public void delete() {
// 將id=3的人刪除
String sql = "delete from user where id=2";
// 1.得到Connection
Connection con = null;
Statement st = null;
try {
con = JdbcUtils.getConnection();
// 3.獲取操作sql語句對象Statement
st = con.createStatement();
// 4.執(zhí)行sql
int row = st.executeUpdate(sql);
if (row != 0) {
System.out.println("刪除成功");
}
} catch (SQLException e) {
e.printStackTrace();
} finally { // 關(guān)閉資源
try {
JdbcUtils.closeStatement(st);
JdbcUtils.closeConnection(con);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4.添加
public void add() {
// 定義sql
String sql = "insert into user values(null,'張三','123','zs@163.com')";
Connection con = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.注冊驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 2.獲取連接
con = DriverManager.getConnection("jdbc:mysql:///day17", "root",
"abc");
// 3.獲取操作sql語句對象Statement
st = con.createStatement();
// 4.執(zhí)行sql
int row = st.executeUpdate(sql);
if (row != 0) {
System.out.println("添加成功");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally { // 6.釋放資源
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (st != null)
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
滾動結(jié)果集:
默認得到的ResultSet它只能向下遍歷(next()),對于ResultSet它可以設(shè)置成是滾動的善涨,可以向上遍歷窒盐,
或者直接定位到一個指定的物理行號.
問題:怎樣得到一個滾動結(jié)果集?
Statement st=con.createStatement();
ResultSet rs=st.executeQuery(sql);
這是一個默認結(jié)果集:只能向下執(zhí)行,并且只能迭代一次钢拧。
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery(sql);
這個就可以創(chuàng)建滾動結(jié)果集.
簡單說蟹漓,就是在創(chuàng)建Statement對象時,不使用createStatement();
而使用帶參數(shù)的createStatement(int,int)
Statement createStatement(int resultSetType,
int resultSetConcurrency)
throws SQLException
resultSetType - 結(jié)果集類型源内,它是 ResultSet.TYPE_FORWARD_ONLY葡粒、ResultSet.TYPE_SCROLL_INSENSITIVE 或 ResultSet.TYPE_SCROLL_SENSITIVE 之一
resultSetConcurrency - 并發(fā)類型;它是 ResultSet.CONCUR_READ_ONLY 或 ResultSet.CONCUR_UPDATABLE 之一
第一個參數(shù)值
ResultSet.TYPE_FORWARD_ONLY 該常量指示光標只能向前移動的 ResultSet 對象的類型。
ResultSet.TYPE_SCROLL_INSENSITIVE 該常量指示可滾動但通常不受 ResultSet 底層數(shù)據(jù)更改影響的 ResultSet 對象的類型嗽交。
ResultSet.TYPE_SCROLL_SENSITIVE 該常量指示可滾動并且通常受 ResultSet 底層數(shù)據(jù)更改影響的 ResultSet 對象的類型卿嘲。
第二個參數(shù)值
ResultSet.CONCUR_READ_ONLY 該常量指示不可以更新的 ResultSet 對象的并發(fā)模式。
ResultSet.CONCUR_UPDATABLE 該常量指示可以更新的 ResultSet 對象的并發(fā)模式夫壁。
以上五個值拾枣,可以有三種搭配方式
ResultSet.TYPE_FORWARD_ONLY ResultSet.CONCUR_READ_ONLY 默認
ResultSet.TYPE_SCROLL_INSENSITIVE ResultSet.CONCUR_READ_ONLY
ResultSet.TYPE_SCROLL_SENSITIVE ResultSet.CONCUR_UPDATABLE
常用API
next():移動到下一行
previous():移動到前一行
absolute(int row):移動到指定行
beforeFirst():移動resultSet的最前面
afterLast() :移動到resultSet的最后面
updateRow() :更新行數(shù)據(jù)
四、dao模式
DAO模式(Data Access Object 數(shù)據(jù)訪問對象):在持久層通過DAO將數(shù)據(jù)源操作完全封裝起來盒让,業(yè)務層通過操作Java對象梅肤,完成對數(shù)據(jù)源操作
業(yè)務層無需知道數(shù)據(jù)源底層實現(xiàn),通過java對象操作數(shù)據(jù)源
DAO模式結(jié)構(gòu) :
- 數(shù)據(jù)源(MySQL數(shù)據(jù)庫)
- Business Object 業(yè)務層代碼邑茄,調(diào)用DAO完成 對數(shù)據(jù)源操作
- DataAccessObject 數(shù)據(jù)訪問對象姨蝴,持久層DAO程序,封裝對數(shù)據(jù)源增刪改查肺缕,提供方法參數(shù)都是Java對象
- TransferObject 傳輸對象(值對象) 業(yè)務層通過向數(shù)據(jù)層傳遞 TO對象左医,完成對數(shù)據(jù)源的增刪改查
1.使用dao模式登錄操作
1.web層
login.jsp LoginServlet User
2.service層
UserService
3.dao層
UserDao
2.sql注入
由于沒有對用戶輸入進行充分檢查,而SQL又是拼接而成同木,在用戶輸入?yún)?shù)時炒辉,在參數(shù)中添加一些SQL關(guān)鍵字,達到改變SQL運行結(jié)果的目的泉手,也可以完成惡意攻擊。
示例:
在輸入用戶名時 tom' or '1'='1
這時就不會驗證密碼了偶器。
解決方案:
PreparedStatement(重點)
它是一個預處理的Statement斩萌,它是java.sql.Statement接口的一個子接口。
總結(jié)PreparedStatement使用:
1.在sql語句中屏轰,使用"?"占位
String sql="select * from user where username=? and password=?";
2.得到PreparedStatement對象
PreparedStatement pst=con.prepareStatement(String sql);
3.對占位符賦值
pst.setXxx(int index,Xxx obj);
例如:
setInt()
setString();
參數(shù)index,代表的是"?"的序號.注意:從1開始颊郎。
4.執(zhí)行sql
DML: pst.executeUpdate();
DQL: pst.executeQuery();
注意:這兩方法無參數(shù)
關(guān)于PreparedStatement優(yōu)點:
1.解決sql注入(具有預處理功能)
2.不需要在拼sql語句。
五霎苗、JDBC處理大數(shù)據(jù)
mysql中有大數(shù)據(jù)
blob 大二進制
TINYBLOB(255)姆吭、BLOB(64kb)、MEDIUMBLOB(16m)和LONGBLOB(4g)
text(clob) 大文本
TINYTEXT(255)唁盏、TEXT(64kb)内狸、MEDIUMTEXT(16m)和LONGTEXT(4g)
對于大數(shù)據(jù)操作,我們一般只有兩種 insert select
演示1:
大二進制操作
create table myblob(
id int primary key auto_increment,
content longblob
)
向表中插入數(shù)據(jù)
問題1:java.lang.AbstractMethodError: com.mysql.jdbc.PreparedStatement.setBinaryStream(ILjava/io/InputStream;)V
原因:mysql驅(qū)動不支持setBinaryStream(int,InputStream);
修改成
pst.setBinaryStream(1, fis,file.length());
原因:因為mysql驅(qū)動不支持setBinaryStream(int,InputStream,long);
解決:
mysql驅(qū)動支持setBinaryStream(int,InputStream,int);
注意:如果文件比較大厘擂,那么需要在my.ini文件中配置
max_allowed_packet=64M
總結(jié):
存
pst.setBinaryStream(1, fis, (int) (file.length()));
取
InputStream is = rs.getBinaryStream("content");
--------------------------------------------------------------
演示:存儲大文本
create table mytext(
id int primary key auto_increment,
content longtext
)
存儲
File file = new File("D:\\java1110\\workspace\\day17_3\\a.txt");
FileReader fr = new FileReader(file);
pst.setCharacterStream(1, fr, (int) (file.length()));
獲取:
Reader r = rs.getCharacterStream("content");
六昆淡、JDBC批處理
一次可以執(zhí)行多條sql語句.
在jdbc中可以執(zhí)行sql語句的對象有Statement,PreparedStatement,它們都提供批處理.
1.Statement執(zhí)行批處理
addBatch(String sql); 將sql語句添加到批處理
executeBatch(); 執(zhí)行批處理
clearBatch();
2.PreparedStatement執(zhí)行批處理
addBatch();
executeBatch();
clearBatch();
以上兩個對象執(zhí)行批處理區(qū)別?
1.Statement它更適合執(zhí)行不同sql的批處理刽严。它沒有提供預處理功能昂灵,性能比較低。
2.PreparedStatement它適合執(zhí)行相同sql的批處理,它提供了預處理功能眨补,性能比較高管削。
注意;mysql默認情況下,批處理中的預處理功能沒有開啟撑螺,需要開啟
1.在 url下添加參數(shù)
url=jdbc:mysql:///day17?useServerPrepStmts=true&cachePrepStmts=true&rewriteBatchedStatements=true
2.注意驅(qū)動版本
Mysql驅(qū)動要使用mysql-connector-java-5.1.13以上