JDBC學習
1.獲取JDBC的連接對象
1.加載註冊驅動
Class.forName("com.mysql.jdbc.Driver");
當執行這一步時,會把Driver這份字節碼加載進JVM,然後執行該字節碼的靜態代碼塊
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
2.獲取連接
通過DriverManager類的getConnection方法獲取連接對象
@Test
public void test1() throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn =
DriverManager.getConnection("jdbc:mysql:///stu", "root", "password");
Thread.sleep(5000);
}
連接過後出現這樣的錯誤:
java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more
找了資料之後發現意思是 mysqljdbc . driver被棄用了新的驅動類是“com.mysql.cjdbc.driver”。驅動程序通過SPI自動註冊,而手動加載類通常是不必要的,只要將com.mysql.jdbc.Driver 改爲com.mysql.cj.jdbc.Driver 即可。
另一個異常是系統時區出了問題,只要在URL之後加上"?serverTimezone=GMT%2B8"就可以了( GMT%2B8代表東八區)
或者在Navicat的命令行中輸入:set global time_zone='+8:00';
另外,在mysql的my.ini文件中的[mysqld]中加入default-time_zone = '+8:00'語句,再重啓MySQL服務就可以了。
在MySQL命令行再次查看系統時間:show variables like '%time_zone%'; 和 select now();
然後我們用mysql命令行的show processlist 檢測連接。
3.操作JDBC
- 加載註冊驅動
- 獲取連接對象
- 創建語句對象
- 執行SQL語句
- 釋放資源
@Test
public void test() throws Exception {
String sql = "CREATE TABLE test (id bigint(10) PRIMARY KEY AUTO_INCREMENT, name varchar(20), age int(2))";
//1.加載註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接對象
Connection conn =
DriverManager.getConnection("jdbc:mysql:///stu", "root", "password");
//3.創建語句對象
Statement st = conn.createStatement();
//4.執行SQL語句
int row = st.executeUpdate(sql);
st.close();;
conn.close();
System.out.println(row);
}
運行後發現數據庫中就會多出一張表test
4.異常處理
@Test
public void testHandleException() {
String sql = "CREATE TABLE test (id bigint(10) PRIMARY KEY AUTO_INCREMENT, name varchar(20), age int(2))";
//聲明需要關閉的資源
Connection conn = null;
Statement st = null;
try {
//1.加載註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接對象
conn = DriverManager.getConnection("jdbc:mysql:///stu","root","password");
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL語句
st.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.釋放資源
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5.DML操作
仍然使用executeUpdate,沒什麼好說的,再寫一遍熟練一下
@Test
public void testInsert() throws Exception{
String sql = "INSERT INTO test (name,age) values('Bobbui',19)";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///stu","root","password");
Statement st = conn.createStatement();
int row = st.executeUpdate(sql);
System.out.println(row);
st.close();;
conn.close();
}
@Test
public void testUpdate() throws Exception{
String sql = "DELETE FROM test WHERE name='Bobbui'";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///stu","root","password");
Statement st = conn.createStatement();
st.executeUpdate(sql);
st.close();
conn.close();
}
6.DQL操作
有個地方比較有意思,在Java中,只有JDBC和JPA中的索引是從1開始的。
ResultSet可以用迭代器取出對象:
List<String> list = Arrays.asList("A", "B", "C", "D");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
public class DQLTest {
@Test
public void test() throws Exception {
String sql = "SELECT score.Student_id 學號,Student_name 姓名,SUM(Grade) 總分\n" +
"FROM score,student\n" +
"WHERE (score.Student_id=student.Student_id) GROUP BY student.Student_name";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
//處理結果集
while (rs.next()) {
String stuNum = rs.getString("學號");
String stuName = rs.getString("姓名");
int stuScore = rs.getInt("總分");
System.out.println(stuNum + "," + stuName + "," + stuScore);
}
//關閉資源
rs.close();
st.close();
conn.close();
}
}
得到的結果爲:
7.DAO介紹
DAO(Data Access Object):數據存取對象,位於業務邏輯和持久化數據之間,能夠實現對持久化數據的訪問
DAO在實體類與數據庫之間起着轉換器的作用,能夠把實體類轉換爲數據庫中的記錄
DAO模式是作用:
- 隔離業務邏輯代碼和數據訪問代碼
- 隔離不同數據庫的實現
DAO模式的組成部分:
- DAO接口
- DAO實現類
- 實體類
- 數據庫連接和關閉工具類
分層開發:
一種化大爲小,分而治之的軟件開發方法
分層的特點:
1.每一層都有自己的職責
2.上層不用關心下次的實現細節,上層通過下層提供的對外接口來使用其功能
3.上一層調用下一層的功能,下一層不能調用上一層的功能
分層開發的好處:
1.各層專注於自己功能的實現,便於提高質量
2.便於分工協作,提高開發效率
3.便於代碼複用
4.便於程序擴展
分層原則:
封裝性原則
每個層次向外公開接口,但是隱藏內部細節
順序訪問原則
下一層爲上一層服務,但不使用上層的服務
分層結構中,不同層之間通過實體類傳輸數據
8.DAO設計
DAO組件
- DAO接口
- DAO實現類
- DAO測試類
包名規範
package 域名倒寫.模塊名稱.組件名稱
package com.Bryan.smis.domain 存儲所有的domain類
package com.Bryan.smis.dao 存儲所有的DAO接口
package com.Bryan.smis.dao.impl 存儲所有的DAO接口的實現類
package com.Bryan.smis.test 存儲DAO組件的測試類
Student類:
public class Student {
private String id;
private String name;
private Integer age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", age=" + age + '}';
}
}
IStudentDAO接口:
//封裝Student對象的CRUD操作
public interface IStudentDAO {
/**
* 保存操作
* @param stu 學生對象
*/
void save(Student stu);
/**
* 刪除操作
* @param id 被刪除學生的主鍵值
*/
void delete(String id);
/**
* 更新操作
* @param id 被查詢學生的主鍵值
* @param newStu 如果id存在,返回該學生對象,否則返回null
*/
void update(String id,Student newStu);
/**
* 查詢指定Id的學生對象
* @param id 被查詢學生的主鍵值
* @return 如果id存在,返回該學生對象,否則返回null
*/
Student get(String id);
/**
* 查詢並返回所有學生對象
* @return 如果結果集爲空,則返回null
*/
List<Student> listAll();
}
StudentDAOImpl類:
public class StudentDAOImpl implements IStudentDAO {
public void save(Student stu) {
//String sql = "INSERT INTO student (Student_id,sex,Student_name,age) VALUES ('20178013','M','Cherry',19)";
StringBuilder sb = new StringBuilder(80);
sb.append("INSERT INTO student (Student_id,sex,Student_name,age) VALUES ('");
sb.append(stu.getId());
sb.append("','");
sb.append(stu.getSex());
sb.append("','");
sb.append(stu.getName());
sb.append("',");
sb.append(stu.getAge());
sb.append(")");
//System.out.println(sb);
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加載註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接對象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//DELETE FROM student WHERE Student_id = '20178013'
public void delete(String id) {
StringBuilder sb = new StringBuilder(80);
sb.append("DELETE FROM student WHERE Student_id = '");
sb.append(id);
sb.append("'");
//System.out.println(sb);
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加載註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接對象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//UPDATE student SET Student_name = 'XXX',age = 12 WHERE Student_id = '20178013'
public void update(String id, Student newStu) {
StringBuilder sb = new StringBuilder(80);
sb.append("UPDATE student SET Student_name = '");
sb.append(newStu.getName());
sb.append("',age = ");
sb.append(newStu.getAge());
sb.append(" WHERE Student_id = '");
sb.append(id);
sb.append("'");
//System.out.println(sb);
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加載註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接對象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public Student get(String id) {
String sql = "SELECT * FROM student WHERE Student_id = " + id;
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加載註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接對象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
rs = st.executeQuery(sql);
//5.處理結果集
//--------------------------------
if (rs.next()) {
Student stu = new Student();
//獲取當前光標所在的行的列值,並設置到stu中
stu.setAge(rs.getInt("age"));
stu.setId(rs.getString("Student_id"));
stu.setName(rs.getString("Student_name"));
return stu;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return null;
}
public List<Student> listAll() {
List<Student> list = new ArrayList<>();
String sql = "SELECT * FROM student";
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加載註冊驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.獲取連接對象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
rs = st.executeQuery(sql);
//5.處理結果集
//--------------------------------
while (rs.next()) {
Student stu = new Student();
//獲取當前光標所在的行的列值,並設置到stu中
stu.setAge(rs.getInt("age"));
stu.setId(rs.getString("Student_id"));
stu.setName(rs.getString("Student_name"));
list.add(stu);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return list;
}
}
StudentDAOTest類:
public class StudentDAOTest {
//依賴DAO對象
private IStudentDAO dao = new StudentDAOImpl();
@Test
public void testSave() {
Student stu = new Student();
stu.setName("Dans");
stu.setAge(19);
dao.save(stu);
}
@Test
public void testDelete() {
dao.delete("20178013");
}
@Test
public void testUpdate() {
Student stu = new Student();
stu.setName("lrh");
stu.setSex("F");
stu.setAge(18);
dao.update("20178013",stu);
}
@Test
public void testGet() {
Student stu = dao.get("20010103");
System.out.println(stu);
}
@Test
public void testListAll() {
List<Student> stus = dao.listAll();
for (Student stu : stus) {
System.out.println(stu);
}
}
}
到現在爲止,已經完成了DAO模式的封裝,但是我們發現在StudentDAOImpl中重複代碼太多,不符合軟件代碼的高內聚低耦合原則,下面我們進行重構。
9.重構設計
重構(Refactoring)就是通過調整程序代碼,改善軟件的質量、性能,使其程序的設計模式和架構更加合理,提高軟件的擴展性和維護性。
抽取出JDBCUtil類
我們發現連接數據庫的幾行代碼每次都要重寫一遍,因此考慮將其改成成員變量,但是當DAO太多的時候,這樣也會造成代碼冗餘,因此我們考慮封裝成一個Util類。
/**
* JDBC工具類
*/
public class JdbcUtil {
//連接數據庫的四要素
public static String driverClassName = "com.mysql.cj.jdbc.Driver";
public static String url = "jdbc:mysql:///studentinfo";
public static String userName = "root";
public static String password = "password";
}
//---------------------------------------------------------
//1.加載註冊驅動
Class.forName(JdbcUtil.driverClassName);
//2.獲取連接對象
conn = DriverManager.getConnection(JdbcUtil.url, JdbcUtil.userName, JdbcUtil.password);
但是考慮到封裝的Util字段爲公有,安全性不高。因此我們考慮將創建Connection對象也封裝到Util類。然而,每次都需要重新註冊驅動,沒有必要,因此我們在JdbcUtil中的靜態代碼塊中進行驅動註冊,並將關閉資源進行封裝。
/**
* JDBC工具類
*/
public class JdbcUtil {
//連接數據庫的四要素
private static String driverClassName = "com.mysql.cj.jdbc.Driver";
private static String url = "jdbc:mysql:///studentinfo";
private static String userName = "root";
private static String password = "password";
//靜態代碼塊,當JdbcUtil加載進JVM就開始執行
static {
try {
Class.forName(driverClassName);
} catch (Exception e) {
e.printStackTrace();
}
}
//創建並返回一個Connection對象
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, userName, password);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//釋放資源
public static void close(Connection conn, Statement st, ResultSet rs) {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
封裝完的StudentDAOImpl類如下:
public class StudentDAOImpl implements IStudentDAO {
public void save(Student stu) {
//String sql = "INSERT INTO student (Student_id,sex,Student_name,age) VALUES ('20178013','M','Cherry',19)";
StringBuilder sb = new StringBuilder(80);
sb.append("INSERT INTO student (Student_id,sex,Student_name,age) VALUES ('");
sb.append(stu.getId());
sb.append("','");
sb.append(stu.getSex());
sb.append("','");
sb.append(stu.getName());
sb.append("',");
sb.append(stu.getAge());
sb.append(")");
//System.out.println(sb);
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, null);
}
}
//DELETE FROM student WHERE Student_id = '20178013'
public void delete(String id) {
StringBuilder sb = new StringBuilder(80);
sb.append("DELETE FROM student WHERE Student_id = '");
sb.append(id);
sb.append("'");
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, null);
}
}
//UPDATE student SET Student_name = 'XXX',age = 12 WHERE Student_id = '20178013'
public void update(String id, Student newStu) {
StringBuilder sb = new StringBuilder(80);
sb.append("UPDATE student SET Student_name = '");
sb.append(newStu.getName());
sb.append("',age = ");
sb.append(newStu.getAge());
sb.append(" WHERE Student_id = '");
sb.append(id);
sb.append("'");
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, null);
}
}
public Student get(String id) {
String sql = "SELECT * FROM student WHERE Student_id = " + id;
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
rs = st.executeQuery(sql);
//5.處理結果集
//--------------------------------
if (rs.next()) {
Student stu = new Student();
//獲取當前光標所在的行的列值,並設置到stu中
stu.setAge(rs.getInt("age"));
stu.setId(rs.getString("Student_id"));
stu.setName(rs.getString("Student_name"));
return stu;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, rs);
}
return null;
}
public List<Student> listAll() {
List<Student> list = new ArrayList<>();
String sql = "SELECT * FROM student";
//聲明資源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.創建語句對象
st = conn.createStatement();
//4.執行SQL
rs = st.executeQuery(sql);
//5.處理結果集
//--------------------------------
while (rs.next()) {
Student stu = new Student();
//獲取當前光標所在的行的列值,並設置到stu中
stu.setAge(rs.getInt("age"));
stu.setId(rs.getString("Student_id"));
stu.setName(rs.getString("Student_name"));
list.add(stu);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, rs);
}
return list;
}
}
這樣看上去代碼更加簡潔了。
但是,這些還是可以繼續進行封裝。
抽取db.properties
在JdbcUtil中存在硬編碼,還是不利於維護,我們可以將配置信息專門提取到配置文件中去,因此,我們抽取出了db.properties文件,存放於source folder目錄中:
修改後的JdbcUtil一部分:
private static Properties p = new Properties();
//靜態代碼塊,當JdbcUtil加載進JVM就開始執行
static {
try {
//加載和讀取db.properties
InputStream inStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("db.properties");
p.load(inStream);
Class.forName(p.getProperty("driverClassName"));
} catch (Exception e) {
e.printStackTrace();
}
}
//創建並返回一個Connection對象
public static Connection getConnection() {
try {
return DriverManager.getConnection(
p.getProperty("url"),
p.getProperty("username"),
p.getProperty("password"));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
但是SQL語句看上去還是過於冗餘,而且用字符串拼接來進行查詢,不易於查詢和修改。因此我們使用PreparedStatement(預編譯語句對象)。
讓我們深入分析該Demo,發現在DAO方法中,每次都新創建一個Connection,然後關閉,所消耗的資源非常大。所以我們考慮可以重複利用Connection對象,解決方案:數據庫連接池(DataSource)。
對於DML(增刪改)操作來說,代碼模板其實是相同的,DQL(查詢)操作代碼模板其實也是相同的,我們考慮將其重構。因此我們重構出Jdbc Template類,封裝DML和DQL操作的通用模板。
我們發現SQL語句是很麻煩的,我們可以考慮不寫SQL語句而能操作數據庫。因此我們考慮模擬Hibernate框架。
具體內容請關注JAVA學習之DAO優化。