數(shù)據(jù)庫(kù)基本操作
數(shù)據(jù)庫(kù)
RDBMS溉浙,relation database management system烫止,關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng)。
存放的是結(jié)構(gòu)化數(shù)據(jù)
SQL:Structured Query Language戳稽,結(jié)構(gòu)化查詢語(yǔ)言
CRUD
insert into table_name(field_name,...) values(value,...)
select id,... from table_name where id=xxx,...
update table_name set id=xxx,... where id=xxx
delete from test where ...
JDBC介紹
* JDBC是Java應(yīng)用程序和數(shù)據(jù)庫(kù)之間的通信橋梁馆蠕,是Java應(yīng)用程序訪問數(shù)據(jù)庫(kù)的通道
* JDBC代表Java數(shù)據(jù)庫(kù)連接(Java Database Connectivity),它是用于Java編程語(yǔ)言和數(shù)據(jù)庫(kù)之間的
* 數(shù)據(jù)庫(kù)無(wú)關(guān)連接的標(biāo)準(zhǔn)Java API,換句話說:JDBC是用于在Java語(yǔ)言編程中與數(shù)據(jù)庫(kù)連接的API互躬。
*
* 1.JDBC標(biāo)準(zhǔn)主要由一組接口組成播赁,其好處就是統(tǒng)一了各種數(shù)據(jù)庫(kù)訪問方式。
* 2.JDBC接口的實(shí)現(xiàn)類稱為數(shù)據(jù)庫(kù)驅(qū)動(dòng)吼渡,有各個(gè)數(shù)據(jù)庫(kù)廠商提供容为,使用JDBC必須導(dǎo)入特定的驅(qū)動(dòng)
JDBC與數(shù)據(jù)庫(kù)通信就是進(jìn)程間的通信,用的就是套接字寺酪。
對(duì)于數(shù)據(jù)庫(kù)坎背,我用的是phpstudy自帶的MySQL數(shù)據(jù)庫(kù),phpstudy的安裝十分簡(jiǎn)單房维,一直點(diǎn)擊下一步就行沼瘫,數(shù)據(jù)庫(kù)賬號(hào)密碼默認(rèn)都是root
java JDBC連接數(shù)據(jù)庫(kù)的步驟
* 1.導(dǎo)入JDBC驅(qū)動(dòng)jar
* 2.注冊(cè)JDBC驅(qū)動(dòng)
* -參數(shù):"驅(qū)動(dòng)程序類名"
* -Class.forName("驅(qū)動(dòng)程序類名")
* 3.獲得Connection對(duì)象
* -需要三個(gè)參數(shù):url,username,password
* -連接到數(shù)據(jù)庫(kù)
* 4.創(chuàng)建Statement(語(yǔ)句)對(duì)象
* -conn.getStatement()方法創(chuàng)建對(duì)象
* -用于執(zhí)行SQL語(yǔ)句
* -execute(ddl) 執(zhí)行任何SQL,常用執(zhí)行DDL咙俩,DCL
* -executeUpdate(dml) 執(zhí)行DML語(yǔ)句种柑,如:insert,update,delete
* -executeQuery(dql) 執(zhí)行DQL語(yǔ)句,如:select
* 5.處理SQL執(zhí)行結(jié)果
* -execute(ddl) 如果沒有異常則執(zhí)行成功
* -executeUpdate 返回?cái)?shù)字堕澄,表示更新"行"數(shù)量瀑志,拋出異常則失敗
* -executeQuery(dql) 返回ResultSet(結(jié)果集)對(duì)象,代表執(zhí)行結(jié)果
* 使用for遍歷處理脖阵,如果查詢失敗拋出異常皂股!
* 6.關(guān)閉數(shù)據(jù)連接,
* -conn.close();
maven導(dǎo)包命黔,引入MySQL驅(qū)動(dòng)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
package com.libai;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class Demo1 {
public static void main(String[] args) {
try {
* 注冊(cè)驅(qū)動(dòng)程序
Class.forName("com.mysql.jdbc.Driver");
* 連接到數(shù)據(jù)庫(kù)
* getConnection()方法查找并且嘗試連接到數(shù)據(jù)庫(kù)
String url = "jdbc:mysql://localhost:3306/test";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
* 輸出conn引用對(duì)象的實(shí)際類型
* 證明:驅(qū)動(dòng)程序提供了Connection接口的實(shí)現(xiàn)類
System.out.println(conn.getClass());
* 創(chuàng)建Statement語(yǔ)句對(duì)象
Statement st = conn.createStatement();
String ddl = "create table rabbit (id INT PRIMARY KEY auto_increment,name varchar(100))";
String delsql = "drop table rabbit";
boolean b = st.execute(delsql);
* 返回結(jié)果:true 表示有結(jié)果集
* false 沒有結(jié)果集
* 創(chuàng)建失敗拋出異常
* 如果沒有異常呜呐,則創(chuàng)建成功!--- 也就是false
System.out.println(b);
st.close();
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
打雍纺肌:
class com.mysql.jdbc.JDBC4Connection
false
插入數(shù)據(jù)蘑辑,使用executeUpdate()
String sql = "insert into rabbit set id=2,name='frame2'";
int n = st.executeUpdate(sql);
System.out.println(n);
打印:
1 --- 修改了一行坠宴,輸出正確
select查詢數(shù)據(jù)洋魂,使用executeQuery()
String sql = "select id,name from rabbit";
ResultSet n = st.executeQuery(sql);
* 處理結(jié)果····
* n.next():移動(dòng)結(jié)果集游標(biāo)到下一行,默認(rèn)在第一行之前
* 檢查是否有數(shù)據(jù)喜鼓,如果有返回true副砍,否則false
while(n.next()) {
* getXXX(列名):返回結(jié)果集當(dāng)前行指定列名的數(shù)據(jù)
System.out.print(n.getInt("id"));
System.out.print(n.getString("name"));
}
Properties 是Java專門讀取配置文件的API
db.properties文件,建立在與main同級(jí)的resources下
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test
jdbc.username=root
jdbc.password=root
package com.libai;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
* Properties 其目的就是為了讀取 .properties文件庄岖,本身實(shí)現(xiàn)了Map接口
* 內(nèi)部是散列表豁翎,限定了key和value都是String類型。
* 方法:load(流)將文件讀取為散列表
* getProperty(key)查詢value
public class Demo5 {
public static void main(String[] args) {
try {
Properties prop = new Properties();
System.out.println(prop);
System.out.println(prop.size());
System.out.println(prop.isEmpty());
* 從包中加載資源
InputStream input = Demo5.class.getClassLoader().getResourceAsStream("db.properties");
* 將文件內(nèi)容讀取到散列表中
prop.load(input);
* 查詢數(shù)據(jù),讀取文件內(nèi)容
String driver = prop.getProperty("jdbc.driver");
System.out.println(driver);
} catch (IOException e) {
e.printStackTrace();
}
}
}
修改數(shù)據(jù)參數(shù)后重新寫入到文件中
Properties prop = new Properties();
prop.load(new FileInputStream(path));
prop.setProperty("name", "libai");
prop.setProperty("age", 22 + "");
prop.store(new FileOutputStream(path), "我的注釋");
將Properties與JDBC查詢數(shù)據(jù)庫(kù)結(jié)合起來隅忿,方便之后修改數(shù)據(jù)庫(kù)連接信息(總不能老是去修改java代碼吧)
* Properties讀取配置文件,
*
* Properties是java中專門用于讀取配置文件的API
* 管理數(shù)據(jù)庫(kù)連接
* 在軟件中數(shù)據(jù)庫(kù)連接使用非常頻繁谨垃,如果每次都創(chuàng)建連接启搂,就會(huì)造成代碼的大量冗余,
* 常規(guī)的做法是建立數(shù)據(jù)庫(kù)連接工具類刘陶,封裝數(shù)據(jù)庫(kù)連接過程胳赌,統(tǒng)一數(shù)據(jù)庫(kù)連接過程,使用時(shí)候就可以簡(jiǎn)化代碼匙隔。
*
*
* 實(shí)現(xiàn)步驟:
* 1.創(chuàng)建數(shù)據(jù)庫(kù)連接參數(shù)文件疑苫,db.properties
* 2.創(chuàng)建DbUtils.java封裝數(shù)據(jù)庫(kù)連接方法
* -利用Properties讀取配置文件中的數(shù)據(jù)庫(kù)連接參數(shù)
* -創(chuàng)建方法 getConnection封裝數(shù)據(jù)庫(kù)連接過程
* 3.使用getConnection方法
下面是封裝了Properties讀取數(shù)據(jù)庫(kù)配置信息的和連接數(shù)據(jù)庫(kù)的類
package com.libai;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
public class DbUtils {
* 封裝創(chuàng)建數(shù)據(jù)庫(kù)連接的過程,簡(jiǎn)化數(shù)據(jù)庫(kù)連接
* 因?yàn)橹恍枰环莘自穑瑒t定義為靜態(tài)變量
static String driver;
static String url;
static String username;
static String password;
static {
try {
Properties cfg = new Properties();
InputStream input = DbUtils.class.getClassLoader().getResourceAsStream("db.properties");
cfg.load(input);
driver = cfg.getProperty("jdbc.driver");
url = cfg.getProperty("jdbc.url");
username = cfg.getProperty("jdbc.username");
password = cfg.getProperty("jdbc.password");
System.out.println(cfg);
----上面這句打印{jdbc.password=root, jdbc.username=root, jdbc.url=jdbc:mysql://localhost/test, initSize=5, jdbc.driver=com.mysql.jdbc.Driver, maxActive=10}
}catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
try {
Class.forName(driver);
Connection conn = DriverManager.getConnection(url,username,password);
return conn;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
* 關(guān)閉數(shù)據(jù)庫(kù)的連接
public static void close(Connection conn) {
try {
if(conn != null) {
conn.close();
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
* ResultSet代表DQL查詢結(jié)果捍掺,是2維結(jié)果,其內(nèi)部維護(hù)了一個(gè)讀取數(shù)據(jù)的游標(biāo)再膳,
* 默認(rèn)情況挺勿,游標(biāo)在第一行數(shù)據(jù)之前,當(dāng)調(diào)用next()方法時(shí)候喂柒,游標(biāo)會(huì)向下移動(dòng)不瓶,
* 并將返回結(jié)果集中是否包含數(shù)據(jù),如果包含數(shù)據(jù)就返回true灾杰,
* 結(jié)果集還提供了很好get方法用于獲取結(jié)果集游標(biāo)指向當(dāng)前行數(shù)據(jù)蚊丐。
調(diào)用上面的類實(shí)現(xiàn)連接數(shù)據(jù)庫(kù)
package com.libai;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class Demo4 {
public static void main(String[] args) {
Connection conn = null;
try {
conn = DbUtils.getConnection();
Statement st = conn.createStatement();
String sql = "select * from rabbit";
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
System.out.println(rs.getInt("id")+", "+rs.getString("name"));
}
}catch (Exception e) {
e.printStackTrace();
}finally {
DbUtils.close(conn);
}
}
}
打印:
1, libai
2, ttt
數(shù)據(jù)庫(kù)連接池DBCP
數(shù)據(jù)庫(kù)連接池中存放一定數(shù)量的數(shù)據(jù)庫(kù)連接艳吠,需要使用時(shí)從其中取麦备,用完了就放回池中,循環(huán)使用(數(shù)據(jù)庫(kù)連接的連接和關(guān)閉都是十分耗費(fèi)資源的)
單例模式:singleton
池(集合)化模式:
使用有限的對(duì)象數(shù)量服務(wù)于大量的客戶端請(qǐng)求昭娩。
Datasource
1.內(nèi)部是連接池
2.java.sql.Datasource, Connection getConnection();
3.Connection.close()
使用DBCP
* 使用DBCP
*
* 1.導(dǎo)入連接池jar
* 2.創(chuàng)建連接池對(duì)象
* 3.設(shè)置數(shù)據(jù)庫(kù)必須的連接參數(shù)
* 4.設(shè)置可選的連接池管理策略參數(shù)
* 5.從連接池中獲得活動(dòng)的數(shù)據(jù)庫(kù)連接
* 6.使用連接范圍數(shù)據(jù)庫(kù)
* 7.使用以后關(guān)閉數(shù)據(jù)庫(kù)連接凛篙,這個(gè)不是真的關(guān)閉連接,而是將使用過的連接歸還到連接池栏渺。
*
* 為了便捷的使用連接池呛梆,經(jīng)常將連接池封裝為一個(gè)連接管理工具類。
maven導(dǎo)入包DBCP
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
一個(gè)數(shù)據(jù)庫(kù)連接池的簡(jiǎn)單例子
package day02;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.commons.dbcp.BasicDataSource;
public class Demo1 {
public static void main(String[] args) throws SQLException {
BasicDataSource ds = new BasicDataSource();
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost/test";
String username = "root";
String password = "root";
* 設(shè)置必須的參數(shù)
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
* 設(shè)置連接池的管理策略參數(shù)
ds.setInitialSize(2); --- 一開始就設(shè)置池中存放兩個(gè)連接
ds.setMaxActive(100); ---最多可以有100個(gè)連接
* 使用連接池中的數(shù)據(jù)庫(kù)連接迈嘹,取不到就堵塞
Connection conn = ds.getConnection();
* 執(zhí)行SQL
Statement st = conn.createStatement();
String sql = "select * from rabbit";
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println(id+", "+name);
}
* 歸還連接到數(shù)據(jù)庫(kù)連接池!全庸!
conn.close();
}
}
打有阒佟:
1, libai
2, talent
3, where
4, HOW
db.properties文件添加兩條配置
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test
jdbc.username=root
jdbc.password=root
# parameter for BasicDataSource,initSize是初始化建立1個(gè)連接壶笼,最大2個(gè)連接
initSize=1
maxActive=2
DBUtils.java 連接池模塊神僵,直接調(diào)用靜態(tài)方法就行,配置參數(shù)依據(jù)上面的db.properties文件覆劈,下面所有的代碼用到的全都是下面這個(gè)DBUtils.java 連接池模塊保礼。
package day02;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
* 連接池版本的數(shù)據(jù)庫(kù)連接管理工具類沛励,適合于并發(fā)場(chǎng)合
public class DBUtils {
private static String driver;
private static String url;
private static String username;
private static String password;
private static int initSize;
private static int maxActive;
private static BasicDataSource ds;
static {
try {
Properties cfg = new Properties();
InputStream input = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
cfg.load(input);
driver = cfg.getProperty("jdbc.driver");
url = cfg.getProperty("jdbc.url");
username = cfg.getProperty("jdbc.username");
password = cfg.getProperty("jdbc.password");
initSize = Integer.parseInt(cfg.getProperty("initSize"));
maxActive = Integer.parseInt(cfg.getProperty("maxActive"));
ds = new BasicDataSource();
* 初始化連接池
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
ds.setInitialSize(initSize);
ds.setMaxActive(maxActive);
} catch (IOException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
Connection conn = null;
try {
* getConnection()從連接池中獲取重用的連接,如果連接池滿了炮障,
* 則等待目派,如果有連接歸還,則獲取重用的連接
conn = ds.getConnection();
return conn;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
* 將用過的連接歸還到連接池
public static void close(Connection conn) {
try {
if(conn != null) {
conn.close();
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
調(diào)用上面的靜態(tài)方法連接池
package day02;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo2 {
public static void main(String[] args) {
Connection conn = DBUtils.getConnection();
Statement st;
try {
st = conn.createStatement();
String sql = "select * from rabbit where id>2";
ResultSet rs= st.executeQuery(sql);
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println(id+", "+name);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtils.close(conn);
}
}
}
打有灿:
3, where
4, HOW
線程測(cè)試數(shù)據(jù)連接池的效果
package day02;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
* 線程并發(fā)測(cè)試
public class Demo3 {
public static void main(String[] args) {
for(int i=0; i<5; i++) {
Thread t = new DemoThread(5000);
t.start();
}
}
}
class DemoThread extends Thread{
int wait;
public DemoThread(int wait) {
this.wait = wait;
}
public void run() {
Connection conn = null;
try {
* getConnection方法在連接池中沒有連接可以使用的時(shí)候企蹭,會(huì)堵塞等待
conn = DBUtils.getConnection();
Statement st = conn.createStatement();
String sql = "select 'Helllo' as a from dual";
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
System.out.println(Thread.currentThread().getName()+" "+rs.getString("a"));
}
Thread.sleep(wait);
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+" 歸還連接");
DBUtils.close(conn);
}
}
}
* 連接池中最大只有兩個(gè)連接時(shí),第三個(gè)線程獲取連接時(shí)會(huì)等待智末,
* 等待到有連接歸還為止
---- 上面的db.preproties文件中最大連接數(shù)設(shè)置為2了谅摄,所以此處兩個(gè)兩個(gè)執(zhí)行
打印:
Thread-0 Helllo
Thread-3 Helllo
Thread-3 歸還連接
Thread-0 歸還連接
Thread-1 Helllo
Thread-2 Helllo
Thread-2 歸還連接
Thread-1 歸還連接
Thread-4 Helllo
Thread-4 歸還連接
使用PreparedStatement提高數(shù)據(jù)庫(kù)系統(tǒng)性能系馆,重用執(zhí)行計(jì)劃
SQL語(yǔ)句翻譯成執(zhí)行計(jì)劃送漠,才能執(zhí)行
SQL語(yǔ)句完全一樣(大小寫和空格都一樣)的情況下,會(huì)重用執(zhí)行計(jì)劃
使用帶參數(shù)的SQL語(yǔ)句可以使得重用執(zhí)行計(jì)劃由蘑,提高數(shù)據(jù)庫(kù)性能
用法跟上面的Statement差不多
package day02;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class Demo4 {
public static void main(String[] args) {
Connection conn = null;
try {
conn = DBUtils.getConnection();
* 帶參數(shù)的SQL語(yǔ)句
String sql = "insert into rabbit (id,name) values (?,?)";
* 將SQL語(yǔ)句發(fā)送到數(shù)據(jù)庫(kù)闽寡,創(chuàng)建執(zhí)行計(jì)劃,返回值ps就代表執(zhí)行計(jì)劃
PreparedStatement ps= conn.prepareStatement(sql);
* 替換[執(zhí)行計(jì)劃]中的參數(shù)纵穿,2個(gè)參數(shù)下隧,按照序號(hào)發(fā)送參數(shù)
ps.setInt(1, 5);
ps.setString(2, "zhizhang");
* 執(zhí)行執(zhí)行計(jì)劃
int n1 = ps.executeUpdate();
System.out.println(n1);
* 重用
ps.setInt(1, 6);
ps.setString(2, "hello");
int n2 = ps.executeUpdate();
System.out.println(n2);
} catch(Exception e) {
e.printStackTrace();
} finally {
DBUtils.close(conn);
}
}
}
打印: --執(zhí)行成功谓媒,返回修改的行數(shù)
1
1
換成update淆院,也是類似的
conn = DBUtils.getConnection();
String sql = "update rabbit set name=? where id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "demo change1");
ps.setInt(2, 5);
int n1 = ps.executeUpdate();
System.out.println(n1);
ps.setString(1, "demo change2");
ps.setInt(2, 6);
int n2 = ps.executeUpdate();
System.out.println(n2);
note
* JDBC
* 數(shù)據(jù)庫(kù)連接池
*
* DriverManager管理數(shù)據(jù)庫(kù)連接適合單線程情況,而在多線程并發(fā)情況下句惯,為了能夠重用數(shù)據(jù)庫(kù)連接土辩,
* 同時(shí)控制并發(fā)連接總數(shù),避免數(shù)據(jù)庫(kù)連接超載抢野,一定要使用數(shù)據(jù)庫(kù)連接池拷淘。
*
* 連接池原理:
* 數(shù)據(jù)庫(kù)連接池的開源實(shí)現(xiàn)非常多,DBCP是常用的連接池之一指孤。
只要用到了SQL語(yǔ)句拼接启涯,要思考是否有SQL注入的風(fēng)險(xiǎn),PreparedStatement可以避免SQL注入的風(fēng)險(xiǎn)恃轩,它將單引號(hào)等特殊字符轉(zhuǎn)義结洼。
* PreparedStatement 對(duì)象用于執(zhí)行帶參數(shù)的預(yù)編譯執(zhí)行計(jì)劃
*
* 關(guān)于執(zhí)行計(jì)劃:
* 1.任何SQL執(zhí)行過程都是先編譯“執(zhí)行計(jì)劃”,再執(zhí)行“執(zhí)行計(jì)劃”
* 2.數(shù)據(jù)庫(kù)為了優(yōu)化性能叉跛,在SQL相同的時(shí)候松忍,會(huì)重用執(zhí)行計(jì)劃
* -執(zhí)行計(jì)劃編譯較慢
* -重用執(zhí)行計(jì)劃可以提提高數(shù)據(jù)庫(kù)性能
* 3.數(shù)據(jù)庫(kù)只在SQL語(yǔ)句完全一樣時(shí)候才重用相同的執(zhí)行計(jì)劃
* -如果SQL語(yǔ)句中有一個(gè)字符的更改,會(huì)創(chuàng)建不同的執(zhí)行計(jì)劃
* -SQL中一個(gè)空格或者一個(gè)大小寫的不同也會(huì)創(chuàng)建不同的執(zhí)行計(jì)劃
*
* PreparedStatement 好處是可以重復(fù)使用執(zhí)行計(jì)劃筷厘,提高DB效率
*
* 使用步驟:
* 1.將帶參數(shù)的SQL發(fā)送到數(shù)據(jù)庫(kù)創(chuàng)建執(zhí)行計(jì)劃
* 2.替換執(zhí)行計(jì)劃中的參數(shù)
* 3.執(zhí)行執(zhí)行計(jì)劃鸣峭,得到結(jié)果
*
* PreparedStatement 可以避免SQL注入攻擊宏所,它將單引號(hào)等特殊字符加上反斜杠轉(zhuǎn)義了
SQL注入例子
package day02;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
public class Demo6 {
public static void main(String[] args) {
if(login()) {
System.out.println("登陸成功");
}else {
System.out.println("賬戶名或密碼錯(cuò)誤");
}
}
public static boolean login() {
Scanner scan = new Scanner(System.in);
System.out.print("請(qǐng)輸入用戶名:");
String username = scan.nextLine().trim();
System.out.print("請(qǐng)輸入密碼:");
String password = scan.nextLine().trim();
Connection conn = null;
int back = 0;
try {
conn = DBUtils.getConnection();
String sql1 = "select count(*) as back from rabbit where username=? and password=?";
--- 第一種方式,PreparedStatement
PreparedStatement ps = conn.prepareStatement(sql1);
ps.setString(1, username);
ps.setString(2, password);
System.out.println("自動(dòng)拼接:"+ps);
// ResultSet rs = ps.executeQuery(); ---未執(zhí)行
--- 第一種方式摊溶,Statement
Statement st = conn.createStatement();
String sql2 = "select count(*) as back from rabbit where username='"+ username +"' and password='"+ password +"'";
System.out.println("手動(dòng)拼接:"+sql2);
ResultSet rs = st.executeQuery(sql2);
rs.next();
back = rs.getInt("back");
System.out.println("back = " + back);
} catch (Exception e) {
e.printStackTrace();
}finally {
DBUtils.close(conn);
}
if(back >= 1) {
return true;
}else {
return false;
}
}
}
打印;
請(qǐng)輸入用戶名:aa
請(qǐng)輸入密碼:123' or '1'='1
自動(dòng)拼接:com.mysql.jdbc.JDBC42PreparedStatement@3daa422a: select count(*) as back from rabbit where username='aa' and password='123\' or \'1\'=\'1'
手動(dòng)拼接:select count(*) as back from rabbit where username='aa' and password='123' or '1'='1'
back = 6
登陸成功
ResultSet中存在可滾動(dòng)的結(jié)果集爬骤,但從來不用,其性能差
獲取的ResultSet結(jié)果集中更扁,存在獲取的數(shù)據(jù)庫(kù)信息的元數(shù)據(jù)盖腕,可用ResultSetMetaData API調(diào)用獲取
* 結(jié)果集元數(shù)據(jù)
* ResultSetMetaData用于描述查詢結(jié)果的相關(guān)性信息,其中包含列名稱浓镜,列數(shù)量溃列,類數(shù)據(jù)類型等
package day03;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class Demo1 {
public static void main(String[] args) {
Connection conn = null;
try {
conn = DBUtils.getConnection();
String sql = "select * from rabbit where id=1";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
--- 調(diào)用元數(shù)據(jù),具體的信息都可以從接口中取得膛薛,具體可以查看java.sql文檔
System.out.println(rs.getMetaData());
while(rs.next()) {
String username = rs.getString("username");
String password = rs.getString("password");
System.out.println("username = "+username+" password = "+password);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.close(conn);
}
}
}
打犹:
com.mysql.jdbc.ResultSetMetaData@654f0d9c - Field level information:
com.mysql.jdbc.Field@6a400542[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=id,originalColumnName=id,mysqlType=3(FIELD_TYPE_LONG),flags= AUTO_INCREMENT PRIMARY_KEY, charsetIndex=63, charsetName=US-ASCII]
com.mysql.jdbc.Field@6580cfdd[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=username,originalColumnName=username,mysqlType=253(FIELD_TYPE_VAR_STRING),flags=, charsetIndex=8, charsetName=WINDOWS-1252]
com.mysql.jdbc.Field@7e0b85f9[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=password,originalColumnName=password,mysqlType=253(FIELD_TYPE_VAR_STRING),flags=, charsetIndex=33, charsetName=UTF-8]
username = libai password = 111
JDBC事務(wù)控制
* JDBC 事務(wù)控制,Transaction 一組不可分割的操作哄啄。
*
* 數(shù)據(jù)庫(kù)提供了事務(wù)控制功能雅任,支持ACID特性。
* JDBC提供了API咨跌,方便調(diào)用數(shù)據(jù)庫(kù)的事務(wù)功能沪么。
*
* 相關(guān)API:
* - Connection.getAutoCommit():獲得當(dāng)前事務(wù)的提交方式,默認(rèn)為true
* - Connection.setAutoCommit():設(shè)置事務(wù)的提交屬性锌半,參數(shù)是
* -true:自動(dòng)提交禽车; -false:不自動(dòng)提交(默認(rèn)自動(dòng)提交)
* - Connection.commit():提交事務(wù)
* - Connection.rollback():回滾事務(wù)
特點(diǎn):Atomic,原子性刊殉,不可分割殉摔,整體性。
Consistent记焊,一致性逸月,數(shù)據(jù)不被破壞。
isolate遍膜,隔離性碗硬,事務(wù)之間是獨(dú)立的,不能被干擾的瓢颅。
Durable恩尾,持續(xù)性,數(shù)據(jù)被永久保存起來惜索。
模擬轉(zhuǎn)賬特笋,不符合條件拋出錯(cuò)誤回滾剃浇,
注意:MySQL數(shù)據(jù)庫(kù)測(cè)試表格的數(shù)據(jù)引擎要設(shè)置為InnoDB巾兆,默認(rèn)的MyISAM不支持事務(wù)處理猎物。
package day03;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Demo2 {
public static void main(String[] args) {
Connection conn = null;
try {
conn = DBUtils.getConnection();
conn.setAutoCommit(false);
String sql1 = "update myaccount set balance=balance+? where id=?";
String sql2 = "select balance from myaccount where id=?";
PreparedStatement ps = conn.prepareStatement(sql1);
* 一個(gè)賬戶減少 1000
ps.setInt(1, -1000);
ps.setInt(2, 1);
int n1 = ps.executeUpdate();
System.out.println(n1);
* 一個(gè)賬戶增加 1000
ps.setInt(1, 1000);
ps.setInt(2, 2);
int n2 = ps.executeUpdate();
System.out.println(n2);
ps.close();
* 檢測(cè)余額是否大于等于0
ps = conn.prepareStatement(sql2);
ps.setInt(1, 1);
ResultSet rs = ps.executeQuery();
rs.next();
int balance = rs.getInt("balance");
if(balance<0) {
throw new RuntimeException("余額不足,轉(zhuǎn)賬失敗");
}else {
System.out.println("轉(zhuǎn)賬成功");
}
* 延時(shí)20s期間角塑,在命令行中查看當(dāng)前修改的表格時(shí)蔫磨,是看不到本次交易結(jié)果的
* 命令行中修改時(shí),會(huì)堵塞到此次修改完畢
System.out.println("延時(shí)中");
Thread.sleep(20000);
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
if(conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
DBUtils.close(conn);
}
}
}
打悠粤妗:
1
1
轉(zhuǎn)賬成功
延時(shí)中
效果圖
JDBC批量SQL處理
package day03;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays;
public class Demo3 {
public static void main(String[] args) {
String sql1 = "CREATE TABLE IF NOT EXISTS `account_01` (" +
" `id` int(11) NOT NULL," +
" `balance` int(11) NOT NULL," +
" PRIMARY KEY (`id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
String sql2 = "CREATE TABLE IF NOT EXISTS `account_02` (" +
" `id` int(11) NOT NULL," +
" `balance` int(11) NOT NULL," +
" PRIMARY KEY (`id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
String sql3 = "CREATE TABLE IF NOT EXISTS `account_03` (" +
" `id` int(11) NOT NULL," +
" `balance` int(11) NOT NULL," +
" PRIMARY KEY (`id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
Connection conn = null;
try {
conn = DBUtils.getConnection();
Statement ps = conn.createStatement();
* 將SQL語(yǔ)句添加到Statement緩存中
ps.addBatch(sql1);
ps.addBatch(sql2);
ps.addBatch(sql3);
* 執(zhí)行一批SQL
int[] n = ps.executeBatch();
System.out.println(Arrays.toString(n));
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.close(conn);
}
}
}
打拥倘纭:
[0, 0, 0] ---執(zhí)行成功
Statement批量插入數(shù)據(jù),注意窒朋,SQL語(yǔ)句中的并不是單引號(hào)搀罢,而是tab鍵上面的那個(gè)鍵的符號(hào),不使用(`)也是可以的侥猩。
* 批量執(zhí)行
* 批處理:發(fā)送到數(shù)據(jù)庫(kù)作為一個(gè)單元執(zhí)行的一組更新語(yǔ)句
*
* 批處理降低了應(yīng)用程序和數(shù)據(jù)庫(kù)之間的網(wǎng)絡(luò)調(diào)用榔至,相比單個(gè)SQL語(yǔ)句的處理,批處理更為有效
*
* API方法:
* - addBatch(String sql)
* - Statement類的方法欺劳,可以將多條SQL語(yǔ)句添加到Statement對(duì)象的
* SQL語(yǔ)句列表中
* - addBatch()
* - PreparedStatement類的方法唧取,可以將多條預(yù)編譯的SQL語(yǔ)句添加到
* PreparedStatement對(duì)象的SQL語(yǔ)句列表中
* - executeBatch()
* - 把Statement對(duì)象或PreparedStatement對(duì)象語(yǔ)句列表中的所有SQL
* 語(yǔ)句發(fā)送給數(shù)據(jù)庫(kù)進(jìn)行處理
* - clearBatch()
* - 清空當(dāng)前SQL語(yǔ)句列表
*
*
* 防止批量過大出現(xiàn)OutOfMemory錯(cuò)誤:
* 如果PreparedStatement對(duì)象中的SQL列表包含過多的待處理SQL語(yǔ)句,可能會(huì)
* 產(chǎn)生OutOfMemory錯(cuò)誤划提,分段處理緩沖列表
package day03;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays;
public class Demo3 {
public static void main(String[] args) {
String sql1 = "insert into myaccount (`id`,`balance`) values (1,1000)";
String sql2 = "insert into myaccount (`id`,`balance`) values (2,2000)";
String sql3 = "insert into myaccount (`id`,`balance`) values (3,3000)";
Connection conn = null;
try {
conn = DBUtils.getConnection();
Statement ps = conn.createStatement();
* 將SQL語(yǔ)句添加到Statement緩存中
ps.addBatch(sql1);
ps.addBatch(sql2);
ps.addBatch(sql3);
* 執(zhí)行一批SQL
int[] n = ps.executeBatch();
System.out.println(Arrays.toString(n));
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.close(conn);
}
}
}
打臃愕堋:
[1, 1, 1] ---執(zhí)行成功
PreparedStatement批量插入數(shù)據(jù)
package day03;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Arrays;
public class Demo4 {
public static void main(String[] args) {
String sql = "insert into myaccount (id,balance) values(?,?)";
Connection conn = DBUtils.getConnection();
try {
PreparedStatement ps = conn.prepareStatement(sql);
for(int i=0; i<100; i++) {
* 替換參數(shù)
ps.setInt(1, i+100);
ps.setInt(2, i*1000);
* 將參數(shù)添加到ps緩沖區(qū)
ps.addBatch();
* 清空緩沖區(qū),一般是在數(shù)據(jù)量特別大的時(shí)候避免內(nèi)存溢出
if(i%8 == 0) {
ps.executeBatch();
ps.clearBatch();
}
}
int[] n = ps.executeBatch();
System.out.println(Arrays.toString(n));
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.close(conn);
}
}
}
打优敉:
[1, 1, 1] --- 每8個(gè)一批已經(jīng)被執(zhí)行過了
返回自動(dòng)主鍵
JDBC API提供了返回插入數(shù)據(jù)期間自動(dòng)生成ID的API
PreparedStatement ps = conn.prepareStatement(sql,列名列表);
ResutSet rs = ps.getGeneratedKeys();
向數(shù)據(jù)庫(kù)中插入/讀取image和長(zhǎng)文本
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.junit.Test;
import Utils.JDBCUtils;
public class TestCase2 {
* 向數(shù)據(jù)庫(kù)中插入image淡诗,長(zhǎng)文本
* 在數(shù)據(jù)庫(kù)中image對(duì)應(yīng)的存儲(chǔ)類型是longblob,長(zhǎng)文本對(duì)應(yīng)的存儲(chǔ)類型是longtext
@Test
public void test1() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO customers(name,sex,age,photo,memo) VALUES(?,?,?,?,?)";
PreparedStatement ppst = conn.prepareStatement(sql);
ppst.setString(1, "hello");
ppst.setInt(2, 0);
ppst.setInt(3, 23);
ppst.setBinaryStream(4, new FileInputStream("E://TestCase//day20//demo.jpg"));
ppst.setString(5, "aaaaaaaaaaaaaaaaaaaaa");
ppst.executeUpdate();
ppst.close();
conn.close();
System.out.println("over");
}
* 從數(shù)據(jù)庫(kù)中讀取image和長(zhǎng)文本
@Test
public void test2() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "SELECT * FROM customers WHERE id=?";
PreparedStatement ppst = conn.prepareStatement(sql);
ppst.setInt(1, 1);
ResultSet rs = ppst.executeQuery();
rs.next();
int id = rs.getInt("id");
String name = rs.getString("name");
int sex = rs.getInt("sex");
int age = rs.getInt("age");
InputStream photo = rs.getBinaryStream("photo");
String memo = rs.getString("memo");
System.out.println(id + ", " + name + ", " + age + ", " + sex + "," + memo);
System.out.println("photo length = " + photo.available());
FileOutputStream fos = new FileOutputStream("E://TestCase//day24//demo.jpg");
byte[] buffer = new byte[1024];
int len = -1;
while((len = photo.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.close();
ppst.close();
conn.close();
System.out.println("over");
}
}
存儲(chǔ)過程
是在服務(wù)器中直接調(diào)用的掸犬,只需要調(diào)用時(shí)傳遞必要的參數(shù)袜漩,就可以直接執(zhí)行,速度非惩逅椋快宙攻。
前提是數(shù)據(jù)庫(kù)不再需要其他的參數(shù)來執(zhí)行數(shù)據(jù)庫(kù)的操作了
在Java中用CallableStatement來負(fù)責(zé)調(diào)用數(shù)據(jù)庫(kù)的存儲(chǔ)過程
存儲(chǔ)過程
store procedure,存放在數(shù)據(jù)庫(kù)中的一組SQL,在服務(wù)器端執(zhí)行
創(chuàng)建存儲(chǔ)過程
mysql>delimiter // --聲明結(jié)束符
create procedure sp_biginsert(in num int)
begin
start transaction;
declare i int default 0;
while i < num do
insert into customers(name,age,sex) values(concat('tom',i),i,i);
set i = i + 1;
end while;
commit;
end//
mysql> show procedure status;//可以查看所有的存儲(chǔ)過程
創(chuàng)建存儲(chǔ)過程介褥,作用是返回customers表的數(shù)據(jù)量
mysql> create procedure sp_count(out param int)
-> begin
-> select count(*) into param from customers;
-> end
-> //
Query OK, 0 rows affected (0.11 sec)
調(diào)用存儲(chǔ)過程
@Test
public void test3() throws Exception {
Connection conn = JDBCUtils.getConnection();
//輸出的
String sql = "{call sp_count(?)}";
//創(chuàng)建cst對(duì)象
CallableStatement cst = conn.prepareCall(sql);
//注冊(cè)輸出參數(shù)
cst.registerOutParameter(1, Types.INTEGER);
//執(zhí)行存儲(chǔ)過程
cst.execute();
//取得輸出參數(shù)
int count = cst.getInt(1);
System.out.println(count);
}
打印結(jié)果:取得該表的數(shù)據(jù)量
10000
返回a+b的值
返回兩個(gè)數(shù)的合
mysql> create procedure sp_add(in a int, in b int,out c int)
-> begin
-> set c:=a+b;
-> end
-> //
Query OK, 0 rows affected (0.00 sec)
調(diào)用該存儲(chǔ)過程
@Test
public void test4() throws Exception {
Connection conn = JDBCUtils.getConnection();
//輸出的
String sql = "{call sp_add(?,?,?)}";
//創(chuàng)建cst對(duì)象
CallableStatement cst = conn.prepareCall(sql);
//對(duì)于輸入?yún)?shù)需要綁定參數(shù)值
cst.setInt(1, 1);
cst.setInt(2, 5);
//注冊(cè)輸出參數(shù)
cst.registerOutParameter(3, Types.INTEGER);
//執(zhí)行存儲(chǔ)過程
cst.execute();
//取得輸出參數(shù)
int count = cst.getInt(3);
System.out.println(count);
}
打印結(jié)果:
5
返回a-b的值座掘,一個(gè)數(shù)可以作為輸入量,也可以作為輸出量
返回a-b的值柔滔,一個(gè)數(shù)可以作為輸入量溢陪,也可以作為輸出量
mysql> create procedure sp_subtract(in a int, inout b int)
-> begin
-> set b:=a-b;
-> end
-> //
Query OK, 0 rows affected (0.00 sec)
調(diào)用該存儲(chǔ)過程
@Test
public void test5() throws Exception {
Connection conn = JDBCUtils.getConnection();
//輸出的
String sql = "{call sp_subtract(?,?)}";
//創(chuàng)建cst對(duì)象
CallableStatement cst = conn.prepareCall(sql);
//對(duì)于輸入?yún)?shù)需要綁定參數(shù)值
cst.setInt(1, 1);
cst.setInt(2, 5);
//注冊(cè)輸出參數(shù)
cst.registerOutParameter(2, Types.INTEGER);
//執(zhí)行存儲(chǔ)過程
cst.execute();
//取得輸出參數(shù)
int count = cst.getInt(2);
System.out.println(count);
}
打印結(jié)果:
-4
創(chuàng)建一個(gè)函數(shù),并在JDBC中調(diào)用
該函數(shù)返回a+b的值
mysql> create function f_add(a int, b int) returns int
-> return a+b//
Query OK, 0 rows affected (0.00 sec)
調(diào)用該函數(shù)
@Test
public void test6() throws Exception {
Connection conn = JDBCUtils.getConnection();
//輸出的
String sql = "{? = call f_add(?,?)}";
//創(chuàng)建cst對(duì)象
CallableStatement cst = conn.prepareCall(sql);
//對(duì)于輸入?yún)?shù)需要綁定參數(shù)值
cst.setInt(2, 100);
cst.setInt(3, 600);
//注冊(cè)輸出參數(shù)
cst.registerOutParameter(1, Types.INTEGER);
//執(zhí)行存儲(chǔ)過程
cst.execute();
//取得輸出參數(shù)
int count = cst.getInt(1);
System.out.println(count);
}
打印結(jié)果:
700