Java之路(十):JDBC及DAO

 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

  1. 加載註冊驅動
  2. 獲取連接對象
  3. 創建語句對象
  4. 執行SQL語句
  5. 釋放資源
    @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();
    }
}

得到的結果爲:

Java運行結果

 

MySQL運行結果

7.DAO介紹

DAO(Data Access Object):數據存取對象,位於業務邏輯和持久化數據之間,能夠實現對持久化數據的訪問

DAO在實體類與數據庫之間起着轉換器的作用,能夠把實體類轉換爲數據庫中的記錄

DAO模式是作用:

  1. 隔離業務邏輯代碼和數據訪問代碼
  2. 隔離不同數據庫的實現

DAO模式的組成部分:

  1. DAO接口
  2. DAO實現類
  3. 實體類
  4. 數據庫連接和關閉工具類

分層開發:

一種化大爲小,分而治之的軟件開發方法

分層的特點:

     1.每一層都有自己的職責

     2.上層不用關心下次的實現細節,上層通過下層提供的對外接口來使用其功能

     3.上一層調用下一層的功能,下一層不能調用上一層的功能

分層開發的好處:

     1.各層專注於自己功能的實現,便於提高質量

     2.便於分工協作,提高開發效率

     3.便於代碼複用

     4.便於程序擴展

分層原則:

    封裝性原則

       每個層次向外公開接口,但是隱藏內部細節

    順序訪問原則

       下一層爲上一層服務,但不使用上層的服務

分層結構中,不同層之間通過實體類傳輸數據

8.DAO設計

DAO組件

  1. DAO接口
  2. DAO實現類
  3. 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優化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章