第1章:JDBC概述
1.1 數(shù)據(jù)的持久化
持久化(persistence):把數(shù)據(jù)保存到可掉電式存儲(chǔ)設(shè)備中以供之后使用。大多數(shù)情況下,特別是企業(yè)級(jí)應(yīng)用,數(shù)據(jù)持久化意味著將內(nèi)存中的數(shù)據(jù)保存到硬盤上加以”固化”,而持久化的實(shí)現(xiàn)過程大多通過各種關(guān)系數(shù)據(jù)庫來完成。
-
持久化的主要應(yīng)用是將內(nèi)存中的數(shù)據(jù)存儲(chǔ)在關(guān)系型數(shù)據(jù)庫中,當(dāng)然也可以存儲(chǔ)在磁盤文件、XML數(shù)據(jù)文件中。
數(shù)據(jù)持久化
1.2 Java中的數(shù)據(jù)存儲(chǔ)技術(shù)
-
在Java中,數(shù)據(jù)庫存取技術(shù)可分為如下幾類:
JDBC直接訪問數(shù)據(jù)庫
JDO (Java Data Object )技術(shù)
第三方O/R工具,如Hibernate, Mybatis 等
JDBC是java訪問數(shù)據(jù)庫的基石,JDO、Hibernate、MyBatis等只是更好的封裝了JDBC。
1.3 JDBC介紹
- JDBC(Java Database Connectivity)是一個(gè)獨(dú)立于特定數(shù)據(jù)庫管理系統(tǒng)、通用的SQL數(shù)據(jù)庫存取和操作的公共接口(一組API),定義了用來訪問數(shù)據(jù)庫的標(biāo)準(zhǔn)Java類庫,(java.sql,javax.sql)使用這些類庫可以以一種標(biāo)準(zhǔn)的方法、方便地訪問數(shù)據(jù)庫資源。
- JDBC為訪問不同的數(shù)據(jù)庫提供了一種統(tǒng)一的途徑,為開發(fā)者屏蔽了一些細(xì)節(jié)問題。
- JDBC的目標(biāo)是使Java程序員使用JDBC可以連接任何提供了JDBC驅(qū)動(dòng)程序的數(shù)據(jù)庫系統(tǒng),這樣就使得程序員無需對(duì)特定的數(shù)據(jù)庫系統(tǒng)的特點(diǎn)有過多的了解,從而大大簡(jiǎn)化和加快了開發(fā)過程。
-
如果沒有JDBC,那么Java程序訪問數(shù)據(jù)庫時(shí)是這樣的:
沒有JDBC
有了JDBC,Java程序訪問數(shù)據(jù)庫時(shí)是這樣的:
有了JDBC
綜述
1.4 JDBC體系結(jié)構(gòu)
- JDBC接口(API)包括兩個(gè)層次:
- 面向應(yīng)用的API:Java API,抽象接口,供應(yīng)用程序開發(fā)人員使用(連接數(shù)據(jù)庫,執(zhí)行SQL語句,獲得結(jié)果)。
- 面向數(shù)據(jù)庫的API:Java Driver API,供開發(fā)商開發(fā)數(shù)據(jù)庫驅(qū)動(dòng)程序用。
JDBC是sun公司提供一套用于數(shù)據(jù)庫操作的接口,java程序員只需要面向這套接口編程即可。
不同的數(shù)據(jù)庫廠商,需要針對(duì)這套接口,提供不同實(shí)現(xiàn)。不同的實(shí)現(xiàn)的集合,即為不同數(shù)據(jù)庫的驅(qū)動(dòng)。 ————面向接口編程
1.5 JDBC程序編寫步驟
第2章:獲取數(shù)據(jù)庫連接
2.1 要素一:Driver接口實(shí)現(xiàn)類
2.1.1 Driver接口介紹
- java.sql.Driver 接口是所有 JDBC 驅(qū)動(dòng)程序需要實(shí)現(xiàn)的接口。這個(gè)接口是提供給數(shù)據(jù)庫廠商使用的,不同數(shù)據(jù)庫廠商提供不同的實(shí)現(xiàn)。
- 在程序中不需要直接去訪問實(shí)現(xiàn)了 Driver 接口的類,而是由驅(qū)動(dòng)程序管理器類(java.sql.DriverManager)去調(diào)用這些Driver實(shí)現(xiàn)。
- Oracle的驅(qū)動(dòng):oracle.jdbc.driver.OracleDriver
- mySql的驅(qū)動(dòng): com.mysql.jdbc.Driver
OJBC
JDBC
將上述jar包拷貝到Java工程的一個(gè)目錄中,習(xí)慣上新建一個(gè)lib文件夾。
拷貝jar包
在驅(qū)動(dòng)jar上右鍵-->Build Path-->Add to Build Path
構(gòu)建路徑
注意:如果是Dynamic Web Project(動(dòng)態(tài)的web項(xiàng)目)話,則是把驅(qū)動(dòng)jar放到WebContent(有的開發(fā)工具叫WebRoot)目錄中的WEB-INF目錄中的lib目錄下即可web應(yīng)用
2.2 要素二:URL
JDBC URL 用于標(biāo)識(shí)一個(gè)被注冊(cè)的驅(qū)動(dòng)程序,驅(qū)動(dòng)程序管理器通過這個(gè) URL 選擇正確的驅(qū)動(dòng)程序,從而建立到數(shù)據(jù)庫的連接。
-
JDBC URL的標(biāo)準(zhǔn)由三部分組成,各部分間用冒號(hào)分隔。
- jdbc:子協(xié)議:子名稱
- 協(xié)議:JDBC URL中的協(xié)議總是jdbc
- 子協(xié)議:子協(xié)議用于標(biāo)識(shí)一個(gè)數(shù)據(jù)庫驅(qū)動(dòng)程序
- 子名稱:一種標(biāo)識(shí)數(shù)據(jù)庫的方法。子名稱可以依不同的子協(xié)議而變化,用子名稱的目的是為了定位數(shù)據(jù)庫提供足夠的信息。包含主機(jī)名(對(duì)應(yīng)服務(wù)端的ip地址),端口號(hào),數(shù)據(jù)庫名
-
舉例:
協(xié)議
幾種常用數(shù)據(jù)庫的 JDBC URL -
MySQL的連接URL編寫方式:
- jdbc:mysql://主機(jī)名稱:mysql服務(wù)端口號(hào)/數(shù)據(jù)庫名稱?參數(shù)=值&參數(shù)=值
- jdbc:mysql://localhost:3306/aicanci
- jdbc:mysql://localhost:3306/icanci?useUnicode=true&characterEncoding=utf8(如果JDBC程序與服務(wù)器端的字符集不一致,會(huì)導(dǎo)致亂碼,那么可以通過參數(shù)指定服務(wù)器端的字符集)
- jdbc:mysql://localhost:3306/icanci?user=root&password=123456
-
Oracle 9的連接URL編寫方式:
- jdbc:oracle:thin:@主機(jī)名稱:oracle服務(wù)端口號(hào):數(shù)據(jù)庫名稱
- jdbc:oracle:thin:@localhost:1521:icanci
-
SQLServer的連接URL編寫方式:
jdbc:sqlserver://主機(jī)名稱:sqlserver服務(wù)端口號(hào):DatabaseName=數(shù)據(jù)庫名稱
2.3 要素三:用戶名和密碼
- user,password可以用“屬性名=屬性值”方式告訴數(shù)據(jù)庫
- 可以調(diào)用 DriverManager 類的 getConnection() 方法建立到數(shù)據(jù)庫的連接
2.4 數(shù)據(jù)庫連接方式舉例
2.4.1 連接方式一
@Test
public void testConnection() throws Exception{
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/test";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","ok");
try {
Connection connection = driver.connect(url, properties);
System.out.println(connection);
} catch (SQLException e) {
e.printStackTrace();
} finally {
}
}
說明:上述代碼中顯式出現(xiàn)了第三方數(shù)據(jù)庫的API
2.4.2 連接方式二
@Test
public void testConnection2() throws Exception{
//1.獲取Driver對(duì)象 通過反射獲取
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver =(Driver) clazz.newInstance();
//需要鏈接的數(shù)據(jù)庫
String url = "jdbc:mysql://localhost:3306/test";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","ok");
try {
Connection connection = driver.connect(url, properties);
System.out.println(connection);
} catch (SQLException e) {
e.printStackTrace();
} finally {
}
}
說明:相較于方式一,這里使用反射實(shí)例化Driver,不在代碼中體現(xiàn)第三方數(shù)據(jù)庫的API。體現(xiàn)了面向接口編程思想。
2.4.3 連接方式三
@Test
public void testConnection3() throws Exception{
//0.獲取Driver的實(shí)現(xiàn)類對(duì)象
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//1.注冊(cè)驅(qū)動(dòng)
DriverManager.registerDriver(driver);
//2.獲取鏈接
String url = "jdbc:mysql://localhost:3306/test";
String username ="root";
String password = "ok";
Connection connection = DriverManager.getConnection(url, username, password);
System.out.println(connection);
connection.close();
}
2.4.4 連接方式四
@Test
public void testConnection4() throws Exception{
//1.加載驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Driver");
//2.獲取鏈接
String url = "jdbc:mysql://localhost:3306/test";
String username ="root";
String password = "ok";
Connection connection = DriverManager.getConnection(url, username, password);
System.out.println(connection);
connection.close();
}
2.4.5 連接方式五
@Test
public void testConnection5() throws Exception {
//讀取配置文件
InputStream resourceAsStream = TestConnection1.class.getClassLoader().getResourceAsStream("db.properties");
Properties pro = new Properties();
pro.load(resourceAsStream);
String url = pro.getProperty("url");
String className = pro.getProperty("className");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
//1.加載驅(qū)動(dòng)
Class.forName(className);
//2.獲取鏈接
Connection connection = DriverManager.getConnection(url, username, password);
System.out.println(connection);
connection.close();
}
配置文件
className=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=ok
說明:使用配置文件的方式保存配置信息,在代碼中加載配置文件
使用配置文件的好處:
①實(shí)現(xiàn)了代碼和數(shù)據(jù)的分離,如果需要修改配置信息,直接在配置文件中修改,不需要深入代碼
②如果修改了配置信息,省去重新編譯的過程。
第3章:使用PreparedStatement實(shí)現(xiàn)CRUD操作
3.1 操作和訪問數(shù)據(jù)庫
數(shù)據(jù)庫連接被用于向數(shù)據(jù)庫服務(wù)器發(fā)送命令和 SQL 語句,并接受數(shù)據(jù)庫服務(wù)器返回的結(jié)果。其實(shí)一個(gè)數(shù)據(jù)庫連接就是一個(gè)Socket連接。
-
在 java.sql 包中有 3 個(gè)接口分別定義了對(duì)數(shù)據(jù)庫的調(diào)用的不同方式:
- Statement:用于執(zhí)行靜態(tài) SQL 語句并返回它所生成結(jié)果的對(duì)象。
- PrepatedStatement:SQL 語句被預(yù)編譯并存儲(chǔ)在此對(duì)象中,可以使用此對(duì)象多次高效地執(zhí)行該語句。
-
CallableStatement:用于執(zhí)行 SQL 存儲(chǔ)過程
存儲(chǔ)過程
3.2 使用Statement操作數(shù)據(jù)表的弊端
- 通過調(diào)用 Connection 對(duì)象的 createStatement() 方法創(chuàng)建該對(duì)象。該對(duì)象用于執(zhí)行靜態(tài)的 SQL 語句,并且返回執(zhí)行結(jié)果。
- Statement 接口中定義了下列方法用于執(zhí)行 SQL 語句:
int excuteUpdate(String sql):執(zhí)行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql):執(zhí)行查詢操作SELECT
-
但是使用Statement操作數(shù)據(jù)表存在弊端:
- 問題一:存在拼串操作,繁瑣
- 問題二:存在SQL注入問題
SQL 注入是利用某些系統(tǒng)沒有對(duì)用戶輸入的數(shù)據(jù)進(jìn)行充分的檢查,而在用戶輸入數(shù)據(jù)中注入非法的 SQL 語句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,從而利用系統(tǒng)的 SQL 引擎完成惡意行為的做法。
對(duì)于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(從Statement擴(kuò)展而來) 取代 Statement 就可以了。
代碼演示:
package cn.icanci.jdbc;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.Scanner;
import org.junit.Test;
public class StatementTest {
// 使用Statement的弊端:需要拼寫sql語句,并且存在SQL注入的問題
@Test
public void testLogin() {
Scanner scan = new Scanner(System.in);
System.out.print("用戶名:");
String userName = scan.nextLine();
System.out.print("密 碼:");
String password = scan.nextLine();
// SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '
// ='1' or '1' = '1';
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
+ "'";
User user = get(sql, User.class);
if (user != null) {
System.out.println("登陸成功!");
} else {
System.out.println("用戶名或密碼錯(cuò)誤!");
}
}
// 使用Statement實(shí)現(xiàn)對(duì)數(shù)據(jù)表的查詢操作
public <T> T get(String sql, Class<T> clazz) {
T t = null;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.加載配置文件
InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("db.properties");
Properties pros = new Properties();
pros.load(is);
// 2.讀取配置信息
String user = pros.getProperty("username");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("className");
// 3.加載驅(qū)動(dòng)
Class.forName(driverClass);
// 4.獲取連接
conn = DriverManager.getConnection(url, user, password);
st = conn.createStatement();
rs = st.executeQuery(sql);
// 獲取結(jié)果集的元數(shù)據(jù)
ResultSetMetaData rsmd = rs.getMetaData();
// 獲取結(jié)果集的列數(shù)
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
// //1. 獲取列的名稱
// String columnName = rsmd.getColumnName(i+1);
// 1. 獲取列的別名
String columnName = rsmd.getColumnLabel(i + 1);
// 2. 根據(jù)列名獲取對(duì)應(yīng)數(shù)據(jù)表中的數(shù)據(jù)
Object columnVal = rs.getObject(columnName);
// 3. 將數(shù)據(jù)表中得到的數(shù)據(jù),封裝進(jìn)對(duì)象
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 關(guān)閉資源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return null;
}
}
綜上:
3.3 PreparedStatement的使用
3.3.1 PreparedStatement介紹
可以通過調(diào)用 Connection 對(duì)象的 preparedStatement(String sql) 方法獲取 PreparedStatement 對(duì)象
PreparedStatement 接口是 Statement 的子接口,它表示一條預(yù)編譯過的 SQL 語句
PreparedStatement 對(duì)象所代表的 SQL 語句中的參數(shù)用問號(hào)(?)來表示,調(diào)用 PreparedStatement 對(duì)象的 setXxx() 方法來設(shè)置這些參數(shù). setXxx() 方法有兩個(gè)參數(shù),第一個(gè)參數(shù)是要設(shè)置的 SQL 語句中的參數(shù)的索引(從 1 開始),第二個(gè)是設(shè)置的 SQL 語句中的參數(shù)的值
3.3.2 PreparedStatement vs Statement
代碼的可讀性和可維護(hù)性。
-
PreparedStatement 能最大可能提高性能:
- DBServer會(huì)對(duì)預(yù)編譯語句提供性能優(yōu)化。因?yàn)轭A(yù)編譯語句有可能被重復(fù)調(diào)用,所以語句在被DBServer的編譯器編譯后的執(zhí)行代碼被緩存下來,那么下次調(diào)用時(shí)只要是相同的預(yù)編譯語句就不需要編譯,只要將參數(shù)直接傳入編譯過的語句執(zhí)行代碼中就會(huì)得到執(zhí)行。
- 在statement語句中,即使是相同操作但因?yàn)閿?shù)據(jù)內(nèi)容不一樣,所以整個(gè)語句本身不能匹配,沒有緩存語句的意義.事實(shí)是沒有數(shù)據(jù)庫會(huì)對(duì)普通語句編譯后的執(zhí)行代碼緩存。這樣每執(zhí)行一次都要對(duì)傳入的語句編譯一次。
- (語法檢查,語義檢查,翻譯成二進(jìn)制命令,緩存)
PreparedStatement 可以防止 SQL 注入
3.3.3 Java與SQL對(duì)應(yīng)數(shù)據(jù)類型轉(zhuǎn)換表
Java類型 | SQL類型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR,VARCHAR,LONGVARCHAR |
byte array | BINARY , VAR BINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
3.3.4 使用PreparedStatement實(shí)現(xiàn)增、刪、改操作
增加
//向Customer添加數(shù)據(jù)
@Test
public void testInsert() throws Exception{
//讀取配置文件
InputStream resourceAsStream = TestConnection1.class.getClassLoader().getResourceAsStream("db.properties");
Properties pro = new Properties();
pro.load(resourceAsStream);
String url = pro.getProperty("url");
String className = pro.getProperty("className");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
//1.加載驅(qū)動(dòng)
Class.forName(className);
//2.獲取鏈接
Connection connection = DriverManager.getConnection(url, username, password);
//編寫sql語句
String sql = "insert into customers(name,email,birth) values (?,?,?)";
PreparedStatement prep = connection.prepareStatement(sql);
//設(shè)置占位符參數(shù)
prep.setString(1,"哈希");
prep.setString(2,"icanci@163.com");
prep.setDate(3,new Date(System.currentTimeMillis()));
//執(zhí)行操作
prep.execute();
connection.close();
prep.close();
}
修改
JdbcUtils工具類
package cn.icanci.jdbc.utils;
import cn.icanci.jdbc.TestConnection1;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;
/**
* @Author: icanci
* @ProjectName: jdbc
* @PackageName: cn.icanci.jdbc.utils
* @Date: Created in 2020/2/16 7:41
* @ClassAction: 操作數(shù)據(jù)庫的鏈接
*/
public class JdbcUtils {
private static String url;
private static String className;
private static String username;
private static String password;
static {
InputStream resourceAsStream = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties pro = new Properties();
try {
pro.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
url = pro.getProperty("url");
className = pro.getProperty("className");
username = pro.getProperty("username");
password = pro.getProperty("password");
}
public static Connection getConnection() {
//讀取配置文件
Connection conn = null;
try {
//1.加載驅(qū)動(dòng)
Class.forName(className);
//2.獲取鏈接
conn = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
public static void closeConnection(Connection conn, PreparedStatement prep) {
try {
if (conn!=null){
conn.close();
}
if (prep!=null){
prep.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//Customer修改數(shù)據(jù)
@Test
public void testUpdate() throws Exception{
Connection conn = JdbcUtils.getConnection();
//編寫sql語句
String sql = "update customers set name=?,email=? where id = ?";
PreparedStatement prep = conn.prepareStatement(sql);
prep.setString(1,"哈希2");
prep.setString(2,"icanci@136.com");
prep.setInt(3,19);
prep.executeUpdate();
JdbcUtils.closeConnection(conn,prep);
}
刪除
//Customers刪除數(shù)據(jù)
@Test
public void testDelete() throws Exception{
Connection conn = JdbcUtils.getConnection();
//編寫sql語句
String sql = "delete from customers where id = ?";
PreparedStatement prep = conn.prepareStatement(sql);
prep.setInt(1,19);
prep.executeUpdate();
JdbcUtils.closeConnection(conn,prep);
}
編寫通用修改JDBC操作
package cn.icanci.jdbc.utils;
import cn.icanci.jdbc.TestConnection1;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;
/**
* @Author: icanci
* @ProjectName: jdbc
* @PackageName: cn.icanci.jdbc.utils
* @Date: Created in 2020/2/16 7:41
* @ClassAction: 操作數(shù)據(jù)庫的鏈接
*/
public class JdbcUtils {
private static String url;
private static String className;
private static String username;
private static String password;
static {
InputStream resourceAsStream = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties pro = new Properties();
try {
pro.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
url = pro.getProperty("url");
className = pro.getProperty("className");
username = pro.getProperty("username");
password = pro.getProperty("password");
}
public static Connection getConnection() {
//讀取配置文件
Connection conn = null;
try {
//1.加載驅(qū)動(dòng)
Class.forName(className);
//2.獲取鏈接
conn = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
public static void closeConnection(Connection conn, PreparedStatement prep) {
try {
if (conn!=null){
conn.close();
}
if (prep!=null){
prep.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//通用的增加刪除修改
public static void update(String sql, Object... objs) throws Exception {
try (Connection conn = JdbcUtils.getConnection();
PreparedStatement prep = conn.prepareStatement(sql)) {
for (int i = 0; i < objs.length; i++) {
prep.setObject(i + 1, objs[i]);
}
prep.executeUpdate();
JdbcUtils.closeConnection(conn, prep);
}
}
}
測(cè)試
@Test
public void testUpdateMethod() throws Exception{
JdbcUtils.update("delete from customers where id = ?",13);
}
說明:使用PreparedStatement實(shí)現(xiàn)的查詢操作可以替換Statement實(shí)現(xiàn)的查詢操作,解決Statement拼串和SQL注入問題。
3.4 ResultSet與ResultSetMetaData
3.4.1 ResultSet
查詢需要調(diào)用PreparedStatement 的 executeQuery() 方法,查詢結(jié)果是一個(gè)ResultSet 對(duì)象
ResultSet 對(duì)象以邏輯表格的形式封裝了執(zhí)行數(shù)據(jù)庫操作的結(jié)果集,ResultSet 接口由數(shù)據(jù)庫廠商提供實(shí)現(xiàn)
ResultSet 返回的實(shí)際上就是一張數(shù)據(jù)表。有一個(gè)指針指向數(shù)據(jù)表的第一條記錄的前面。
ResultSet 對(duì)象維護(hù)了一個(gè)指向當(dāng)前數(shù)據(jù)行的游標(biāo),初始的時(shí)候,游標(biāo)在第一行之前,可以通過 ResultSet 對(duì)象的 next() 方法移動(dòng)到下一行。調(diào)用 next()方法檢測(cè)下一行是否有效。若有效,該方法返回 true,且指針下移。相當(dāng)于Iterator對(duì)象的 hasNext() 和 next() 方法的結(jié)合體。
-
當(dāng)指針指向一行時(shí), 可以通過調(diào)用 getXxx(int index) 或 getXxx(int columnName) 獲取每一列的值。
- 例如: getInt(1), getString("name")
- 注意:Java與數(shù)據(jù)庫交互涉及到的相關(guān)Java API中的索引都從1開始。
-
ResultSet 接口的常用方法:
- boolean next()
- getString()
-
…
查詢流程
查詢實(shí)例
@Test
public void testQuery1() throws Exception{
Connection conn = JdbcUtils.getConnection();
//編寫sql語句
String sql = "select id,name,email,birth from customers where id = ?";
PreparedStatement prep = conn.prepareStatement(sql);
prep.setInt(1,1);
//執(zhí)行返回查詢的結(jié)果集
ResultSet resultSet = prep.executeQuery();
while (resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String email = resultSet.getString("email");
java.util.Date date = resultSet.getDate("birth");
Customer customer = new Customer(id,name,email,date);
System.out.println(customer);
}
JdbcUtils.closeConnection(conn,prep);
}
3.4.2 ResultSetMetaData
可用于獲取關(guān)于 ResultSet 對(duì)象中列的類型和屬性信息的對(duì)象
-
ResultSetMetaData meta = rs.getMetaData();
getColumnName(int column):獲取指定列的名稱
getColumnLabel(int column):獲取指定列的別名
getColumnCount():返回當(dāng)前 ResultSet 對(duì)象中的列數(shù)。
getColumnTypeName(int column):檢索指定列的數(shù)據(jù)庫特定的類型名稱。
getColumnDisplaySize(int column):指示指定列的最大標(biāo)準(zhǔn)寬度,以字符為單位。
isNullable(int column):指示指定列中的值是否可以為 null。
-
isAutoIncrement(int column):指示是否自動(dòng)為指定列進(jìn)行編號(hào),這樣這些列仍然是只讀的。
查詢
問題1:得到結(jié)果集后, 如何知道該結(jié)果集中有哪些列 ? 列名是什么?
需要使用一個(gè)描述 ResultSet 的對(duì)象, 即 ResultSetMetaData
問題2:關(guān)于ResultSetMetaData
- 如何獲取 ResultSetMetaData: 調(diào)用 ResultSet 的 getMetaData() 方法即可
- 獲取 ResultSet 中有多少列:調(diào)用 ResultSetMetaData 的 getColumnCount() 方法
-
獲取 ResultSet 每一列的列的別名是什么:調(diào)用 ResultSetMetaData 的getColumnLabel() 方法 推薦使用,因?yàn)間etColumnName只能獲取列名
查詢流程
針對(duì)單表的查詢操作
package cn.icanci.jdbc.study;
import cn.icanci.jdbc.dao.Customer;
import cn.icanci.jdbc.utils.JdbcUtils;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
/**
* @Author: icanci
* @ProjectName: jdbc
* @PackageName: cn.icanci.jdbc.study
* @Date: Created in 2020/2/16 8:48
* @ClassAction:
*/
public class QueryForCustomer {
public static Customer queryForCustomer(String sql, Object... objs) throws Exception {
Connection conn = JdbcUtils.getConnection();
PreparedStatement prep = conn.prepareStatement(sql);
for (int i = 0; i < objs.length; i++) {
prep.setObject(i + 1, objs[i]);
}
ResultSet resultSet = prep.executeQuery();
//獲取結(jié)果集的元數(shù)據(jù)
ResultSetMetaData metaData = resultSet.getMetaData();
//獲取結(jié)果集的列數(shù)
int columnCount = metaData.getColumnCount();
if (resultSet.next()) {
Customer customer = new Customer();
for (int i = 0; i < columnCount; i++) {
Object value = resultSet.getObject(i + 1);
//獲取每個(gè)列的名字
String columnName = metaData.getColumnName(i + 1);
//給Customer 指定 賦值
Field declaredField = Customer.class.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(customer, value);
}
return customer;
}
resultSet.close();
JdbcUtils.closeConnection(conn,prep);
return null;
}
測(cè)試
@Test
public void testQueryForCustomer()throws Exception{
Customer customer = QueryForCustomer.queryForCustomer("select id,name,email,birth from customers where id =?",2);
System.out.println(customer);
}
注意點(diǎn):domain的字段名必須和數(shù)據(jù)庫對(duì)應(yīng)表的字段名相同
3.5 資源的釋放
- 釋放ResultSet, Statement,Connection。
- 數(shù)據(jù)庫連接(Connection)是非常稀有的資源,用完后必須馬上釋放,如果Connection不能及時(shí)正確的關(guān)閉將導(dǎo)致系統(tǒng)宕機(jī)。Connection的使用原則是盡量晚創(chuàng)建,盡量早的釋放。
- 可以在finally中關(guān)閉,保證及時(shí)其他代碼出現(xiàn)異常,資源也一定能被關(guān)閉。
3.6 JDBC API小結(jié)
-
兩種思想
- 面向接口編程的思想
- ORM思想(object relational mapping)
- 一個(gè)數(shù)據(jù)表對(duì)應(yīng)一個(gè)java類
- 表中的一條記錄對(duì)應(yīng)java類的一個(gè)對(duì)象
- 表中的一個(gè)字段對(duì)應(yīng)java類的一個(gè)屬性
sql是需要結(jié)合列名和表的屬性名來寫。注意起別名。
-
兩種技術(shù)
- JDBC結(jié)果集的元數(shù)據(jù):ResultSetMetaData
- 獲取列數(shù):getColumnCount()
- 獲取列的別名:getColumnLabel()
- 通過反射,創(chuàng)建指定類的對(duì)象,獲取指定的屬性并賦值
- JDBC結(jié)果集的元數(shù)據(jù):ResultSetMetaData
JdbcTemplate模板類
package cn.icanci.jdbc.utils;
import java.util.ArrayList;
import cn.icanci.jdbc.domain.Customer;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.List;
/**
* @Author: icanci
* @ProjectName: jdbc
* @PackageName: cn.icanci.jdbc.utils
* @Date: Created in 2020/2/16 8:13
* @ClassAction:
*/
public class JdbcTemplate {
/**
* 通用的增加刪除修改
* @param sql 需要執(zhí)行的sql語句
* @param objs 需要傳入的參數(shù)
* @throws Exception
*/
public static void update(String sql, Object... objs) throws Exception {
try (Connection conn = JdbcUtils.getConnection();
PreparedStatement prep = conn.prepareStatement(sql)) {
for (int i = 0; i < objs.length; i++) {
prep.setObject(i + 1, objs[i]);
}
prep.executeUpdate();
JdbcUtils.closeConnection(conn, prep);
}
}
public static <T>T getInstance(String sql,Class<T> clazz,Object... objs)throws Exception{
Connection conn = JdbcUtils.getConnection();
PreparedStatement prep = conn.prepareStatement(sql);
for (int i = 0; i < objs.length; i++) {
prep.setObject(i + 1, objs[i]);
}
ResultSet resultSet = prep.executeQuery();
//獲取結(jié)果集的元數(shù)據(jù)
ResultSetMetaData metaData = resultSet.getMetaData();
//獲取結(jié)果集的列數(shù)
int columnCount = metaData.getColumnCount();
if (resultSet.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object value = resultSet.getObject(i + 1);
//獲取每個(gè)列的名字
String columnName = metaData.getColumnName(i + 1);
//給Customer 指定 賦值
Field declaredField = Customer.class.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(t, value);
}
return t;
}
resultSet.close();
JdbcUtils.closeConnection(conn,prep);
return null;
}
public static <T> List<T> getInstanceList(String sql, Class<T> clazz, Object... objs)throws Exception{
Connection conn = JdbcUtils.getConnection();
PreparedStatement prep = conn.prepareStatement(sql);
for (int i = 0; i < objs.length; i++) {
prep.setObject(i + 1, objs[i]);
}
ResultSet resultSet = prep.executeQuery();
//獲取結(jié)果集的元數(shù)據(jù)
ResultSetMetaData metaData = resultSet.getMetaData();
//獲取結(jié)果集的列數(shù)
int columnCount = metaData.getColumnCount();
List<T> list = new ArrayList<T> ();
while (resultSet.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object value = resultSet.getObject(i + 1);
//獲取每個(gè)列的名字
String columnName = metaData.getColumnName(i + 1);
//給Customer 指定 賦值
Field declaredField = Customer.class.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(t, value);
}
list.add(t);
}
resultSet.close();
JdbcUtils.closeConnection(conn,prep);
return list;
}
}
測(cè)試 測(cè)試通過的
@Test
public void testQueryForCustomer() throws Exception {
Customer customer = JdbcTemplate.getInstance("select id,name,email,birth from customers where id =?", Customer.class, 2);
System.out.println(customer);
System.out.println("---------------------------");
List<Customer> list = JdbcTemplate.getInstanceList("select id,name,email,birth from customers", Customer.class);
list.forEach(System.out::println);
}
第4章 操作BLOB類型字段
4.1 MySQL BLOB類型
MySQL中,BLOB是一個(gè)二進(jìn)制大型對(duì)象,是一個(gè)可以存儲(chǔ)大量數(shù)據(jù)的容器,它能容納不同大小的數(shù)據(jù)。
插入BLOB類型的數(shù)據(jù)必須使用PreparedStatement,因?yàn)锽LOB類型的數(shù)據(jù)無法使用字符串拼接寫的。
-
MySQL的四種BLOB類型(除了在存儲(chǔ)的最大信息量上不同外,他們是等同的)
四種Blob類型 實(shí)際使用中根據(jù)需要存入的數(shù)據(jù)大小定義不同的BLOB類型。
需要注意的是:如果存儲(chǔ)的文件過大,數(shù)據(jù)庫的性能會(huì)下降。
如果在指定了相關(guān)的Blob類型以后,還報(bào)錯(cuò):xxx too large,那么在mysql的安裝目錄下,找my.ini文件加上如下的配置參數(shù): max_allowed_packet=16M。同時(shí)注意:修改了my.ini文件之后,需要重新啟動(dòng)mysql服務(wù)。
4.2 向數(shù)據(jù)表中插入大數(shù)據(jù)類型
//Customer 插入數(shù)據(jù)
@Test
public void testInsert3()throws Exception{
Connection conn = JdbcUtils.getConnection();
String sql = "insert into customers(name,email,birth,photo) values (?,?,?,?)";
PreparedStatement prep = conn.prepareStatement(sql);
PreparedStatement ps = conn.prepareStatement(sql);
prep.setString(1,"啦啦啦");
prep.setString(2,"lalal@icanci,cn");
prep.setDate(3,new Date(System.currentTimeMillis()));
FileInputStream inputStream = new FileInputStream(new File("1001400.jpg"));
prep.setBlob(4,inputStream);
prep.execute();
JdbcUtils.closeConnection(conn,prep);
}
4.3 從數(shù)據(jù)表中讀取大數(shù)據(jù)類型
//向數(shù)據(jù)表customers中插入Blob類型的字段
@Test
public void testGet() throws Exception {
Connection conn = JdbcUtils.getConnection();
String sql = "select * from customers where id =?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 22);
ResultSet resultSet = ps.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String email = resultSet.getString("email");
Date date = resultSet.getDate("birth");
Blob photo = resultSet.getBlob("photo");
//把圖片保存
InputStream is = photo.getBinaryStream();
FileOutputStream out = new FileOutputStream("lalal.png");
byte[] buffer = new byte[1204];
int len;
while ((len = is.read()) != -1) {
out.write(buffer, 0, len);
}
Customer customer = new Customer(id, name, email, date);
System.out.println(customer);
is.close();
out.close();
}
JdbcUtils.closeConnection(conn, ps);
}
4.4 修改數(shù)據(jù)表中的Blob類型字段
和之前類似
Connection conn = JdbcUtils.getConnection();
String sql = "update customers set photo = ? where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
// 填充占位符
// 操作Blob類型的變量
FileInputStream fis = new FileInputStream("coffee.png");
ps.setBlob(1, fis);
ps.setInt(2, 25);
ps.execute();
fis.close();
JdbcUtils.closeConnection(conn, ps);
第5章 批量插入
5.1 批量執(zhí)行SQL語句
當(dāng)需要成批插入或者更新記錄時(shí),可以采用Java的批量更新機(jī)制,這一機(jī)制允許多條語句一次性提交給數(shù)據(jù)庫批量處理。通常情況下比單獨(dú)提交處理更有效率
JDBC的批量處理語句包括下面三個(gè)方法:
- addBatch(String):添加需要批量處理的SQL語句或是參數(shù);
- executeBatch():執(zhí)行批量處理語句;
- clearBatch():清空緩存的數(shù)據(jù)
通常我們會(huì)遇到兩種批量執(zhí)行SQL語句的情況:
- 多條SQL語句的批量處理;
- 一個(gè)SQL語句的批量傳參;
5.2 高效的批量插入
舉例:向數(shù)據(jù)表中插入20000條數(shù)據(jù)
- 數(shù)據(jù)庫中提供一個(gè)goods表。創(chuàng)建如下:
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
5.2.1 實(shí)現(xiàn)層次一:使用Statement 耗費(fèi)時(shí)間 23704 ms
@Test //耗費(fèi)時(shí)間 23704 ms
public void testInsertMany() throws Exception {
Long start = System.currentTimeMillis();
Connection conn = JdbcUtils.getConnection();
Statement st = conn.createStatement();
for (int i = 1; i <= 20000; i++) {
String sql = "insert into goods(name) values ('name')";
st.executeUpdate(sql);
}
Long end = System.currentTimeMillis();
System.out.println(end - start);
st.close();
conn.close();
}
5.2.2 實(shí)現(xiàn)層次二:使用PreparedStatement 耗費(fèi)時(shí)間 25450 ms
@Test //插入時(shí)間 25450
public void test2()throws Exception{
Long start = System.currentTimeMillis();
Connection conn = JdbcUtils.getConnection();
String sql = "insert into goods(name) values (?)";
PreparedStatement prep = conn.prepareStatement(sql);
for (int i = 1; i <= 20000; i++) {
prep.setObject(1,"name_"+i);
prep.execute();
}
Long end = System.currentTimeMillis();
System.out.println(end - start);
JdbcUtils.closeConnection(conn,prep);
}
5.2.3 實(shí)現(xiàn)層次三 耗費(fèi)時(shí)間 548 ms
- 使用 addBatch() / executeBatch() / clearBatch()
- mysql服務(wù)器默認(rèn)是關(guān)閉批處理的,我們需要通過一個(gè)參數(shù),讓mysql開啟批處理的支持。 ?rewriteBatchedStatements=true 寫在配置文件的url后面
- 使用更新的mysql 驅(qū)動(dòng):mysql-connector-java-5.1.37-bin.jar 在 5.1.37 以上
@Test //耗費(fèi)時(shí)間 548 ms 插入1000000數(shù)據(jù)條 耗時(shí) 8233 ms
public void test3()throws Exception{
/**使用 addBatch() / executeBatch() / clearBatch()
* mysql服務(wù)器默認(rèn)是關(guān)閉批處理的,我們需要通過一個(gè)參數(shù),讓mysql開啟批處理的支持。 ?rewriteBatchedStatements=true 寫在配置文件的url后面
* 使用更新的mysql 驅(qū)動(dòng):mysql-connector-java-5.1.37-bin.jar 在 5.1.37 以上
*/
Long start = System.currentTimeMillis();
Connection conn = JdbcUtils.getConnection();
String sql = "insert into goods(name) values (?)";
PreparedStatement prep = conn.prepareStatement(sql);
for (int i = 1; i <= 20000; i++) {
prep.setObject(1,"name_"+i);
//攢sql
prep.addBatch();
if (i%500 == 0){
//執(zhí)行
prep.executeBatch();
//清空
prep.clearBatch();
}
}
Long end = System.currentTimeMillis();
System.out.println(end - start);
JdbcUtils.closeConnection(conn,prep);
}
5.2.4 實(shí)現(xiàn)層次四 耗費(fèi)時(shí)間 496 ms 1000000 耗時(shí) 5726ms
- 使用Connection 的 setAutoCommit(false) / commit()
@Test //耗費(fèi)時(shí)間 496 ms 1000000 耗時(shí) 5726ms
public void test4()throws Exception{
/**
* 使用Connection 的 setAutoCommit(false) / commit()
*/
Long start = System.currentTimeMillis();
Connection conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);
String sql = "insert into goods(name) values (?)";
PreparedStatement prep = conn.prepareStatement(sql);
for (int i = 1; i <= 20000; i++) {
prep.setObject(1,"name_"+i);
//攢sql
prep.addBatch();
if (i%500 == 0){
//執(zhí)行
prep.executeBatch();
//清空
prep.clearBatch();
}
}
conn.commit();
Long end = System.currentTimeMillis();
System.out.println(end - start);
JdbcUtils.closeConnection(conn,prep);
}