知己知彼:持久化演進

引言

談論起「持久化」一詞,每天操做數據庫的你們確定不會陌生。php

持久化中,備受開發者矚目的就是兩大巨頭:HibernateMyBatis,許多開發者也經常拿兩者對比。去Google上,都是相似二者互相對比的文章。java

image.png

HibernateMyBatis,就像javaphp同樣,雙方似有打架之勢。mysql

雖然我是使用Hibernate的工程師,可是我並不否定MyBatis,二者都優秀,但一切都是業務優先。程序員

學習了潘老師的《SpringBoot+Angular入門實例教程》後,也想到了最原始的JDBCspring

今天,咱們就好好地從古至今,聊聊持久化技術的演進與對比。sql

持久化

遠古的JDBC

JDBCJava Database Connectivity,簡稱 JDBC。是 Java語言中用來規範客戶端程序如何來訪問數據庫的應用程序接口,提供了諸如查詢和更新數據庫中數據的方法。 JDBC是面向關係型數據庫的。

每一位Java程序員可能都經歷過被JDBC支配的恐懼。數據庫

下面是我在Java實驗課上寫過的JDBC代碼,實現的功能很簡單,就是要將一個Student對象保存到數據庫中的student表中。數組

/**
 * 持久化學生實體
 */
private static void persistStudent(Student student) {
    try {
        Class.forName("com.mysql.jdbc.Driver");
        // 數據庫鏈接配置
        String url = "jdbc:mysql://127.0.0.1:7777/java?characterEncoding=utf-8";
        // 獲取鏈接
        Connection connection = DriverManager.getConnection(url, "root", "root");
        // 獲取語句
        Statement statement = connection.createStatement();
        // 生成SQL語句
        String SQL = student.toSQLString();
        // 執行語句
        statement.executeUpdate(SQL);
    } catch (SQLException e) {
        System.out.println("ERROR: " + e.getMessage());
    } catch (ClassNotFoundException e) {
        System.out.println("ERROR: " + e.getMessage());
    } finally {
        statement.close();
        connection.close();
    }
}

核心的功能其實就一行:statement.executeUpdate(SQL),我只想執行一條INSERT語句保證數據的持久化。緩存

但是在JDBC中卻須要實現加載MySQL驅動,獲取Connection,獲取Statement,才能執行SQL,執行以後還須要手動釋放資源,不可謂不麻煩。springboot

程序員最討厭寫重複的代碼,就像我感受從華軟平臺移植重複代碼到試題平臺很枯燥同樣,因此這些「模版式」的代碼須要封裝。

封裝工具類

咱們都是平凡人,咱們能想到的,確定早就有人想到了。

Spring封裝了JDBC,提供了JdbcTemplate

另外一個比較出名的是Apache封裝的DBUtils

使用了JdbcTemplate後,咱們無需再編寫模版式的ConnectionStatementtry ... catch ... finally等代碼。教程中使用了JdbcTemplate查詢教師表,代碼長這樣:

@GetMapping
public List<Teacher> getAll() {
    /* 初始化不固定大小的數組 */
    List<Teacher> teachers = new ArrayList<>();

    /* 定義實現了RowCallbackHandler接口的對象 */
    RowCallbackHandler rowCallbackHandler = new RowCallbackHandler() {
        /**
         * 該方法用於執行jdbcTemplate.query後的回調,每行數據回調1次。好比Teacher表中有兩行數據,則回調此方法兩次。
         *
         * @param resultSet 查詢結果,每次一行
         * @throws SQLException 查詢出錯時,將拋出此異常,暫時不處理。
         */
        @Override
        public void processRow(ResultSet resultSet) throws SQLException {
            Teacher teacher = new Teacher();
            /* 獲取字段id,並轉換爲Long類型返回 */
            teacher.setId(resultSet.getLong("id"));
            /* 獲取字段name,並轉換爲String類型返回 */
            teacher.setName(resultSet.getString("name"));
            /* 獲取字段sex,並轉換爲布爾類型返回 */
            teacher.setSex(resultSet.getBoolean("sex"));
            teacher.setUsername(resultSet.getString("username"));
            teacher.setEmail(resultSet.getString("email"));
            teacher.setCreateTime(resultSet.getLong("create_time"));
            teacher.setUpdateTime(resultSet.getLong("update_time"));
            
            /* 將獲得的teacher添加到要返回的數組中 */
            teachers.add(teacher);
        }
    };

    /* 定義查詢字符串 */
    String query = "select id, name, sex, username, email, create_time, update_time from teacher";

    /* 使用query進行查詢,並把查詢的結果經過調用rowCallbackHandler.processRow()方法傳遞給rowCallbackHandler對象 */
    jdbcTemplate.query(query, rowCallbackHandler);
    return teachers;
}

思考問題:既然能夠經過ResultSet獲取查詢結果,這裏爲何Spring封裝的query方法不直接返回ResultSet對象,而要設計這樣一個回調的對象呢?

提示,JdbcTemplate查詢的核心源碼以下:

@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
    ResultSet rs = null;
    try {
        rs = stmt.executeQuery(sql);
        return rse.extractData(rs);
    }
    finally {
        JdbcUtils.closeResultSet(rs);
    }
}

ResultSet用起來很討厭是否是?須要手動地去獲取字段並設置到對象中,JdbcTemplate提升了開發效率,可是提升地不明顯,能不能再簡單一點呢?

ORM

爲了不編寫大量這樣的與業務無關的代碼,設計了ORM思想。

teacher.setName(resultSet.getString("name"));
teacher.setSex(resultSet.getBoolean("sex"));
teacher.setUsername(resultSet.getString("username"));
teacher.setEmail(resultSet.getString("email"));

ORM:即對象關係映射。將對象與數據表進行關聯,咱們無需關注這類不關聯業務的冗餘代碼,操做對象,即操做數據表。

半自動化ORM

半自動化的ORM框架,就是煊赫一時的MyBatis

我簡單地去學習瞭如下MyBatis,畢竟這麼多公司使用,確定有他們的道理,若是足夠優秀,也能夠考慮使用。但是結果卻有些使人失望。

打開官網學習,這應該算是我見過的最寒酸的著名開源框架的官網了,裏面的內容也不詳細。

image.png

官網的例子不夠詳細,我又參閱了許多MyBatis的博文進行學習。

開啓數據庫字段下劃線到對象命名駝峯的配置。

mybatis.configuration.map-underscore-to-camel-case=true

仍是經典的教師、班級、學生的關係:

public class Teacher {

    private Long id;

    private String name;

    private Boolean sex;

    private String username;

    private String email;

    private Long createTime;

    private Long updateTime;
}

public class Klass {

    private Long id;

    private String name;

    private Teacher teacher;

    private List<Student> students;
}

public class Student {

    private Long id;

    private String name;
}

教師的CRUD單表查詢:

@Mapper
public interface TeacherMapper {

    @Select("SELECT * FROM teacher")
    List<Teacher> getAll();

    @Select("SELECT * FROM teacher WHERE id = #{id}")
    Teacher get(Long id);

    @Select("SELECT * FROM teacher WHERE username = #{username}")
    Teacher findByUsername(String username);

    @Insert("INSERT INTO teacher(name, sex, username, email, create_time, update_time) VALUES(#{name}, #{sex}, #{username}, #{email}, #{createTime}, #{updateTime})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(Teacher teacher);

    @Update("UPDATE teacher SET name=#{name}, sex=#{sex}, email=#{email}, update_time=#{updateTime} WHERE id=#{id}")
    void update(Teacher teacher);

    @Delete("DELETE FROM teacher WHERE id=#{id}")
    void delete(Long id);
}

關聯查詢:

@Mapper
public interface KlassMapper {

    @Select("SELECT * FROM klass")
    @Results({
            @Result(column = "teacher_id", property = "teacher", one = @One(select = "club.yunzhi.mybatis.mapper.TeacherMapper.get")),
            @Result(column = "id", property = "students", many = @Many(select = "club.yunzhi.mybatis.mapper.StudentMapper.getAllByKlassId"))
    })
    List<Klass> getAll();
}

關聯中用到的StudentMapper子查詢:

@Mapper
public interface StudentMapper {

    @Select("SELECT * FROM student WHERE klass_id=#{klassId}")
    List<Student> getAllByKlassId(Long klassId);
}

也學了一個晚上了,雖然二級緩存之類的高級特性還沒學習呢,可是對MyBatis也算是又一個大致的瞭解了。

業務是老大,全部技術都是服務於業務的。程序員應該關注業務與實際問題,我是不太喜歡去寫SQL的,SQL應該是DBA去專業學習並優化的,MyBatis看起來更像是給DBA用的框架同樣。

全自動化ORM

當一個系統設計完成以後,其餘的工做就是搬磚。

搬磚固然是越簡單越好,不編寫SQLHibernate走起。

public interface TeacherRepository extends CrudRepository<Teacher, Long> {
}

徹底基於對象,更加註重業務。

怎麼選?

好多人都是「你看支付寶用MyBatis,那我也用MyBatis」。

這是StackOverflow上一個十年前的關於二者對比討論的話題:https://stackoverflow.com/questions/1984548/hibernate-vs-ibatis

image.png

最終的討論結果以下:

iBatisHibernate是徹底不一樣的事物(iBatisMyBatis的前身)。

若是以對象爲中心,那Hibernate更好;若是以數據庫爲中心,那iBatis更好。

若是你設計系統架構,沒有高併發的需求,Hibernate最合適,對象模型會使得代碼很是簡潔,但代價巨大。

若是你接手了一個遺留下來的數據庫,而且須要編寫複雜的SQL查詢,那iBatis更合適。

總結一句話就是性能問題:Hibernate方便,可是會有性能損耗;MyBatis有性能優點。

總結

都說MyBatis適合高併發,但高併發又豈是一個MyBatis就能囊括的?

業務是老大,若是真的碰到了高併發需求,那又是咱們進步的時候了。

相關文章
相關標籤/搜索