JDBC、JdbcTemplate、SpringData對比學習

SpringData入門以及爲什麼學習SpringData

寫在最前

學習一項新的技術,一個新的框架,總是要基於某個問題去學習,而不能是爲了學框架而學框架,這樣不僅學習過程痛苦,學習成效也不高。俗話說,熟能生巧,大概說的是使用的多了就用起來就順手了的意思,如果單純是爲了學習而學習,而沒有去實踐它,學了也會很快就忘記。所以,在這個入門,我會從最簡單的jdbc,到使用Spring的JdbcTemplate再到SpringData,一步步分析各自的優缺點,明明都可以做一樣的工作,爲什麼要選SpringData而不是原生jdbc或者JdbcTemplate?原生JDBC和JdbcTemplate我們略微介紹,重點放在SpringData


正文

一 .原生JDBC

在原生的JDBC開發中,我們通常會先定義一個DAO接口,這裏我們只舉一個例子查詢所有學生

public interface StudentDAO {

    /**
     * 查詢所有學生
     * @return 所有學生
     */
    public List<Student> query();

}

實現StudentDAO接口:

public class StudentDAOImpl implements  StudentDAO{

    @Override
    public List<Student> query() {

        List<Student> students = new ArrayList<Student>();

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        String sql = "select id, name , age from student";
        try {
            connection = JDBCUtil.getConnection();//這裏我們把獲取連接操作寫在一個工具類中,方便調用
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();

            Student student = null;
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");

                student = new Student();
                student.setId(id);
                student.setName(name);
                student.setAge(age);

                students.add(student);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.release(resultSet,preparedStatement,connection);//關閉操作也寫在工具類中,方便調用
        }
        return students;
    }
}

如果只有這個方法可能看不出什麼問題,但問題就在於,我們的應用系統通常會涉及很多對數據庫的CRUD操作,如果你使用JDBC,你會發現你大部分的時間是用於複製一份已有的代碼,然後對sql語句以及結果集進行修改之類的操作而已,並且,這裏的獲得連接以及關閉連接封裝在一個工具類中,如果是直接寫在方法體內,你可能會發現,這個方法裏50行代碼核心只有5行。

這就暴露出一個問題,代碼過於冗餘,另外值得一提的是,原生的JDBC連接使用完需要手工關閉,如果沒有關閉會導致數據庫連接過多,而每次獲取連接和關閉連接會加大數據庫的負擔

然而優秀的程序員都是“懶”的,他們會想:我怎麼能把時間花在copy一些冗餘的代碼上呢?這些冗餘的代碼基本上都是一樣的,能不能用一個模板把它們封裝起來,我需要寫核心代碼就行呢?沒錯,這就是接下來要講的Spring的JdbcTemplate

二. JdbcTemplate

第一步,還是先定義DAO接口,這裏我們使用上面定義的接口,就不重複貼代碼上來了。

這裏還是實現DAO接口:

/**
 * StudentDAO訪問接口實現類:通過Spring jdbc的方式操作
 */
public class StudentDAOSpringJdbcImpl implements  StudentDAO{

    private JdbcTemplate jdbcTemplate;//使用SpringIoc注入

    public List<Student> query() {
        final List<Student> students = new ArrayList<Student>();
        String sql = "select id, name , age from student";

        jdbcTemplate.query(sql, new RowCallbackHandler(){
        //RowCallbackHandler是一個接口,這裏使用內部類的方式實現接口
            @Override
            public void processRow(ResultSet rs) throws SQLException {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");

                Student student = new Student();
                student.setId(id);
                student.setName(name);
                student.setAge(age);

                students.add(student);
            }
        });

        return students;
    }
}

這裏解釋一下這個接口:接口中的processRow方法參數是一個ResultSet,也就是一個結果集,我們可以定製自己對結果集的操作。

我們可以清楚的看到,相對於原生的JDBC,很多冗餘的代碼都不見了,代碼也清爽了許多。這貌似看起來已經很不錯了,然後程序員是真的很“懶”呀,他們又想:我們拿到結果集之後還要自己一個個將值設置到對象中,這個機械化的操作,能不能也有類似模板的東西幫我們去做?答案是可以的,結果集直接映射成對應的實體類,我們拿到的結果就是若干個實體類,省去了手動設值的過程,也就是ORM(Object Relational Mapping,對象關係映射)

三. SpringData

先看看什麼是SpringData?

Spring Data 項目的目的是爲了簡化構建基於 Spring 框架應用的數據訪問計數,包括非關係數據庫、Map-Reduce 框架、雲數據服務等等;另外也包含對關係數據庫的訪問支持。
Spring Data 包含多個子項目:

  • Commons - 提供共享的基礎框架,適合各個子項目使用,支持跨數據庫持久化
  • Hadoop - 基於 Spring 的 Hadoop 作業配置和一個 POJO 編程模型的 MapReduce 作業
  • Key-Value - 集成了 Redis 和 Riak ,提供多個常用場景下的簡單封裝
  • Document - 集成文檔數據庫:CouchDB 和 MongoDB 並提供基本的配置映射和資料庫支持
  • Graph - 集成 Neo4j 提供強大的基於 POJO 的編程模型
  • Graph Roo AddOn - Roo support for Neo4j
  • JDBC Extensions - 支持 Oracle RAD、高級隊列和高級數據類型
  • JPA - 簡化創建 JPA 數據訪問層和跨存儲的持久層功能
  • Mapping - 基於 Grails 的提供對象映射框架,支持不同的數據庫
  • Examples - 示例程序、文檔和圖數據庫
  • Guidance - 高級文檔

說了這麼多,SpringData就是給我們提供了一種通用的代碼格式,統一數據訪問API。並且對於不同的數據庫有很好的支持,不僅是關係型數據庫,非關係型數據庫也可以,在我看來SpringData是站在一個高的層次去統一API,底層可以是不同廠商的實現。可以參考下圖

首先我們做一個簡單的DEMO,我們還是定義一個接口,注意這裏我們繼承了一個Repository接口,具體含義我後面會解釋

public interface StudentRepository extends Repository<Student,Integer>{
    public Student findByName(String name);
}

然後我們就可以用了!!你看到這裏可能會想,定義一個接口都沒具體實現就可以使用,開玩笑吧?然而真的不需要我們去實現它,就可以使用了,當然這是有前提的,你的方法名需要按照一定命名規範。

現在我們解釋一下爲什麼這麼神奇,首先,Repository接口中第一個類型是你要映射的實體類類型,第二個類型是主鍵的類型,繼承之後框架就能幫你實現映射功能,源碼我們就不深入了,暫時知道是這樣子就可以了,另外還可以使用註解的方式,就不用繼承接口了。。

@RepositoryDefinition(domainClass = Student.class, idClass = Integer.class)
public interface StudentRepository {
    public Student findByName(String name);
}

而關於方法的命名規範,由於約定由於配置的思想,SpringData框架可以根據你的方法名,自動生成對應的SQL語句並執行,命名規範可以在官網中找到,搜索引擎搜一個也能找到,這裏給出規則表

我們可以根據自己的SQL語句,編寫對應的方法,但又有一個問題,對於簡單的查詢還能接受,如果是一些很複雜的查詢,那麼方法名就會變得很長很長,讀起來很辛苦怎麼辦?SpringData也考慮到這個問題,我們可以通過註解的方式,將自己寫的SQL語句傳進去,方法名就自由啦。

@RepositoryDefinition(domainClass = Student.class, idClass = Integer.class)
public interface StudentRepository {
    //注意,這裏from後的不是數據表名,而是對於的實體類名
    @Query("select o from Student o where id=(select max(id) from Student t1)")
    public Student getStudentByMaxId();
    //這裏的?是佔位符,在執行是會替換成對應位置的參數,比如這裏?1會被替換成name的值
    @Query("select o from Student o where o.name=?1 and o.age=?2")
    public List<Student> queryParams1(String name, Integer age);
    //這裏的:+參數名則是綁定參數,在參數列中使用@Param註解綁定參數,則可在SQL語句中使用:+參數名得到相應參數的值
    @Query("select o from Student o where o.name=:name and o.age=:age")
    public List<Student> queryParams2(@Param("name")String name, @Param("age")Integer age);
}

使用@Quer註解傳入SQL語句,我們就可以實現一些比較複雜的查詢。除了查詢,我們平時還會有更新、插入、刪除的操作,如果你模仿上述方法編寫,你是無法執行的。原因是插入、更新、刪除是DML,select是DQL。

要實現插入、更新、刪除操作,我們只需要再多加一個註解@Modifying,告訴框架,它是一個DML操作即可。

@RepositoryDefinition(domainClass = Student.class, idClass = Integer.class)
public interface StudentRepository {
    @Modifying
    @Query("update Student o set o.age = :age where o.id = :id")
    public void update(@Param("id")Integer id, @Param("age")Integer age);
}

又或許你必須要使用原生的SQL語句怎麼辦?只需要在@Query註解中設置nativeQuery即可

@RepositoryDefinition(domainClass = Student.class, idClass = Integer.class)
public interface StudentRepository {
    //注意,這裏使用原生的SQL語句,from後的表名應該是數據庫表名
    @Query(nativeQuery = true, value = "select count(1) from student")
    public long getCount();
}

看到這裏,相信大家對於SpringData已經有了初步的瞭解,以及熟悉基本的操作了。在介紹SpringData的時候,我們可以看到對於不同的數據庫廠商,SpringData提供了不同的Repository接口。

以JDBC爲例,我們可以看到有JpaRepository,它是繼承了PagingAndSortingRepository接口,PagingAndSortingRepository接口繼承了CrudRepository接口,CrudRepository接口又繼承了Repository。

所以JpaRepository的功能相比Repository要豐富了許多,除了基本的CRUD操作之外,還支持分頁和排序功能。具體功能可以在使用中慢慢熟悉瞭解,我們也不需要把全部功能都列一遍,畢竟使用起來和Repository都是大同小異的。(之後有時間可能會再更一下JpaRepository的分頁和排序功能如何使用)

以上是本人在學習SpringData入門時的一些總結,如有錯誤,望不吝指正。

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