JDBC in Action
JDBC作為 Java 與關(guān)系型數(shù)據(jù)庫交互的接口红符,在開發(fā)中扮演著十分重要的角色岩四。
Part I. 簡介
JDBC 即 Java Database Connectivity官份, 中文名為 Java 數(shù)據(jù)庫連接, 是由 Sun 公司定義的一組 API 语婴。 各大數(shù)據(jù)庫廠商根據(jù)定義做了具體的實現(xiàn)孕惜,并提供jar 驅(qū)動包給用戶使用,當(dāng)使用 JDBC 進(jìn)行編程時酌壕,執(zhí)行的是 jar 包中的實現(xiàn)類掏愁。
數(shù)據(jù)庫、JDBC卵牍、驅(qū)動果港、Java 代碼的關(guān)系如下圖所示:
java.sql 包提供了 JDBC 的類與接口,主要有以下幾種:
- Driver 接口
- Connection 接口
- Statement 接口
- PreparedStatement 接口
- CallableStatement 接口
- ResultSet 接口
Part II. 快速入門
接下來以 MySQL 為例糊昙,介紹 JDBC 快速入門:
- 導(dǎo)入驅(qū)動 jar 包辛掠,添加到項目的 libs 目錄下;
- 注冊驅(qū)動释牺,MySQL 5 及之后版本的驅(qū)動可省略此步驟萝衩;
- 獲取數(shù)據(jù)庫連接對象 Connection;
- 獲取執(zhí)行 sql 語句的對象 Statement没咙;
- 定義 sql 語句猩谊;
- 執(zhí)行 sql 語句,返回結(jié)果祭刚;
- 處理結(jié)果并釋放資源牌捷。
代碼如下:
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "mysql2018");
String sql = "update account set balance = 500 where id = 1";
Statement stmt = conn.createStatement();
int count = stmt.executeUpdate(sql);
System.out.println(count);
stmt.close();
conn.close();
Part III. 各部分詳解
1. Drivermanager 對象
驅(qū)動管理對象,功能:
-
注冊驅(qū)動(即告知 JDBC 該使用哪個數(shù)據(jù)庫的驅(qū)動 jar 包):
static void registerDriver(Driver driver); Class.forName("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!"); } }
-
獲取數(shù)據(jù)庫的連接
public static Connection getConnection(String url, String user, String password) /** url的格式為 jdbc:mysql://ip地址(域名):端口號/數(shù)據(jù)庫名稱 如果連接的是本機mysql服務(wù)器宜鸯,并且mysql服務(wù)默認(rèn)端口是3306,則url可以簡寫為:jdbc:mysql:///數(shù)據(jù)庫名稱 */
2. Connection 對象
數(shù)據(jù)庫連接對象遮怜,功能:
-
獲取 Statement 對象以執(zhí)行 sql 語句
Statement createStatement(); PreparedStatement prepareStatement(String sql);
-
管理事務(wù)
void setAutoCommit(boolean autoCommit); // true-> 關(guān)閉事務(wù)淋袖;false->開啟事務(wù) void commit(); //提交事務(wù) void rollback(); //回滾事務(wù)
3. Statement 對象
執(zhí)行 sql 語句的對象,功能:
-
執(zhí)行 sql 語句:
boolean execute(String sql); //執(zhí)行任意語句 int executeUpdate(String sql); //執(zhí)行 DML(insert, update, delete) 語句和 DDL(create, alter, drop) 語句锯梁;其返回值為操作所影響的行數(shù)即碗,可用于判斷 sql 執(zhí)行是否成功。 ResultSet executeQuery(String sql); //執(zhí)行 DQL(select) 語句
4. ResultSet 對象
查詢結(jié)果集對象陌凳,對查詢結(jié)果進(jìn)行封裝
boolean next(); //將游標(biāo)向下移動一行剥懒,并判斷當(dāng)前行是否有意義(即是否為最后一行的末尾),返回 true合敦、false
getXXX(); //XXX為返回值類型初橘,如 int getInt(); String getString(); 其參數(shù)類型可以是代表列編號的整形或者字符串類型的列名稱
5. PreparedStatement 對象
執(zhí)行預(yù)編譯的 sql 的對象,可防止 SQL 注入問題并提高效率
String sql = "select * from user where username = ? and password = ?;"; //定義 sql 語句,使用 保檐? 作為占位符
PreparedStatement pstmt = Connection.prepareStatement(String sql); //獲取PrepareStatement對象
pstmr.setString(1,"Tom"); //位置編號(從1開始)耕蝉、參數(shù)值
pstmt.setInt(2,5000);
int count = pstmt.executeUpdate();
Part IV. 定義JDBC工具類
0.獲取配置文件所在路徑
對于 Class.getResource() 和 ClassLoader.getResource() 的淺顯理解:
//若定義了一個 Test 類,則:
Test.class.getResource(""); //返回out目錄中表示src根目錄的URL對象
Test.class.getResource("/"); //返回返回out目錄中Test.class所在的目錄的URL對象
Test.class.getResource(String Filename); //返回out目錄中指向Test.class所在目錄下名為Filename文件或目錄的URL對象
//Test.class.getClassLoader.getResource()方法與上述相同夜只,不過不支持"/"垒在。
//URL.getPath()方法返回絕對路徑的字符串表示
1. 編寫 jdbc.properties 文件
url = jdbc:mysql:///test?useSSL=false
user=root
password=mysql2018
driver=com.mysql.jdbc.Driver
2.編寫 JDBCUtils.java 文件
public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver;
//使用靜態(tài)代碼塊,完成數(shù)據(jù)庫連接參數(shù)的獲取
static{
try {
//1. 創(chuàng)建Properties集合類扔亥。
Properties pro = new Properties();
//獲取src路徑下的文件的方式--->ClassLoader 類加載器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource("jdbc.properties");
String path = res.getPath();
//2. 加載文件
pro.load(new FileReader(path));
//3. 獲取數(shù)據(jù)场躯,賦值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");
//4. 注冊驅(qū)動
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//獲取連接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
//釋放資源
public static void close(Statement stmt,Connection conn){
if( stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//方法重載
public static void close(ResultSet rs,Statement stmt, Connection conn){
if( rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Part V. JDBC 控制事務(wù)
操作:開啟事務(wù)、提交事務(wù)旅挤、回滾事務(wù)
//1.獲取連接
conn = JDBCUtils.getConnection();
//開啟事務(wù)
conn.setAutoCommit(false);
try{
/**
假裝有一些操作
*/
conn.commit();//提交事務(wù)
}catch (Exception e) {
try {
if(conn != null) {
conn.rollback();//事務(wù)回滾
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}
Part VI. 數(shù)據(jù)庫連接池
上文中每次連接數(shù)據(jù)庫都需要加載數(shù)據(jù)庫驅(qū)動踢关、獲取 Connection 對象、獲取 Statement 對象粘茄、執(zhí)行 sql 語句耘成、釋放資源等步驟。而建立數(shù)據(jù)庫連接是一件比較消耗資源和時間的事情驹闰,若使用者較多會給系統(tǒng)造成比較大的負(fù)擔(dān)。一個常見的改良方法是創(chuàng)建一個用來存放數(shù)據(jù)庫連接的容器撒会,容器初始化時會申請一些連接對象嘹朗,用戶對數(shù)據(jù)庫進(jìn)行訪問時會先容器中獲取一個連接對象,用戶訪問完成后會將類诵肛,連接對象歸還給容器屹培。
0.數(shù)據(jù)庫連接池的實現(xiàn)
標(biāo)準(zhǔn)接口:javax.sql 包中的 DataSource 接口。
- getConnection() 獲取連接對象
- Connection.close() 如果 Connection 對象為數(shù)據(jù)庫連接池獲取的怔檩,則調(diào)用 close() 方法可以歸還連接對象褪秀。
數(shù)據(jù)庫連接池的實現(xiàn):由數(shù)據(jù)庫廠商實現(xiàn),目前用用比較廣的是以下兩個:
- C3p0
- Druid (Alibaba)
1.C3p0
//1. 導(dǎo)入C3p0包和數(shù)據(jù)庫驅(qū)動包p薛训;
//2. 定義配置文件媒吗,在 src 目錄下創(chuàng)建c3p0.properties 或 c3p0-config.xml,需要定義數(shù)據(jù)庫連接參數(shù)和連接池的參數(shù)乙埃;
//3. 創(chuàng)建數(shù)據(jù)庫連接池對象闸英,可根據(jù) configName 選擇配置,若空則為默認(rèn)配置介袜;
DataSource ds = new ComboPooledDataSource(String configName);
//4. 獲取連接對象
Connection conn = ds.getConnection();
//5. 釋放連接
conn.close()
2. Druid
//1. 導(dǎo)入 druid 包和數(shù)據(jù)庫驅(qū)動包甫何;
//2. 定義配置文件,形式為 *.properties, 可以放在任意目錄下遇伞;
//3. 加載配置文件到 Properties 對象 pro 中辙喂;
Properties pro = new Properties();
InputStream is = DruidDemo.class.getResourceAsStream("druid.properties");
pro.load(is);
//4. 通過工廠獲取數(shù)據(jù)庫連接池對象
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
//5. 獲取連接對象
Connection conn = ds.getConnection();
//6. 釋放連接
conn.close()
以下的使用方法更為常見:
//1. 定義工具類 JDBCUtils,提供靜態(tài)代碼塊加載配置文件,初始化連接池對象巍耗,并提供獲取連接秋麸、釋放資源等方法。
public class JDBCUtils {
//定義成員變量 DataSource
private static DataSource ds;
static{
try {
//加載配置文件
Properties pro = new Properties(); pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
//獲取DataSource
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
//獲取連接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//釋放資源
public static void close(ResultSet rs , Statement stmt, Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();//歸還連接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//方法重載
public static void close(Statement stmt,Connection conn){
close(null,stmt,conn);
}
//獲取連接池
public static DataSource getDataSource(){
return ds;
}
//2. 使用druid
conn = JDBCUtils.getConnection();
String sql = "insert into account values(null,?,?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"Tom");
pstmt.setDouble(2,3000);
int count = pstmt.executeUpdate();
Part VII. Spring JDBC
Spring JDBC 為 Spring 框架對 JDBC 的簡單封裝芍锦,提供了一個 JDBCTemplate 對象簡化 JDBC 的開發(fā)竹勉。
//1. 導(dǎo)入 jar 包;
//2. 創(chuàng)建 JdbcTemplate 對象娄琉,需要一個 DataSource 對象做參數(shù)次乓;
JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
/**
3. 調(diào)用JdbcTemplate的方法來完成CRUD的操作
update(): 執(zhí)行 DML 語句
queryForMap():查詢結(jié)果將結(jié)果集封裝為 map 集合,將列名作為 key孽水,將值作為 value
queryForList():查詢結(jié)果將結(jié)果集封裝為 list 集合(將每一條記錄封裝為一個Map集合票腰,再將Map集合裝載到List集合中)
query():查詢結(jié)果,將結(jié)果封裝為 JavaBean 對象, query 的參為 RowMapper,一般我們使用BeanPropertyRowMapper 實現(xiàn)類女气,可以完成數(shù)據(jù)到 JavaBean 的自動封裝杏慰,格式為 new BeanPropertyRowMapper<類型>(類型.class)
queryForObject:查詢結(jié)果,將結(jié)果封裝為對象炼鞠,一般用于聚合函數(shù)的查詢
*/
//查詢預(yù)編譯的 sql
String sql = "update account set balance = 5000 where id = ?";
int count = template.update(sql, 3);
//查詢另一個預(yù)編譯的 sql
String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
int count = template.update(sql, 1015, "Tom", 10);
//queryForMap 方法缘滥,將查詢結(jié)果封裝成 Map
String sql = "select * from emp where id = ? or id = ?";
Map<String, Object> map = template.queryForMap(sql, 1001,1002);
//queryForList 方法,將查詢結(jié)果封裝成 Map 再封裝成 List
String sql = "select * from emp";
List<Map<String, Object>> list = template.queryForList(sql);
//以下兩種操作等價
String sql = "select * from emp";
List<Emp> list = template.query(sql, new RowMapper<Emp>() {
@Override
public Emp mapRow(ResultSet rs, int i) throws SQLException {
Emp emp = new Emp();
int id = rs.getInt("id");
String ename = rs.getString("ename");
int job_id = rs.getInt("job_id");
int mgr = rs.getInt("mgr");
Date joindate = rs.getDate("joindate");
double salary = rs.getDouble("salary");
double bonus = rs.getDouble("bonus");
int dept_id = rs.getInt("dept_id");
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
return emp;
}
});
String sql = "select * from emp";
List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
關(guān)于 JDBC 就先這樣吧谒主,以后遇到再補充23333