文章目錄
1 什麼是JDBC
JDBC(Java DataBase Connectivity)就是Java數據庫連接,就是用Java語言來操作數據庫。JDBC是用Java語言向數據庫發送SQL語句。
1、來連接數據庫吖
(1)導入jar包:驅動。
(2)加載驅動類:Class.forName("classname");
(3)給出URL、username、password。
(4)使用DriverManager類來得到Connection對象。
來看示例吖:
package org.lks.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
public class MySQLDemo {
private static final String MYSQL_DATABASE_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String MYSQL_DATABASE_URL = "jdbc:mysql://localhost:3306/test_simple_practice?useSSL=false&serverTimezone=UTC";
private static final String MYSQL_DATABASE_USERNAME = "simple";
private static final String MYSQL_DATABASE_PASSWORD = "123";
public static void main(String[] args ) throws Exception {
Connection conn = null;
Class.forName(MYSQL_DATABASE_DRIVER); //加載驅動類(註冊驅動)
conn = DriverManager.getConnection(MYSQL_DATABASE_URL, MYSQL_DATABASE_USERNAME, MYSQL_DATABASE_PASSWORD); //得到連接對象
System.out.println(conn);
}
}
2 JDBC原理
JDBC是一套訪問數據庫的規範,就是一組接口,並提供連接數據庫的協議標準,然後由各個數據庫廠商遵循這些規範提供一套訪問自己公司的數據庫服務器的API,這樣的API稱之爲驅動。
3 JDBC完成增刪改查
JDBC協議格式:jdbc:廠商的名稱:子協議
,子協議由廠商自己來規定。對MySQL而言,它的子協議結構://主機:端口號/數據庫名稱
。
下面開始對數據庫做增刪改。
1、通過Connection對象創建Statement:Statement createStatement() throws SQLException
(1)Statement語句的發送器,它的功能就是向數據庫發送SQL語句
2、調用它的int executeUpdate(String sql) throws SQLException
,它可以發送DML、DDL。
//通過Connection獲取Statement對象
Statement stat = conn.createStatement();
//定義要執行的SQL語句
String sql = "INSERT INTO temp(sno,sname) VALUES(1001,'LK'),(1002,'HY')";
//通過executeUpdate(String)函數執行SQL語句,並返回所影響的行數
int row = stat.executeUpdate(sql);
System.out.println(row);
下面繼續對數據庫進行數據查詢
1、通過Connection對象創建Statement
2、調用它的ResultSet executeQuery(String sql) throws SQLException
,它可以發送DQL。
3、解析ResultSet。
//通過Connection獲取Statement對象
Statement stat = conn.createStatement();
//定義要執行的SQL語句
String sql = "SELECT * FROM temp";
//通過executeQuery(String)函數執行SQL語句,並返回所影響的行數
ResultSet rs = stat.executeQuery(sql);
while(rs.next()) { //判斷是否有數據
int sno = rs.getInt("sno"); //獲取屬性列名稱爲sno的內容
String sname = rs.getString(2); //獲取第二列的數據
System.out.println(sno + " " + sname); //輸出內容
}
最後不要忘了關閉資源,採用倒關的方式,示例如下:
rs.close(); //關閉ResultSet
stat.close(); //關閉Statement
conn.close(); //關閉數據庫連接
4 JDBC之代碼規範化
package org.lks.jdbcutil;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class MySQLUtils {
private static final String MYSQL_DATABASE_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String MYSQL_DATABASE_URL = "jdbc:mysql://localhost:3306/test_simple_practice?useSSL=false&serverTimezone=UTC";
private static final String MYSQL_DATABASE_USERNAME = "simple";
private static final String MYSQL_DATABASE_PASSWORD = "123";
private MySQLUtils() {}
private static Connection getConnection() {
Connection conn = null;
try {
Class.forName(MYSQL_DATABASE_DRIVER);
conn = DriverManager.getConnection(MYSQL_DATABASE_URL);
}catch(SQLException e) {
e.printStackTrace();
}catch(ClassNotFoundException e) {
e.printStackTrace();
}catch(Exception e) {
e.printStackTrace();
}
return conn;
}
public static int executeUpdate(String sql) {
int result = 0;
Connection conn = null;
Statement stat = null;
if(sql == null | "".equals(sql)) {
return 0;
}
try{
conn = MySQLUtils.getConnection();
stat = conn.createStatement();
result = stat.executeUpdate(sql);
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(stat != null) {
stat.close();
}
if( conn != null) {
conn.close();
}
}catch(Exception e) {
e.printStackTrace();
}
}
return result;
}
public static ResultSet executeQuery(String sql) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
if(sql == null | "".equals(sql)) {
return rs;
}
try{
conn = MySQLUtils.getConnection();
stat = conn.createStatement();
rs = stat.executeQuery(sql);
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(stat != null) {
stat.close();
}
if( conn != null) {
conn.close();
}
}catch(Exception e) {
e.printStackTrace();
}
}
return rs;
}
}
5 結果集光標與元數據
1、DriverManager類
使用最多的就是Class.forName()
和DriverManager.getConnection()
,此代碼可能出現的兩種異常如下:
(1)ClassNotFoundException
:出現這個異常有兩種可能
|——未給出mysql的jar包;
|——類名錯誤,查看類名是否是com.mysql.jdbc.Driver
,數據庫版本爲8.0以上版本的是com.mysql.cj.jdbc.Driver
(2)SQLException
:查看數據庫URL是否出錯,在考慮是否是用戶名和密碼錯誤。
2、Connection類
(1)方法定義:Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
,這兩個參數是用來確定創建的Statement能生成什麼樣的結果集。
3、Statement類
(1)int executeUpdate(String sql) throws SQLException
:執行更新操作,即執行INSERT、UPDATE、DELETE語句,其實這個方法也可以執行CREATE TABLE、ALTER TABLE以及DROP TABLE等語句,但我們很少會使用JDBC來執行這些語句。
(2)ResultSet executeQuery(String sql) throws SQLException
:執行查詢操作,執行查詢操作會返回ResultSet,即結果集。
(3)boolean execute(String sql) throws SQLException
:可以執行所有的更新和查詢操作,該方法返回的是boolean類型,表示SQL語句是否有結果。如果執行的是更新操作,那麼可以調用int getUpdateCount() throws SQLException
來獲取更新語句影響的行數。如果執行的是查詢操作,那麼可以調用ResultSet getResultSet() throws SQLException
獲取查詢結果集。
4、獲取結果集元數據
(1)得到元數據:ResultSetMetaData getMetaData() throws SQLException
,返回值爲ResultSetMetaData;
(2)獲取結果集列數:int getColumnCount() throws SQLException
;
(3)獲取指定列的列名:String getColumnName(int column) throws SQLException
Connection conn = MySQLUtils.getConnection();
String sql = "SELECT * FROM teach";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
//獲取表記錄的列數
int col = rs.getMetaData().getColumnCount();
System.out.println(col);
conn.close(); //關閉數據庫
6 結果集特性(是否可滾動)
1、ResultSet類
下一行:默認只能使用它,其它方法存在,但不能使用,默認的結果集不可滾動。
ResultSet表示結果集,它是一個二維的表格。ResultSet內部維護一個行光標(遊標),ResultSet提供了一系列的方法來移動遊標:
(1)void beforeFirst() throws SQLException
:把光標放在第一行前面,即光標默認位置;
(2)void afterLast() throws SQLException
:把光標放在最後一行後面;
(3)boolean first() throws SQLException
:把光標放在第一行的位置上,返回值表示調控光標是否成功;
(4)boolean last() throws SQLException
:把光標放在最後一行的位置上;
(5)boolean isBeforeFirst() throws SQLException
:當前光標位置是否在第一行前面;
(6)boolean isAfterLast() throws SQLException
:當前光標位置是否在最後一行後面;
(7)boolean isFirst() throws SQLException
:當前光標位置是否在第一行上;
(8)boolean isLast() throws SQLException
:當前光標位置是否在最後一行上;
(9)boolean previous() throws SQLException
:把光標向前挪一行;
(10)boolean next() throws SQLException
:把光標向後挪一行;
(11)boolean relative(int rows) throws SQLException
:相對位移,當row爲正數時,表示向下移動row行,爲負數時表示向上移動row行;
(12)boolean absolute(int row) throws SQLException
:絕對位移,把光標移動到指定的行上;
(13)int getRow() throws SQLException
:返回當前光標所有行。
上面的方法分爲兩類,一類用來判斷遊標位置的,另一類是用來移動遊標的。如果結果集是不可滾動的,那麼只能使用next()方法來移動遊標,而其它方法都不可使用。下面演示如何獲取表中記錄數:
Connection conn = MySQLUtils.getConnection();
String sql = "SELECT * FROM teach";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
//獲取表記錄的行數
if(rs.last()) {
int rowTotal = rs.getRow();
System.out.println(rowTotal);
}
conn.close();
結果集是否可以滾動,需要再創建Statement
時指定是否可滾動。
2、結果集特性
當使用Connection
的createStatement()
方法時,已經確定了Statement
生成的結果集是什麼特性。
(1)是否可滾動
(2)是否敏感
(3)是否可更新
默認情況下,createStatement()生成的結果集不可滾動、不敏感、不可更新。
使用有兩個參數的重載方法時,Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
(1)第一個參數:
|——ResultSet.TYPE_FORWARD_ONLY:不滾動結果集;
|——ResultSet.TYPE_SCROLL_INSENSITIVE:滾動結果集,不敏感(數據集數據不會跟隨數據庫而變化)
|——ResultSet.TYPE_SCROLL_SENSITIVE:滾動結果集,敏感(不常用,各大廠商並未實現,與不敏感無異)
(2)第二個參數:
|—— ResultSet.CONCUR_READ_ONLY:結果集是隻讀的,不能通過修改結果集而反向影響數據庫;
|——ResultSet.CONCUR_UPDATABLE:結果集是可更新的,對結果集的更新可以反向影響數據庫。
MySQL默認可滾動的。
7 PreparedStatement的用法
1、PreparedStatement類
(1)是Statement接口的子接口。
(2)擴展:
|——防SQL攻擊;
|——提高代碼的可讀性,可維護性;
|——提高效率。
2、什麼是SQL攻擊
在需要用戶輸入的地方,用戶輸入的是SQL語句的片段,最終用戶輸入的SQL片段與我們DAO中寫的SQL語句合成一個完整的SQL語句。
3、演示SQL攻擊
package org.lks.jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.lks.jdbcutil.MySQLUtils;
public class MySQLDemo {
public static void main(String[] args ) throws Exception {
//模擬SQL攻擊,可以發現數據庫中並沒有此信息,返回值卻是true
//此輸入生成的SQL語句:SELECT * FROM user_info WHERE username='a' OR 'a'='a' AND password='a' OR 'a'='a';
System.out.println(login("a' OR 'a'='a","a' OR 'a'='a"));
}
public static boolean login(String username, String password) {
Connection conn = null;
// String sql1 = "CREATE TABLE IF NOT EXISTS user_info(username char(3),password char(8))";
// String sql2 = "INSERT INTO user_info(username,password) VALUES('lks','19961015'),('hy','199908')";
String sql3 = "SELECT * FROM user_info WHERE username='" + username + "' AND " + "password='" + password + "'";
System.out.println(sql3);
try {
conn = MySQLUtils.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql3);
if( rs.next() ) {
return true;
}else {
return false;
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //關閉數據庫
}
return false;
}
}
4、PreparedStatement的用法
(1)得到PreparedStatement對象
|——定義sql語句,所有的參數使用?
替代;
|——調用Connection的PreparedStatement prepareStatement(String sql) throws SQLException
;
|——調用PreparedStatement類中的setXxx()對象方法爲sql語句中的?
賦值;
|——調用PreparedStatement類中的executeUpdate()或executeQuery(),但它的方法都沒有參數。
package org.lks.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.lks.jdbcutil.MySQLUtils;
public class MySQLDemo {
public static void main(String[] args ) throws Exception {
//使用PreparedStatement來防止SQL攻擊
System.out.println(login("a' OR 'a'='a","a' OR 'a'='a")); //false
}
public static boolean login(String username, String password) {
Connection conn = null;
// String sql1 = "CREATE TABLE IF NOT EXISTS user_info(username char(3),password char(8))";
// String sql2 = "INSERT INTO user_info(username,password) VALUES('lks','19961015'),('hy','199908')";
String sql3 = "SELECT * FROM user_info WHERE username=? AND password=?";
try {
conn = MySQLUtils.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql3);
pstmt.setString(1,username);
pstmt.setString(2,password);
ResultSet rs = pstmt.executeQuery();
if( rs.next() ) {
return true;
}else {
return false;
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
if(conn != null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //關閉數據庫
}
return false;
}
}
8 預處理的原理
1、服務器的工作
(1)校驗sql語句的語法。
(2)編譯:一個與函數相似的東西。
(3)執行:調用函數。
2、PreparedStatement
(1)前提:連接的數據庫必須支持預處理(幾乎沒有不支持的)。
(2)每個pstmt都與一個sql模板綁定在一起,先把sql模板給數據庫,數據庫先進行校驗,在進行編譯。執行時只是把參數傳遞過去而已。
(3)若二次執行時,就不用再次校驗語法,也不用再此編譯,直接執行。
9 mysql的預編譯功能默認是關
1、useServerPrepStmts
參數
默認使用PreparedStatement是不能執行預編譯的,這需要在url中給出useServerPrepStmts=true
參數(MySQL Server 4.1之前的版本是不支持預編譯的,而Connector/J在5.0.5以後的版本,默認是沒有開啓預編譯功能的)。這樣才能保證mysql驅動會把SQL語句發送給服務器進行預編譯,然後在執行executeQuery時只是把參數發送給服務器。
2、cachePrepStmts
參數
當使用不同的PreparedStatement對象來執行相同的SQL語句時,還是會出現編譯兩次的現象,這是因爲驅動沒有緩存編譯後的函數key,導致二次編譯。如果希望緩存編譯後函數的key,那麼就要設置cachePrepStmts
參數爲true。
3、打開批處理
MySQL的批處理也需要通過參數來打開:rewriteBatchedStatements=true
。
10 JdbcUtils1.0小工具
databaseconfig.properties配置文件:
databaseDriver=com.mysql.cj.jdbc.Driver
databaseURL=jdbc:mysql://localhost:3306/test_simple_practice?useSSL=true&serverTimezone=UTC
databaseUsername=simple
databasePassword=123
JDBCUtils類:
package org.lks.jdbcutil;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static Properties properties = null;
static {
InputStream input = null;
try {
input = JDBCUtils.class.getClassLoader().getResourceAsStream("databaseconfig.properties");
JDBCUtils.properties = new Properties();
JDBCUtils.properties.load(input);
Class.forName(properties.getProperty("databaseDriver"));
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(input != null) {
try {
input.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static Connection getConnection() {
Connection conn = null;
String url = properties.getProperty("databaseURL");
String user = properties.getProperty("databaseUsername");
String password = properties.getProperty("databasePassword");
try {
conn = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
}
11 面向接口編程
1、DAO模式
DAO(Data Access Object)模式就是寫一個類,把訪問數據庫的代碼封裝起來。DAO在數據庫與業務邏輯(Service)之間。
(1)實體域,即操作的對象,例如我們操作的表是user表,那麼就需要先寫一個User類;
(2)DAO模式需要先提供一個DAO接口;
(3)然後再提供一個DAO接口實現類;
(4)在編寫一個DAO工廠,Service通過工廠來獲取DAO實現
2、步驟:
DAO包:
(1)創建UserDao接口。
|——public void addUser(User user)
|——public User findUserByUsername(String username)
package org.lks.loginandregister.dao;
import org.lks.loginandregister.domain.User;
public interface IUserDao {
public User findUserByUsername(String username);
public void addUser(User user);
}
(2)創建UserDaoImpl實現UserDao接口
(3)創建DaoFactory工廠類
|——static UserDao getUserDao():
Service包:
(1)創建UserService類
|——void regist(User user)
|——User login(User user)
(2)創建UserException類
Domain包:
(1)創建User類
12 util包下的Date與sql包下的Date
1、Java中的時間類型
(1)java.sql包下給出三個與數據庫相關的日期時間類型,分別是:
|——Date:表示日期,只有年月日,沒有時分秒,會丟失時間。
|——Time:表示時間,只有時分秒,沒有年月日,會丟失日期。
|——Timestamp:表示時間戳,有年月日時分秒,以及毫秒。
以上三個類都是java.util.Date類的子類。
2、數據庫類型與java中類型的對應關係
DATE→java.sql.Date
TIME→java.sql.Time
TIMESTAMP→java.sql.Timestamp
(1)領域對象(domain)中的所有屬性不能出現java.sql包下的東西,即不能使用java.sql.Date。
(2)ResultSet.getDate()返回的是java.sql.Date。
(3)PreparedStatement.setDate(int,Date)中的Date是java.sql.Date。
3、時間類型的轉換
(1)java.util.Date→java.sql.Date、java.sql.Time、java.sql.Timestamp
|——把util的Date轉換爲毫秒值,long times = new Date().getTime();
|——使用毫秒值創建sql的Date、Time、Timestamp
(2)java.sql.Date、java.sql.Time、java.sql.Timestamp→java.util.Date
|——這一步不用處理了,因爲sql中的時間類型都是util中的Date的子類。
13 大數據
目標:把mp3保存到數據庫。
在my.ini中添加如下配置,否則大文件添加數據庫會失敗。
max_allowed_packet=10485760
1、什麼是大數據
所謂大數據,就是指大的字節數據,或大的字符數據。標準SQL中提供瞭如下類型來保存大數據類型:
類型 | 長度 |
---|---|
tinyblob | 28-1B(256B) |
blob | 216-1B(64KB) |
mediumblob | 224-1B(16MB) |
longblob | 232-1B(4GB) |
tinyclob | 28-1B(256B) |
clob | 216-1B(64KB) |
mediumclob | 224-1B(16MB) |
longclob | 232-1B(4GB) |
但是mysql中沒有提供tinyclob、clob、mediumclob、longclob四種類型,而是使用如下四種類型來處理文本大數據。
類型 | 長度 |
---|---|
tinytext | 28-1B(256B) |
text | 216-1B(64KB) |
mediumtext | 224-1B(16MB) |
longtext | 232-1B(4GB) |
保存音頻文件在數據庫:
public static void saveFile() {
File file = new File("D:\\Users\\victor\\Music\\P.S.我愛你 - A-Lin.mp3");
String fileName = "P.S.我愛你 - A-Lin.mp3";
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
String sql = "INSERT INTO BIG_FILE VALUE(?,?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1);
pstmt.setString(2, fileName);
pstmt.setBlob(3, new SerialBlob(new FileInputStream(file).readAllBytes()));
pstmt.executeUpdate();
}catch(Exception e) {
e.printStackTrace();
}finally {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
下圖可以看到已經成功保存:
從數據庫中讀取文件:
public static void getFile() {
Connection conn = null;
File file = new File("D:" + File.separator + "love.mp3");
try {
conn = JDBCUtils.getConnection();
String sql = "SELECT * FROM BIG_FILE";
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
if(rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
Blob blob = rs.getBlob(3);
System.out.println(id + " "+ name);
FileUtil.copy(blob.getBinaryStream(), new FileOutputStream(file));
}
}catch(Exception e) {
e.printStackTrace();
}finally {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
14 批處理
1、Statement批處理
批處理就是一批一批的處理,而不是一個一個處理。即一次向服務器發送多條SQL語句,然後由服務器一次性處理。
批處理只針對更新操作。可以多次調用Statement類的addBatch(String sql)方法,把需要執行的所有SQL語句添加到一個“批”中,然後調用Statement類的executeBatch()方法來執行當前“批”中的語句。
(1)void addBatch(String sql) throws SQLException
:添加sql語句到批處理集合中。
(2)int[] executeBatch() throws SQLException
:執行批處理的sql語句。
(3)void clearBatch() throws SQLException
:清空批處理集合。
2、PreparedStatement批處理
方法:
(1)void addBatch() throws SQLException
:添加sql語句到批處理集合中。
(2)int[] executeBatch() throws SQLException
:執行批處理的sql語句。
(3)void clearBatch() throws SQLException
:清空批處理集合。
@Test
public void fun() {
String sql = "INSERT INTO TEST_INFO VALUE(?,?,?)";
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
for(int i = 0; i < 10000; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, "stu_" + i);
pstmt.setString(3, i%2==0?"男":"女");
pstmt.addBatch();
}
long start = System.currentTimeMillis();
pstmt.executeBatch();
long end = System.currentTimeMillis();
System.out.println(end - start);
}catch(Exception e) {
e.printStackTrace();
}finally {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}