1. 程式人生 > 程式設計 >MyBatis還是JPA?終於有答案了

MyBatis還是JPA?終於有答案了

對於一個和資料庫打交道的程式設計師來說,很快會面臨著一個艱難的選擇。到底是選擇MyBatis還是JPA呢?

很多人說,技術選擇,都要根據需求來,這個沒錯。但是,除了需求,還有很重要的一個環節,那就是隊友的水平。如果你選擇了一些比較高階的技術,那麼就是在給整個團隊埋坑。

JPA的抽象層次更高,程式碼寫起來也更簡潔,但是它一點都不簡單。雖然經過了多次的培訓,我呆過的幾個團隊,還是把它用的和屎一樣。

我扔掉了JPA

我仔細想了一下,有下面幾點原因,造成了JPA在很多團隊根本就玩不下去。

  • JPA適合業務模型固定的場景,適合比較穩定的需求。但是國內這種朝三暮四的需求風格,產品經理這種傳話筒式的設計模式,造成了需求的泛濫和不確定。JPA在這種模式下就是渣。
  • JPA的技術要求比較高。不要懷疑,你剛開始用起裡可能覺得非常簡單。但隨著你的深入使用,你會發現這是一個災難。裡面的各種轉換和快取,會把人繞暈。而大多數的快餐程式設計師是不想要了解這些的。
  • 很多程式設計師很會寫SQL,所以很多SQL語句長的很胖,長的要命。業務混亂,多張表關聯,我甚至見過上百張業務表關聯的複雜業務。DBA無奈之下,通常都會有sql稽核。JPA搞sql稽核?還是弱了一點。

所以,不是JPA不好,而是它不符合國情而已。想要在公司內推行JPA,你需要給我一個穩定的產品團隊、一個牛X的技術團隊才行。

所以,大多數公司寧可寫一堆重複的、亂七八糟的Mybaits程式碼,也不會輕易嘗試JPA,這是符合邏輯的,符合事物發展規律的。

所以,我們下面的文章就是來討論MyBatis的,來看一下Mybaits到底要怎麼寫才算優雅。

MyBatis為什麼不好用

優秀的程式設計師都是很懶的。所以很多人不想設計實體的sql。JPA可以直接根據Java的實體程式碼,生成sql的庫表,這在使用Mybatis的人來看,是非常羨慕的。

使用MyBatis,要倒著來。需要先設計庫表,然後根據庫表反向生成一堆Java程式碼和配置檔案。

這個程式碼生成器,就是mybatis-generator。

但是,請注意。這個生成器生成的程式碼,有四種模式!!!這就是最讓初學者難受的地方。如果你也是剛接觸MyBatis,強烈推薦只關注下面第一種模式。

  • MyBatis3 這種模式就是我們常用的方式,會生成domain類、Example類、mapper對映檔案等。它生成的資訊比較囉嗦,內容幾乎無法改動。對於專案中自己寫的sql,一般都採用手寫的方式再寫一份,而不是改動原來的檔案。
  • MyBatis3Simple 上面這種模式的簡易程式碼生成模式,缺少一些東西,但很簡潔。對MyBatis沒有經驗,不推薦使用它。
  • MyBatis3DynamicSql 這是通過Builder模式實現的動態SQL特性,你還需要加入額外的jar包。加上它之後,其實和JPA是有點相似的。既然如此,那為何不直接使用JPA呢?所以這個DSQL雖然是預設的生成行為,但是非常不推薦。
  • MyBatis3Kotlin 這個不廢話。就是生成Kotlin版的一些配置和程式碼資訊。

所以,下面僅僅介紹MyBatis3模式的程式碼生成。

要使用它,需要在pom.xml里加入它的依賴。

<dependency>
  <groupId>org.mybatis.generator</groupId>
  <artifactId>mybatis-generator-core</artifactId>
  <optional>true</optional>
  <scope>test</scope>
  <version>1.4.0</version>
</dependency>

我個人喜歡使用Java程式碼來操作程式碼生成這個過程,所以下面就是生成程式碼的程式碼。

public class MBGTool {
  public static void main(String[] args) throws Exception {
    List<String> warnings = new ArrayList<>();
    boolean overwrite = true;
    InputStream configFile = MBGTool.class.getResourceAsStream("/generator/generatorConfig.xml");
    ConfigurationParser cp = new ConfigurationParser(warnings);
    Configuration config = cp.parseConfiguration(configFile);
    DefaultShellCallback callback = new DefaultShellCallback(overwrite);
    MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,callback,warnings);
    myBatisGenerator.generate(null);
  }
}

從程式碼中,我們可以看到需要配置一個generatorConfig.xml檔案,用來規定怎麼生成程式碼檔案。

<!DOCTYPE generatorConfiguration PUBLIC
    "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
    "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
  <context id="simple" targetRuntime="MyBatis3">
    <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/mbye"
            userId="root"
            password="root"
    />
    <javaModelGenerator targetPackage="com.github.javarunfast.mbye.domain" targetProject="src/main/java">
      <property name="enableSubPackages" value="true"/>
      <property name="trimStrings" value="true"/>
    </javaModelGenerator>
    <sqlMapGenerator targetPackage="com.github.javarunfast.mbye.mapper" targetProject="src/main/resources"/>
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.github.javarunfast.mbye.mapper" targetProject="src/main/java">
      <property name="enableSubPackages" value="true"/>
    </javaClientGenerator>
    <table tableName="test"/>
  </context>
</generatorConfiguration>

執行我們的MBGTool檔案之後,就可以生成MyBatis的程式碼了。

怎麼寫程式碼最優雅

但是,我這裡並不是要推薦你使用這種模式。因為,它生成了一大堆無用的檔案。假如你的專案使用了sonar這樣的程式碼質量審查工具,你會發現很多飄紅的地方,還有那要命的覆蓋率問題。
怎麼辦?

經過我多年的摸索,我現在推薦一種非常好用的寫法。自從我採用了這種方式之後,就再也沒有換過。

第一、不需要程式碼生成器了
資料表的設計,還有domain的書寫,全部靠手工。這樣我們的程式碼,如果有必要,還可以遷移到JPA上去。這種模式還能順便學習一下Java裡面的資料型別,是如何和SQL裡的資料型別一一對應的。在做表設計的時候,順便能夠了解一些背後的原理。

第二、不需要寫對映檔案了
生成器生成的東西,確實是有一堆無用的邏輯。比如我的某個資料表,根本不需要提供查詢所有和刪除這種動作,它還是預設提供了。

在這種簡約模式下,我們直接手寫Mapper檔案,然後只宣告所需要的介面方法就可以了。

@Mapper
public interface AccountBasicMapper {
  @Insert("AccountBasicMapper/insert.sql")
  void insert(@Param("r") AccountBasic record);
}

可以看到,裡面有一個Insert註解,我們傳入了一個具體的domain,然後,就可以在AccountBasicMapper目錄下的insert.sql檔案裡,書寫具體的sql語句了。

sql語句樣例如下:

INSERT INTO account_basic(
  account_id,nick_name,password,sex,state,photo_url,created,modified,version
)VALUES (
  <@p name="r.accountId"/>,<@p name="r.nickName"/>,<@p name="r.password"/>,<@p name="r.sex"/>,<@p name="r.state"/>,<@p name="r.photoUrl"/>,<@p name="r.created"/>,<@p name="r.modified"/>,<@p name="r.version"/>
)

那麼這是什麼語法呢?它又是如何知道是這樣配置的呢?這就需要引入MyBatis的指令碼語言配置功能。在這裡,我們使用的freemark的模版。

不要忘了加入它的依賴。

<dependency>
  <groupId>org.mybatis.scripting</groupId>
  <artifactId>mybatis-freemarker</artifactId>
  <version>1.2.2</version>
</dependency>

然後,在yaml檔案裡做上相應的配置就ok了。

mybatis:
 check_config_location: false
 scripting-language-driver:
  freemarker:
   template-file:
    base-dir: mappers/
   path-provider:
    includes-package-path: false
    separate-directory-per-mapper: false

這種方式的好處和壞處

我個人是非常喜歡這種模式的。因為它有下面幾個好處:

  • 用什麼寫什麼,程式碼量少,簡潔優雅。
  • SQL集中,不用分散在程式碼裡,xml裡,或者註解裡。方便DBA進行SQL稽核。由於沒了xml的干擾,SQL反而更加簡潔了。
  • 一個DAO方法一個sql檔案,模式單一可控。
  • MyBatis的功能優勢可以全部發揮,無縫整合。

當然,缺點也是顯而易見的。

  • 即使變了個引數,也要修改很多sql檔案。
  • 需要為每一個方法配一個sql檔案,即使這是個很弱智的插入查詢方法。

不過,我並不認為這是個問題。每一個方法配備一個sql檔案,程式碼寫起來反而更加簡單了。當出現問題的時候,也不用根據邏輯進行跟蹤定位到拼接後的SQL語句。我現在,只需要拿到對應方法的SQL檔案,就可以改吧改吧,直接在sql終端裡執行除錯。這樣,sql優化也變的簡單了。

當然,一個人一個習慣。我個人喜歡這種模式,而且在我的團隊裡推行這種模式,發現執行的也很好。另外,程式設計師為了少寫重複的sql程式碼,在設計Dao介面的時候,反而更加認真了。

這可能是一個額外的收穫吧。

到此這篇關於MyBatis還是JPA?終於有答案了的文章就介紹到這了,更多相關MyBatis還是JPA內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!