來源於how2j
特殊操作
獲取自增長ID
- 在Statement通過execute或者executeUpdate執行完插入語句後,MySQL會爲新插入的數據分配一個自增長id,(前提是這個表的id設置爲了自增長,在Mysql創建表的時候,AUTO_INCREMENT就表示自增長)
CREATE TABLE hero ( id int(11) AUTO_INCREMENT, ... }
- 但是無論是execute還是executeUpdate都不會返回這個自增長id是多少。需要通過Statement的getGeneratedKeys獲取該id
注: 第20行的代碼,後面加了個Statement.RETURN_GENERATED_KEYS
參數,以確保會返回自增長ID。 通常情況下不需要加這個,有的時候需要加,所以先加上,保險一些PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
- 代碼:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String sql = "insert into hero values(null,?,?,?)";
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
) {
ps.setString(1, "蓋倫");
ps.setFloat(2, 616);
ps.setInt(3, 100);
// 執行插入語句
ps.execute();
// 在執行完插入語句後,MySQL會爲新插入的數據分配一個自增長id
// JDBC通過getGeneratedKeys獲取該id
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
int id = rs.getInt(1);
System.out.println(id);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- res:
- 不加
Statement.RETURN_GENERATED_KEYS
也可
獲取表的元數據
- 數據庫服務器相關的數據,比如數據庫版本,有哪些表,表有哪些字段,字段類型是什麼等等。
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");) {
// 查看數據庫層面的元數據
// 即數據庫服務器版本,驅動版本,都有哪些數據庫等等
DatabaseMetaData dbmd = c.getMetaData();
// 獲取數據庫服務器產品名稱
System.out.println("數據庫產品名稱:\t"+dbmd.getDatabaseProductName());
// 獲取數據庫服務器產品版本號
System.out.println("數據庫產品版本:\t"+dbmd.getDatabaseProductVersion());
// 獲取數據庫服務器用作類別和表名之間的分隔符 如test.user
System.out.println("數據庫和表分隔符:\t"+dbmd.getCatalogSeparator());
// 獲取驅動版本
System.out.println("驅動版本:\t"+dbmd.getDriverVersion());
System.out.println("可用的數據庫列表:");
// 獲取數據庫名稱
ResultSet rs = dbmd.getCatalogs();
while (rs.next()) {
System.out.println("數據庫名稱:\t"+rs.getString(1));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
事務
- 沒有事務的情況:
假設業務操作是:加血,減血各做一次
結束後,英雄的血量不變
而減血的SQL
不小心寫錯寫成了 updata(而非update)
那麼最後結果是血量增加了,而非期望的不變
- 代碼:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
Statement s = c.createStatement();) {
//沒有事務的前提下
//假設業務操作時,加血,減血各做一次
//結束後,英雄的血量不變
//加血的SQL
String sql1 = "update hero set hp = hp +1 where id = 22";
s.execute(sql1);
//減血的SQL
//不小心寫錯寫成了 updata(而非update)
String sql2 = "updata hero set hp = hp -1 where id = 22";
s.execute(sql2);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- res:
使用事務
- 在事務中的多個操作,要麼都成功,要麼都失敗;通過
c.setAutoCommit(false);
關閉自動提交;使用c.commit();
進行手動提交
在22行-35行之間的數據庫操作,就處於同一個事務當中,要麼都成功,要麼都失敗所以,雖然第一條SQL語句是可以執行的,但是第二條SQL語句有錯誤,其結果就是兩條SQL語句都沒有被提交。 除非兩條SQL語句都是正確的。 - 代碼:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
Statement s = c.createStatement();) {
// 有事務的前提下
// 在事務中的多個操作,要麼都成功,要麼都失敗
c.setAutoCommit(false);
// 加血的SQL
String sql1 = "update hero set hp = hp +1 where id = 22";
s.execute(sql1);
// 減血的SQL
// 不小心寫錯寫成了 updata(而非update)
String sql2 = "updata hero set hp = hp -1 where id = 22";
s.execute(sql2);
// 手動提交
c.commit();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- res: 血量沒變:
- MYSQL 表的類型必須是INNODB才支持事務
在Mysql中,只有當表的類型是INNODB的時候,才支持事務,所以需要把表的類型設置爲INNODB,否則無法觀察到事務. - 修改表的類型爲INNODB的SQL:
alter table hero ENGINE = innodb;
- 查看錶的類型的SQL:
show table status from how2java;
事務應用:
- 設計一個代碼,刪除數據前在控制檯彈出一個提示:
是否要刪除數據(Y/N)
如果用戶輸入Y,則刪除
如果輸入N則不刪除。
如果輸入的既不是Y也不是N,則重複提示
- 代碼:
public static void main(String[] args) {
Scanner w = new Scanner(System.in);
Connection c = null;
Statement s1 = null;
Statement s2 = null;
try {
// 啓動數據庫驅動
Class.forName("com.mysql.jdbc.Driver");
// 建立數據庫連接
c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root",
"admin");
// 根據sql獲取Statement對象
s1 = c.createStatement();
s2 = c.createStatement();
// 關閉自動提交
c.setAutoCommit(false);
// 查詢出前十條,並獲取前十條id
String sql = "select * from hero limit 0,10";
ResultSet rs = s1.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt("id");
System.out.println("是否刪除id =" + id + "的數據");
String sql1 = "delete from hero where id = " + id;
s2.execute(sql1);
}
// 判斷是否刪除
while (true) {
System.out.println("是否刪除數據(Y/N)");
String a = w.next();
if (a.equals("Y")) {
// 手動提交
c.commit();
System.out.println("刪除成功");
break;
}
if (a.equals("N")) {
System.out.println("放棄刪除");
break;
}
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (s1 != null) {
try {
s1.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (s1 != null) {
try {
s1.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 注意:這是因爲創建兩個對象的原因是Resultset rs 對象再rs.next()循環結束之後,會將第一個對象閉合,比如如果之後的statement對象繼續使用st4Query,而不是st4Delete的話,會提示你st4Query因爲這個循環已經被閉合,沒有辦法再次使用了
ORM映射
- ORM=Object Relationship Database Mapping
對象和關係數據庫的映射
簡單說,一個對象,對應數據庫裏的一條記錄
JDBC DAO
- DAO=DataAccess Object,即數據訪問對象
- 實際上就是運用了ORM中的思路,把數據庫相關的操作都封裝在這個類裏面,其他地方看不到JDBC的代碼
- DAO接口:
package jdbc;
import java.util.List;
import charactor.Hero;
public interface DAO{
//增加
public void add(Hero hero);
//修改
public void update(Hero hero);
//刪除
public void delete(int id);
//獲取
public Hero get(int id);
//查詢
public List<Hero> list();
//分頁查詢
public List<Hero> list(int start, int count);
}
- HeroDAO類,實現接口DAO
- 代碼:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import charactor.Hero;
public class HeroDAO implements DAO{
public HeroDAO() {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root",
"admin");
}
public int getTotal() {
int total = 0;
try (Connection c = getConnection(); Statement s = c.createStatement();) {
String sql = "select count(*) from hero";
ResultSet rs = s.executeQuery(sql);
while (rs.next()) {
total = rs.getInt(1);
}
System.out.println("total:" + total);
} catch (SQLException e) {
e.printStackTrace();
}
return total;
}
public void add(Hero hero) {
String sql = "insert into hero values(null,?,?,?)";
try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
ps.setString(1, hero.name);
ps.setFloat(2, hero.hp);
ps.setInt(3, hero.damage);
ps.execute();
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
int id = rs.getInt(1);
hero.id = id;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public void update(Hero hero) {
String sql = "update hero set name= ?, hp = ? , damage = ? where id = ?";
try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
ps.setString(1, hero.name);
ps.setFloat(2, hero.hp);
ps.setInt(3, hero.damage);
ps.setInt(4, hero.id);
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void delete(int id) {
try (Connection c = getConnection(); Statement s = c.createStatement();) {
String sql = "delete from hero where id = " + id;
s.execute(sql);
} catch (SQLException e) {
e.printStackTrace();
}
}
public Hero get(int id) {
Hero hero = null;
try (Connection c = getConnection(); Statement s = c.createStatement();) {
String sql = "select * from hero where id = " + id;
ResultSet rs = s.executeQuery(sql);
if (rs.next()) {
hero = new Hero();
String name = rs.getString(2);
float hp = rs.getFloat("hp");
int damage = rs.getInt(4);
hero.name = name;
hero.hp = hp;
hero.damage = damage;
hero.id = id;
}
} catch (SQLException e) {
e.printStackTrace();
}
return hero;
}
public List<Hero> list() {
return list(0, Short.MAX_VALUE);
}
public List<Hero> list(int start, int count) {
List<Hero> heros = new ArrayList<Hero>();
String sql = "select * from hero order by id desc limit ?,? ";
try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
ps.setInt(1, start);
ps.setInt(2, count);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Hero hero = new Hero();
int id = rs.getInt(1);
String name = rs.getString(2);
float hp = rs.getFloat("hp");
int damage = rs.getInt(4);
hero.id = id;
hero.name = name;
hero.hp = hp;
hero.damage = damage;
heros.add(hero);
}
} catch (SQLException e) {
e.printStackTrace();
}
return heros;
}
}
數據庫連接池
數據庫連接-傳統方式
- 當有多個線程,每個線程都需要連接數據庫執行SQL語句的話,那麼每個線程都會創建一個連接,並且在使用完畢後,關閉連接。
- 創建連接和關閉連接的過程也是比較消耗時間的,當多線程併發的時候,系統就會變得很卡頓。
- 同時,一個數據庫同時支持的連接總數也是有限的,如果多線程併發量很大,那麼數據庫連接的總數就會被消耗光,後續線程發起的數據庫連接就會失敗
數據庫連接池原理-使用池
- 與傳統方式不同,連接池在使用之前,就會創建好一定數量的連接。如果有任何線程需要使用連接,那麼就從連接池裏面借用,而不是自己重新創建.
- 使用完畢後,又把這個連接歸還給連接池供下一次或者其他線程使用。
- 倘若發生多線程併發情況,連接池裏的連接被借用光了,那麼其他線程就會臨時等待,直到有連接被歸還回來,再繼續使用。
- 整個過程,這些連接都不會被關閉,而是不斷的被循環使用,從而節約了啓動和關閉連接的時間。
ConnectionPool
-
ConnectionPool() 構造方法約定了這個連接池一共有多少連接
-
在init() 初始化方法中,創建了size條連接。 注意,這裏不能使用try-with-resource這種自動關閉連接的方式,因爲連接恰恰需要保持不關閉狀態,供後續循環使用
-
getConnection, 判斷是否爲空,如果是空的就wait等待,否則就借用一條連接出去
-
returnConnection, 在使用完畢後,歸還這個連接到連接池,並且在歸還完畢後,調用notifyAll,通知那些等待的線程,有新的連接可以借用了。
-
代碼實現:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class ConnectionPool {
List<Connection> cs = new ArrayList<Connection>();
int size;
public ConnectionPool(int size) {
this.size = size;
init();
}
public void init() {
//這裏恰恰不能使用try-with-resource的方式,因爲這些連接都需要是"活"的,不要被自動關閉了
try {
Class.forName("com.mysql.jdbc.Driver");
for (int i = 0; i < size; i++) {
Connection c = DriverManager
.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root", "admin");
cs.add(c);
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized Connection getConnection() {
while (cs.isEmpty()) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Connection c = cs.remove(0);
return c;
}
public synchronized void returnConnection(Connection c) {
cs.add(c);
this.notifyAll();
}
}
- 測試類:首先初始化一個有3條連接的數據庫連接池
然後創建100個線程,每個線程都會從連接池中借用連接,並且在借用之後,歸還連接。 拿到連接之後,執行一個耗時1秒的SQL語句。
package jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import jdbc.ConnectionPool;
public class TestConnectionPool {
public static void main(String[] args) {
ConnectionPool cp = new ConnectionPool(3);
for (int i = 0; i < 100; i++) {
new WorkingThread("working thread" + i, cp).start();
}
}
}
class WorkingThread extends Thread {
private ConnectionPool cp;
public WorkingThread(String name, ConnectionPool cp) {
super(name);
this.cp = cp;
}
public void run() {
Connection c = cp.getConnection();
System.out.println(this.getName()+ ":\t 獲取了一根連接,並開始工作" );
try (Statement st = c.createStatement()){
//模擬時耗1秒的數據庫SQL語句
Thread.sleep(1000);
st.execute("select * from hero");
} catch (SQLException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
cp.returnConnection(c);
}
}