JDBC(三)DAO設(shè)計

一、DAO的設(shè)計思想

1.沒有DAO的情況

當應(yīng)用中沒有DAO設(shè)計的時候,會出現(xiàn)什么問題.-->為了解決功能重復(fù)問題.

  • 1.當只有一個客戶端處理數(shù)據(jù)時,如下圖,此時的設(shè)計,在客戶端中編寫了操作JDBC的代碼.這從功能上分析,是沒有問題的.
單客戶端對DB的操作.png
  • 2.但是上面的方法存在功能代碼重復(fù)的問題。因為把功能代碼編寫在客戶端中,此時若有3個客戶端,則功能代碼會重復(fù)3次.效果如下圖:
多客戶端對DB的操作.png
  • 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)的方法即可(如下圖).

數(shù)組的方式處理多客戶端對DB的操作.png

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è)計如下圖:

DAO在邏輯上的關(guān)系.png

上面的設(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).

數(shù)據(jù)對象的形式在DAO中處理.png
學(xué)生對象的DAO操作.png

二、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)該放這里.
包結(jié)構(gòu).png

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放在代碼根目錄下:

Paste_Image.png
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注入的問題.

PrepareStatement的優(yōu)勢.png
防止SQL注入.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容