一、DAO的設(shè)計思想
1.沒有DAO的情況
當應(yīng)用中沒有DAO設(shè)計的時候,會出現(xiàn)什么問題.-->為了解決功能重復(fù)問題.
- 1.當只有一個客戶端處理數(shù)據(jù)時,如下圖,此時的設(shè)計,在客戶端中編寫了操作JDBC的代碼.這從功能上分析,是沒有問題的.

- 2.但是上面的方法存在功能代碼重復(fù)的問題。因為把功能代碼編寫在客戶端中,此時若有3個客戶端,則功能代碼會重復(fù)3次.效果如下圖:

- 3.該問題的解決方案,我們從之前的開發(fā)經(jīng)驗入手去分析:
數(shù)組 :存儲數(shù)據(jù),把數(shù)據(jù)存儲到內(nèi)存中.
數(shù)據(jù)庫:存儲數(shù)據(jù),把數(shù)據(jù)存儲到磁盤中.
數(shù)組的相關(guān)操作:
1):把數(shù)據(jù)存儲到數(shù)組.
2):取出數(shù)組中的數(shù)據(jù).
3):修改數(shù)組中的數(shù)據(jù).
4):刪除數(shù)組中的數(shù)據(jù).
此時,也是在每一個客戶端中編寫代碼,如此一來,在每一個客戶端中代碼依然重復(fù).編寫一個數(shù)組的工具類:ArrayList.把所有和數(shù)組相關(guān)的CRUD操作封裝在ArrayList類中.以后,客戶端需要什么功能,就只需要創(chuàng)建ArrayList對象,并調(diào)用相應(yīng)的方法即可(如下圖).

2.DAO的介紹
DAO(Data Access Object)是一個數(shù)據(jù)訪問接口,數(shù)據(jù)訪問:顧名思義就是與數(shù)據(jù)庫打交道。夾在業(yè)務(wù)邏輯與數(shù)據(jù)庫資源中間。
在核心J2EE模式中是這樣介紹DAO模式的:為了建立一個健壯的J2EE應(yīng)用,應(yīng)該將所有對數(shù)據(jù)源的訪問操作抽象封裝在一個公共API中。
用程序設(shè)計的語言來說,就是建立一個接口,接口中定義了此應(yīng)用程序中將會用到的所有事務(wù)方法。在這個應(yīng)用程序中,當需要和數(shù)據(jù)源進行交互的時候則使用這個接口,并且編寫一個單獨的類來實現(xiàn)這個接口在邏輯上對應(yīng)這個特定的數(shù)據(jù)存儲。
DAO中的主要操作:增刪改查(CRUD).引入DAO之后,此時設(shè)計如下圖:

上面的設(shè)計圖是使用了DAO思想之后的設(shè)計,
從功能的正確與否的角度分析沒有問題,但是考慮設(shè)計save和get方法:
public void save(String name,Integer age){}
若參數(shù)過多,此時save方法的參數(shù)就會出現(xiàn)爆炸式增長.
public XXX get(Long id){}
此時查詢指定ID的某一個學(xué)生信息,問題:XXX表示什么類型.
此時:若XXX表示String,就只能查詢學(xué)生的名字.
若XXX表示Integer,就只能查詢學(xué)生的名字.
那如果,我同時想得到學(xué)生的名字和年齡,怎么辦?????
解決方案:把學(xué)生的信息封裝成一個對象(Student).


二、DAO的規(guī)范
DAO其實是一個組件(可以重復(fù)使用),包括:
1.分包規(guī)范:
域名倒寫.項目模塊名.組件;
com.song.pss.domain; //裝pss模塊的domain類,模型對象.
com.song.pss.dao; //裝pss模塊的dao接口.
com.song.pss.dao.impl;//裝pss模塊的dao接口的實現(xiàn)類.
com.song.pss.test; //暫時存儲DAO的測試類,以后的測試類不應(yīng)該放這里.

2.聲明類
dao對象的名字:xxxDAO,比如:employeeDAO/employeeDao,
- Domain數(shù)據(jù)對象:以下的,Xxx都表示一個對象比如Employee,Department.
- DAO 接口: IXxxDAO/IXxxDao, IEmployeeDAO/IEmployeeDao:僅僅是表示對Employee對象的CRUD的封裝
- DAO實現(xiàn)類: XxxDAOImpl/XxxDaoImpl,EmployeeDAOImpl/EmployeeDaoImpl:僅僅表示IEmployeeDAO的實現(xiàn).
- DAO測試類: XxxDAOTest:表名就是XxxDAO組件的測試類,應(yīng)該測試DAO組件中的所有方法.
開發(fā)建議:面向接口編程,聲明DAO對象:
(1) 傳統(tǒng)的做法 :EmployeeDAOImpl dao = new EmployeeDAOImpl();
(2) 面向接口編程:IEmployeeDAO dao = new EmployeeDAOImpl();
把實現(xiàn)類賦給接口類型,體現(xiàn)多態(tài)的特性:可以屏蔽不同子類之間實現(xiàn)的差異.
public void show(List list){}
3.一般開發(fā)的順序:
1):先建立模型對象:domain
2):編寫DAO接口.
3):定義DAO實現(xiàn)類.
4):生產(chǎn)DAO測試類.
5):實現(xiàn)DAO實現(xiàn)類.
6):在DAO測試類中測試DAO方法.
4.調(diào)用:
show(new ArrayList()); //YES
show(new LinkedList()); //YES
三、DAO的實現(xiàn)
Student
public class Student {
private Long id;
private String name;
private int age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- IStudentDAO
public interface IStudentDAO {
/**
* 保存指定學(xué)生對象
* @param stu
*/
public void save(Student stu);
/**
* 更新指定id的學(xué)生對象
* @param stu
*/
public void update(Student stu);
/**
* 刪除指定id的學(xué)生
* @param id
*/
public void delete(Long id);
/**
* 獲取指定id學(xué)生的對象
* @param id
* @return
*/
public Student getSingle(Long id);
/**
* 獲取全部學(xué)生對象
* @return
*/
public List<Student> list();
}
- StudentDAOImpl
public class StudentDAOImpl implements IStudentDAO {
public void save(Student stu) {
StringBuilder sb = new StringBuilder(100);
sb.append("INSERT INTO t_student(name,age) VALUES(");
sb.append("'").append(stu.getName()).append("'");
sb.append(",").append(stu.getAge()).append(")");
String sql = sb.toString();
Connection conn = null;
Statement st = null;
try {
// 加載驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 獲取連接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 獲取語句對象
st = conn.createStatement();
// 執(zhí)行
System.out.println("save:" + sql);
st.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放資源
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
public void update(Student stu) {
StringBuilder sb = new StringBuilder(100);
sb.append("UPDATE t_student SET name =");
sb.append("'").append(stu.getName()).append("'");
sb.append(",age = ").append(stu.getAge());
sb.append(" WHERE id = ").append(stu.getId());
String sql = sb.toString();
Connection conn = null;
Statement st = null;
try {
// 加載驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 獲取連接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 獲取語句對象
st = conn.createStatement();
// 執(zhí)行
System.out.println("update:" + sql);
st.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放資源
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
public void delete(Long id) {
String sql = "DELETE t_student WHERE id = " + id;
Connection conn = null;
Statement st = null;
try {
// 加載驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 獲取連接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 獲取語句對象
st = conn.createStatement();
// 執(zhí)行
System.out.println("delete:" + sql);
st.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放資源
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
public Student getSingle(Long id) {
String sql = "SELECT * FROM t_student WHERE id = " + id;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 加載驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 獲取連接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 獲取語句對象
st = conn.createStatement();
// 執(zhí)行
System.out.println("getSingle:" + sql);
rs = st.executeQuery(sql);
if (rs.next()) {
Student stu = new Student();
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
stu.setId(rs.getLong("id"));
return stu;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放資源
try {
if (rs != null) {
rs.close();
}
} catch (Exception e2) {
e2.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
return null;
}
public List<Student> list() {
String sql = "SELECT * FROM t_student";
List<Student> list = new ArrayList<Student>();
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 加載驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 獲取連接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 獲取語句對象
st = conn.createStatement();
// 執(zhí)行
rs = st.executeQuery(sql);
while (rs.next()) {
Student stu = new Student();
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
stu.setId(rs.getLong("id"));
list.add(stu);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放資源
try {
if (rs != null) {
rs.close();
}
} catch (Exception e2) {
e2.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
return null;
}
}
四、重構(gòu)設(shè)計
1.問題1:在DAO組件中每一個DAO方法,都得加載注冊驅(qū)動,都得獲取Connection對象.
存在問題:
每一個DAO方法的代碼都重復(fù)了.如果要切換MySQL為Oracle,就得修改每一個DAO方法的代碼.
// 加載驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 獲取連接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
解決方案:
把DAO方法中的這四個連接數(shù)據(jù)庫的基本信息(驅(qū)動名,url,賬號,密碼)作為DAO類的成員變量.
private String driverClassName="com.mysql.jdbc.Driver";
private String url="jdbc:mysql:///jarry";
private String userName="root";
private String password="123456";
提取之后,DAO方法的代碼,只需要直接引用成員變量即可:
// 加載驅(qū)動
Class.forName(driverClassName);
// 獲取連接
conn = DriverManager.getConnection(url, userName, password);
2.問題2:問題1的解決方案,已經(jīng)把四個連接數(shù)據(jù)庫的基本信息(驅(qū)動名,url,賬號,密碼)作為DAO類的成員變量.
這本身是沒有問題的,但是:在實際開發(fā)中,往往不止一個DAO類,難道每一個DAO實現(xiàn)類都得編寫.
解決方案:
定義一個Jdbc的工具類:JdbcUtil,把上述四行代碼定義在JdbcUtil類中,然后各個DAO類直接調(diào)用即可.
public class JdbcUtil {
public static String driverClassName="com.mysql.jdbc.Driver";
public static String url="jdbc:mysql:///jarry";
public static String userName="root";
public static String password="123456";
}
此時在DAO方法中的調(diào)用代碼如下:
// 加載驅(qū)動
Class.forName(JdbcUtil.driverClassName);
// 獲取連接
conn = DriverManager.getConnection(JdbcUtil.url, JdbcUtil.userName, JdbcUtil.password);
3.問題3:問題2在JdbcUtil中的使用public來修飾變量,破壞封裝了.
在問題2:其實每一個DAO方法,只需要獲取Connection對象即可,是不需要關(guān)系如何創(chuàng)建Connection的.
解決方案:
在JdbcUtil類中向外暴露一個getConn方法,用于向調(diào)用者返回Connection對象.
public class JdbcUtil {
private static String driverClassName="com.mysql.jdbc.Driver";
private static String url="jdbc:mysql:///jarry";
private static String userName="root";
private static String password="123456";
public static Connection getConn(){
Connection conn = null;
try {
// 獲取連接
Class.forName(JdbcUtil.driverClassName);
// 加載驅(qū)動
conn = DriverManager.getConnection(url, userName, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
此時在DAO方法中的調(diào)用代碼如下:
// 獲取連接
conn = JdbcUtil.getConn();
4.在DAO方法中都會調(diào)用JdbcUtil.getConn()方法,但是在getConn方法底層,每次調(diào)用都會重新加載注冊一次驅(qū)動,而這是沒有必要的.
解決方案:如何保證加載注冊驅(qū)動只會執(zhí)行一次?把加載注冊驅(qū)動代碼,存放到JdbcUtil的靜態(tài)代碼塊中。
public class JdbcUtil {
private static String driverClassName="com.mysql.jdbc.Driver";
private static String url="jdbc:mysql:///jarry";
private static String userName="root";
private static String password="123456";
public JdbcUtil() {
//在JdbcUtil的字節(jié)碼被加載進JVM就執(zhí)行,只是執(zhí)行一次
try {
// 獲取連接
Class.forName(JdbcUtil.driverClassName);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConn(){
Connection conn = null;
try {
// 加載驅(qū)動
conn = DriverManager.getConnection(url, userName, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
5.問題5:每一個DAO方法都得關(guān)閉資源(沒有任何技術(shù)含量,但是又必須得寫).
解決方案:在JdbcUtil類中定義close方法,專門用于關(guān)閉資源.
此時在DAO方法中的調(diào)用代碼如下:
JdbcUtil.close(conn, st, rs); // 如果是查詢方法需要關(guān)閉資源
JdbcUtil.close(conn, st, null); // 如果是增刪改方法需要關(guān)閉資源
6.問題6:在JdbcUtil類中,定義了連接數(shù)據(jù)庫的四個基本信息,雖然即使以后需要切換數(shù)據(jù)庫,只需要修改一次.
但是,需要修改源代碼(因為我們在代碼中寫死了信息(硬編碼)).
解決方案:把數(shù)據(jù)庫的連接信息抽取到一個配置文件(properties/xml)中去.
db.properties放在代碼根目錄下:

drvierClassName = com.mysql.jdbc.Driver
url=jdbc:mysql:///jdbcdemo
username=root
password=admin
新的問題:如何在Java代碼中讀取db.properties文件,并獲取其中的信息.
以后,實施人員需要修改數(shù)據(jù)庫的連接信息,只需要修改db.properties文件即可.
public class JdbcUtil {
private static Properties properties=new Properties();
static {
//在JdbcUtil的字節(jié)碼被加載進JVM就執(zhí)行,只是執(zhí)行一次
try {
InputStream inStream=Thread.currentThread().getContextClassLoader()
.getResourceAsStream("db.properties");
properties.load(inStream);
Class.forName(properties.getProperty("driverClassName"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConn(){
Connection conn = null;
try {
// 加載驅(qū)動
conn = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("userName"), properties.getProperty("password"));
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
7.問題7:在DAO方法中拼接SQL,太惡心了.
解決方案:預(yù)編譯語句對象來解決.
8.問題8:此時,每一個Connection對象,獲取之后只使用一次就關(guān)閉了(獲取連接/關(guān)閉連接比較耗性能),沒有充分利用Connection對象.
解決方案:連接池思想.
9.問題9:在DAO實現(xiàn)類中,增刪改操作的代碼模板相同,查詢操作代碼模板相同,存在了相同的代碼結(jié)構(gòu).
解決方案:JdbcTemplate.
10.問題10:我不想拼SQL,拼SQL不爽,能不能一行代碼就搞定操作呢.
解決方案:期待Hibernate框架.
五.預(yù)編譯語句對象-PreparedStatement
1.PreparedStatement的實現(xiàn)
public class PreParedStatementTest extends TestCase{
//statement
public void testStatement() throws SQLException{
String sql="INSERT INTO t_student (name,age) VALUES ('marry',18)";
Connection conn=JdbcUtil.getConn();
Statement st = st=conn.createStatement();
st.execute(sql);
JdbcUtil.close(conn, st, null);
}
//preparedStatement
public void testPreparedStatement() throws SQLException{
String sql="INSERT INTO t_student (name,age) VALUES (?,?)";
Connection conn=JdbcUtil.getConn();
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "kity");
ps.setInt(2, 23);
ps.executeUpdate();
JdbcUtil.close(conn, ps, null);
}
}
2.Statement和PreparedStatement的區(qū)別:
PreparedStatement存在的優(yōu)勢:
1):更好的可讀性,可維護性.
2):可以提供更好的性能(預(yù)編譯).MySQL不支持PreparedStatement性能優(yōu)化.
3):更安全,可以防止SQL注入的問題.

