成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

MyBatis 源碼分析系列文章導讀

weizx / 3280人閱讀

摘要:本文速覽本篇文章是我為接下來的源碼分析系列文章寫的一個導讀文章。年該項目從基金會遷出,并改名為。同期,停止維護。符號所在的行則是表示的執(zhí)行結(jié)果。同時,使用無需處理受檢異常,比如。另外,把寫在配置文件中,進行集中管理,利于維護。

1.本文速覽

本篇文章是我為接下來的 MyBatis 源碼分析系列文章寫的一個導讀文章。本篇文章從 MyBatis 是什么(what),為什么要使用(why),以及如何使用(how)等三個角度進行了說明和演示。由于文章的篇幅比較大,這里特地拿出一章用于介紹本文的結(jié)構(gòu)和內(nèi)容。那下面我們來看一下本文的章節(jié)安排:

如上圖,本文的大部分篇幅主要集中在了第3章和第4章。第3章演示了幾種持久層技術(shù)的用法,并在此基礎(chǔ)上,分析了各種技術(shù)的使用場景。通過分析 MyBatis 的使用場景,說明了為什么要使用 MyBatis 這個問題。第4章主要用于介紹 MyBatis 的兩種不同的用法。在 4.1 節(jié),演示多帶帶使用 MyBatis 的過程,演示示例涉及一對一一對多的查詢場景。4.2 節(jié)則是介紹了 MyBatis 和 Spring 整合的過程,并在最后演示了如何在 Spring 中使用 MyBatis。除了這兩章內(nèi)容,本文的第2章和第5章內(nèi)容比較少,就不介紹了。

以上就是本篇文章內(nèi)容的預(yù)覽,如果這些內(nèi)容大家都掌握,那么就不必往下看了。當然,如果沒掌握或者是有興趣,那不妨繼續(xù)往下閱讀。好了,其他的就不多說了,咱們進入正題吧。

2.什么是 MyBatis

MyBatis 的前身是 iBatis,其是 Apache 軟件基金會下的一個開源項目。2010年該項目從 Apache 基金會遷出,并改名為 MyBatis。同期,iBatis 停止維護。

MyBatis 是一種半自動化的 Java 持久層框架(persistence framework),其通過注解或 XML 的方式將對象和 SQL 關(guān)聯(lián)起來。之所以說它是半自動的,是因為和 Hibernate 等一些可自動生成 SQL 的 ORM(Object Relational Mapping) 框架相比,使用 MyBatis 需要用戶自行維護 SQL。維護 SQL 的工作比較繁瑣,但也有好處。比如我們可控制 SQL 邏輯,可對其進行優(yōu)化,以提高效率。

MyBatis 是一個容易上手的持久層框架,使用者通過簡單的學習即可掌握其常用特性的用法。這也是 MyBatis 被廣泛使用的一個原因。

3.為什么要使用 MyBatis

我們在使用 Java 程序訪問數(shù)據(jù)庫時,有多種選擇。比如我們可通過編寫最原始的 JDBC 代碼訪問數(shù)據(jù)庫,或是通過 Spring 提供的 JdbcTemplate 訪問數(shù)據(jù)庫。除此之外,我們還可以選擇 Hibernate,或者本篇的主角 MyBatis 等。在有多個可選項的情況下,我們?yōu)槭裁催x擇 MyBatis 呢?要回答這個問題,我們需要將 MyBatis 與這幾種數(shù)據(jù)庫訪問方式對比一下,高下立判。當然,技術(shù)之間通常沒有高下之分。從應(yīng)用場景的角度來說,符合應(yīng)用場景需求的技術(shù)才是合適的選擇。那下面我會通過寫代碼的方式,來比較一下這幾種數(shù)據(jù)庫訪問技術(shù)的優(yōu)缺點,并會在最后說明 MyBatis 的適用場景。

這里,先把本節(jié)所用到的一些公共類和配置貼出來,后面但凡用到這些資源的地方,大家可以到這里進行查看。本章所用到的類如下:

public class Article {
    private Integer id;
    private String title;
    private String author;
    private String content;
    private Date createTime;
    
    // 省略 getter/setter 和 toString
}

數(shù)據(jù)庫相關(guān)配置放在了 jdbc.properties 文件中,詳細內(nèi)容如下:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/coolblog?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=TRUE
jdbc.username=root
jdbc.password=****

表記錄如下:

下面先來演示 MyBatis 訪問數(shù)據(jù)庫的過程。

3.1 使用 MyBatis 訪問數(shù)據(jù)庫

前面說過,MyBatis 是一種半自動化的 Java 持久化框架,使用 MyBatis 需要用戶自行維護 SQL。這里,我們把 SQL 放在 XML 中,文件名稱為 ArticleMapper.xml。相關(guān)配置如下:


    
        
        
        
        
        
    
    
    

上面的 SQL 用于從article表中查詢出某個作者從某個時候到現(xiàn)在所寫的文章記錄。在 MyBatis 中,SQL 映射文件需要與數(shù)據(jù)訪問接口對應(yīng)起來,比如上面的配置對應(yīng)xyz.coolblog.dao.ArticleDao接口,這個接口的定義如下:

public interface ArticleDao {
    List
findByAuthorAndCreateTime(@Param("author") String author, @Param("createTime") String createTime); }

要想讓 MyBatis 跑起來,還需要進行一些配置。比如配置數(shù)據(jù)源、配置 SQL 映射文件的位置信息等。本節(jié)所使用到的配置如下:


    

    
        
            
            
                
                
                
                
            
        
    
    
    
        
    

到此,MyBatis 所需的環(huán)境就配置好了。接下來把 MyBatis 跑起來吧,相關(guān)測試代碼如下:

public class MyBatisTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void prepare() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        inputStream.close();
    }

    @Test
    public void testMyBatis() throws IOException {
        SqlSession session = sqlSessionFactory.openSession();
        try {
            ArticleDao articleDao = session.getMapper(ArticleDao.class);
            List
articles = articleDao.findByAuthorAndCreateTime("coolblog.xyz", "2018-06-10"); } finally { session.commit(); session.close(); } } }

在上面的測試代碼中,prepare 方法用于創(chuàng)建SqlSessionFactory工廠,該工廠的用途是創(chuàng)建SqlSession。通過 SqlSession,可為我們的數(shù)據(jù)庫訪問接口ArticleDao接口生成一個代理對象。MyBatis 會將接口方法findByAuthorAndCreateTime和 SQL 映射文件中配置的 SQL 關(guān)聯(lián)起來,這樣調(diào)用該方法等同于執(zhí)行相關(guān)的 SQL。

上面的測試代碼運行結(jié)果如下:

如上,大家在學習 MyBatis 框架時,可以配置一下 MyBatis 的日志,這樣可把 MyBatis 的調(diào)試信息打印出來,方便觀察 SQL 的執(zhí)行過程。在上面的結(jié)果中,==>符號所在的行表示向數(shù)據(jù)庫中輸入的 SQL 及相關(guān)參數(shù)。<==符號所在的行則是表示 SQL 的執(zhí)行結(jié)果。上面輸入輸出不難看懂,這里就不多說了。

關(guān)于 MyBatis 的優(yōu)缺點,這里先不進行總結(jié)。后面演示其他的框架時再進行比較說明。

演示完 MyBatis,下面,我們來看看通過原始的 JDBC 直接訪問數(shù)據(jù)庫過程是怎樣的。

3.2 使用 JDBC 訪問數(shù)據(jù)庫 3.2.1 JDBC 訪問數(shù)據(jù)庫的過程演示

在初學 Java 編程階段,多數(shù)朋友應(yīng)該都是通過直接寫 JDBC 代碼訪問數(shù)據(jù)庫。我這么說,大家應(yīng)該沒異議吧。這種方式的代碼流程一般是加載數(shù)據(jù)庫驅(qū)動,創(chuàng)建數(shù)據(jù)庫連接對象,創(chuàng)建 SQL 執(zhí)行語句對象,執(zhí)行 SQL 和處理結(jié)果集等,過程比較固定。下面我們再手寫一遍 JDBC 代碼,回憶一下初學 Java 的場景。

public class JdbcTest {

    @Test
    public void testJdbc() {
        String url = "jdbc:mysql://localhost:3306/myblog?user=root&password=1234&useUnicode=true&characterEncoding=UTF8&useSSL=false";

        Connection conn = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(url);

            String author = "coolblog.xyz";
            String date = "2018.06.10";
            String sql = "SELECT id, title, author, content, create_time FROM article WHERE author = "" + author + "" AND create_time > "" + date + """;

            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(sql);
            List
articles = new ArrayList<>(rs.getRow()); while (rs.next()) { Article article = new Article(); article.setId(rs.getInt("id")); article.setTitle(rs.getString("title")); article.setAuthor(rs.getString("author")); article.setContent(rs.getString("content")); article.setCreateTime(rs.getDate("create_time")); articles.add(article); } System.out.println("Query SQL ==> " + sql); System.out.println("Query Result: "); articles.forEach(System.out::println); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

代碼比較簡單,就不多說了。下面來看一下測試結(jié)果:

上面代碼的步驟比較多,但核心步驟只有兩部,分別是執(zhí)行 SQL 和處理查詢結(jié)果。從開發(fā)人員的角度來說,我們也只關(guān)心這兩個步驟。如果每次為了執(zhí)行某個 SQL 都要寫很多額外的代碼。比如打開驅(qū)動,創(chuàng)建數(shù)據(jù)庫連接,就顯得很繁瑣了。當然我們可以將這些額外的步驟封裝起來,這樣每次調(diào)用封裝好的方法即可。這樣確實可以解決代碼繁瑣,冗余的問題。不過,使用 JDBC 并非僅會導致代碼繁瑣,冗余的問題。在上面的代碼中,我們通過字符串對 SQL 進行拼接。這樣做會導致兩個問題,第一是拼接 SQL 可能會導致 SQL 出錯,比如少了個逗號或者多了個單引號等。第二是將 SQL 寫在代碼中,如果要改動 SQL,就需要到代碼中進行更改。這樣做是不合適的,因為改動 Java 代碼就需要重新編譯 Java 文件,然后再打包發(fā)布。同時,將 SQL 和 Java 代碼混在一起,會降低代碼的可讀性,不利于維護。關(guān)于拼接 SQL,是有相應(yīng)的處理方法。比如可以使用 PreparedStatement,同時還可解決 SQL 注入的問題。

除了上面所說的問題,直接使用 JDBC 訪問數(shù)據(jù)庫還會有什么問題呢?這次我們將目光轉(zhuǎn)移到執(zhí)行結(jié)果的處理邏輯上。從上面的代碼中可以看出,我們需要手動從 ResultSet 中取出數(shù)據(jù),然后再設(shè)置到 Article 對象中。好在我們的 Article 屬性不多,所以這樣做看起來也沒什么。假如 Article 對象有幾十個屬性,再用上面的方式接收查詢結(jié)果,會非常的麻煩。而且可能還會因為屬性太多,導致忘記設(shè)置某些屬性。以上的代碼還有一個問題,用戶需要自行處理受檢異常,這也是導致代碼繁瑣的一個原因。哦,還有一個問題,差點忘了。用戶還需要手動管理數(shù)據(jù)庫連接,開始要手動獲取數(shù)據(jù)庫連接。使用好后,又要手動關(guān)閉數(shù)據(jù)庫連接。不得不說,真麻煩。

沒想到直接使用 JDBC 訪問數(shù)據(jù)庫會有這么多的問題。如果在生產(chǎn)環(huán)境直接使用 JDBC,怕是要被 Leader 打死了。當然,視情況而定。如果項目非常小,且對數(shù)據(jù)庫依賴比較低。直接使用 JDBC 也很方便,不用像 MyBatis 那樣搞一堆配置了。

3.2.2 MyBatis VS JDBC

上面說了一大堆 JDBC 的壞話,有點過意不去,所以下面來吐槽一下 MyBatis 吧。與 JDBC 相比,MyBatis 缺點比較明顯,它的配置比較多,特別是 SQL 映射文件。如果一個大型項目中有幾十上百個 Dao 接口,就需要有同等數(shù)量的 SQL 映射文件,這些映射文件需要用戶自行維護。不過與 JDBC 相比,維護映射文件不是什么問題。不然如果把同等數(shù)量的 SQL 像 JDBC 那樣寫在代碼中,那維護的代價才叫大,搞不好還會翻車。除了配置文件的問題,大家會發(fā)現(xiàn)使用 MyBatis 訪問數(shù)據(jù)庫好像過程也很繁瑣啊。它的步驟大致如下:

讀取配置文件

創(chuàng)建 SqlSessionFactoryBuilder 對象

通過 SqlSessionFactoryBuilder 對象創(chuàng)建 SqlSessionFactory

通過 SqlSessionFactory 創(chuàng)建 SqlSession

為 Dao 接口生成代理類

調(diào)用接口方法訪問數(shù)據(jù)庫

如上,如果每次執(zhí)行一個 SQL 要經(jīng)過上面幾步,那和 JDBC 比較起來,也沒什優(yōu)勢了。不過這里大家需要注意,SqlSessionFactoryBuilder 和 SqlSessionFactory 以及 SqlSession 等對象的作用域和生命周期是不一樣的,這一點在 MyBatis 官方文檔中說的比較清楚,我這里照搬一下。SqlSessionFactoryBuilder 對象用于構(gòu)建 SqlSessionFactory,只要構(gòu)建好,這個對象就可以丟棄了。SqlSessionFactory 是一個工廠類,一旦被創(chuàng)建就應(yīng)該在應(yīng)用運行期間一直存在,不應(yīng)該丟棄或重建。SqlSession 不是線程安全的,所以不應(yīng)被多線程共享。官方推薦的使用方式是有按需創(chuàng)建,用完即銷毀。因此,以上步驟中,第1、2和第3步只需執(zhí)行一次。第4和第5步需要進行多次創(chuàng)建。至于第6步,這一步是必須的。所以比較下來,MyBatis 的使用方式還是比 JDBC 簡單的。同時,使用 MyBatis 無需處理受檢異常,比如 SQLException。另外,把 SQL 寫在配置文件中,進行集中管理,利于維護。同時將 SQL 從代碼中剝離,在提高代碼的可讀性的同時,也避免拼接 SQL 可能會導致的錯誤。除了上面所說這些,MyBatis 會將查詢結(jié)果轉(zhuǎn)為相應(yīng)的對象,無需用戶自行處理 ResultSet。

總的來說,MyBatis 在易用性上要比 JDBC 好太多。不過這里拿 MyBatis 和 JDBC 進行對比并不太合適。JDBC 作為 Java 平臺的數(shù)據(jù)庫訪問規(guī)范,它僅提供一種訪問數(shù)據(jù)庫的能力。至于使用者覺得 JDBC 流程繁瑣,還要自行處理異常等問題,這些還真不怪 JDBC。比如 SQLException 這個異常,JDBC 沒法處理啊,拋給調(diào)用者處理也是理所應(yīng)當?shù)摹V劣诜彪s的步驟,這僅是從使用者的角度考慮的,從 JDBC 的角度來說,這里的每個步驟對于完成一個數(shù)據(jù)訪問請求來說都是必須的。至于 MyBatis,它是構(gòu)建在 JDBC 技術(shù)之上的,對訪問數(shù)據(jù)庫的操作進行了簡化,方便用戶使用。綜上所述,JDBC 可看做是一種基礎(chǔ)服務(wù),MyBatis 則是構(gòu)建在基礎(chǔ)服務(wù)之上的框架,它們的目標是不同的。

3.3 使用 Spring JDBC 訪問數(shù)據(jù)庫

上一節(jié)演示了 JDBC 訪問數(shù)據(jù)的過程,通過演示及分析,大家應(yīng)該感受到了直接使用 JDBC 的一些痛點。為了解決其中的一些痛點,Spring JDBC 應(yīng)運而生。Spring JDBC 在 JDBC 基礎(chǔ)上,進行了比較薄的包裝,易用性得到了不少提升。那下面我們來看看如何使用 Spring JDBC。

我們在使用 Spring JDBC 之前,需要進行一些配置。這里我把配置信息放在了 application.xml 文件中,后面寫測試代碼時,讓容器去加載這個配置。配置內(nèi)容如下:




    
    
    
    


    

如上,JdbcTemplate封裝了一些訪問數(shù)據(jù)庫的方法,下面我們會通過此對象訪問數(shù)據(jù)庫。演示代碼如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class SpringJdbcTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testSpringJdbc() {
        String author = "coolblog.xyz";
        String date = "2018.06.10";
        String sql = "SELECT id, title, author, content, create_time FROM article WHERE author = "" + author + "" AND create_time > "" + date + """;
        List
articles = jdbcTemplate.query(sql, (rs, rowNum) -> { Article article = new Article(); article.setId(rs.getInt("id")); article.setTitle(rs.getString("title")); article.setAuthor(rs.getString("author")); article.setContent(rs.getString("content")); article.setCreateTime(rs.getDate("create_time")); return article; }); System.out.println("Query SQL ==> " + sql); System.out.println("Spring JDBC Query Result: "); articles.forEach(System.out::println); } }

測試結(jié)果如下:

從上面的代碼中可以看得出,Spring JDBC 還是比較容易使用的。不過它也是存在一定缺陷的,比如 SQL 仍是寫在代碼中。又比如,對于較為復(fù)雜的結(jié)果(數(shù)據(jù)庫返回的記錄包含多列數(shù)據(jù)),需要用戶自行處理 ResultSet 等。不過與 JDBC 相比,使用 Spring JDBC 無需手動加載數(shù)據(jù)庫驅(qū)動,獲取數(shù)據(jù)庫連接,以及創(chuàng)建 Statement 對象等操作??偟膩碚f,易用性上得到了不少的提升。

這里就不對比 Spring JDBC 和 MyBatis 的優(yōu)缺點了。Spring JDBC 僅對 JDBC 進行了一層比較薄的封裝,相關(guān)對比可以參考上一節(jié)的部分分析,這里不再贅述。

3.4 使用 Hibernate 訪問數(shù)據(jù)庫

本節(jié)會像之前的章節(jié)一樣,我會先寫代碼進行演示,然后再對比 Hibernate 和 MyBatis 的區(qū)別。需要特別說明的是,我在工作中沒有用過 Hibernate,對 Hibernate 也僅停留在了解的程度上。本節(jié)的測試代碼都是現(xiàn)學現(xiàn)賣的,可能有些地方寫的會有問題,或者不是最佳實踐。所以關(guān)于測試代碼,大家看看就好。若有不妥之處,也歡迎指出。

3.4.1 Hibernate 訪問數(shù)據(jù)庫的過程演示

使用 Hibernate,需要先進行環(huán)境配置,主要是關(guān)于數(shù)據(jù)庫方面的配置。這里為了演示,我們簡單配置一下。如下:


    
        com.mysql.cj.jdbc.Driver
        jdbc:mysql://localhost:3306/myblog?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=TRUE
        root
        ****
        org.hibernate.dialect.MySQL5Dialect
        true

        
    

下面再配置一下實體類和表之間的映射關(guān)系,也就是上面配置中出現(xiàn)的Article.hbm.xml。不過這個配置不是必須的,可用注解進行替換。


    
        
            
        
        
        
        
        
    

測試代碼如下:

public class HibernateTest {

    private SessionFactory buildSessionFactory;

    @Before
    public void init() {
        Configuration configuration = new Configuration();
        configuration.configure("hibernate.cfg.xml");
        buildSessionFactory = configuration.buildSessionFactory();
    }

    @After
    public void destroy() {
        buildSessionFactory.close();
    }

    @Test
    public void testORM() {
        System.out.println("-----------------------------? ORM Query ?--------------------------");

        Session session = null;
        try {
            session = buildSessionFactory.openSession();
            int id = 6;
            Article article = session.get(Article.class, id);
            System.out.println("ORM Query Result: ");
            System.out.println(article);
            System.out.println();
        } finally {
            if (Objects.nonNull(session)) {
                session.close();
            }
        }

    }

    @Test
    public void testHQL() {
        System.out.println("-----------------------------? HQL Query ?+--------------------------");
        Session session = null;
        try {
            session = buildSessionFactory.openSession();
            String hql = "from Article where author = :author and create_time > :createTime";
            Query query = session.createQuery(hql);
            query.setParameter("author", "coolblog.xyz");
            query.setParameter("createTime", "2018.06.10");

            List
articles = query.list(); System.out.println("HQL Query Result: "); articles.forEach(System.out::println); System.out.println(); } finally { if (Objects.nonNull(session)) { session.close(); } } } @Test public void testJpaCriteria() throws ParseException { System.out.println("---------------------------? JPA Criteria ?------------------------"); Session session = null; try { session = buildSessionFactory.openSession(); CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); CriteriaQuery
criteriaQuery = criteriaBuilder.createQuery(Article.class); // 定義 FROM 子句 Root
article = criteriaQuery.from(Article.class); // 構(gòu)建查詢條件 SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd"); Predicate greaterThan = criteriaBuilder.greaterThan(article.get("createTime"), sdf.parse("2018.06.10")); Predicate equal = criteriaBuilder.equal(article.get("author"), "coolblog.xyz"); // 通過具有語義化的方法構(gòu)建 SQL,等價于 SELECT ... FROM article WHERE ... AND ... criteriaQuery.select(article).where(equal, greaterThan); Query
query = session.createQuery(criteriaQuery); List
articles = query.getResultList(); System.out.println("JPA Criteria Query Result: "); articles.forEach(System.out::println); } finally { if (Objects.nonNull(session)) { session.close(); } } } }

這里我寫了三種不同的查詢方法,對于比較簡單的查詢,可以通過OID的方式進行,也就是testORM方法中對應(yīng)的代碼。這種方式不需要寫 SQL,完全由 Hibernate 去生成。生成的 SQL 如下:

select 
    article0_.id as id1_0_0_, 
    article0_.title as title2_0_0_, 
    article0_.author as author3_0_0_, 
    article0_.content as content4_0_0_, 
    article0_.create_time as create_t5_0_0_ 
from 
    article article0_ 
where 
    article0_.id=?

第二種方式是通過HQL進行查詢,查詢過程對應(yīng)測試類中的testHQL方法。這種方式需要寫一點 HQL,并為其設(shè)置相應(yīng)的參數(shù)。最終生成的 SQL 如下:

select 
    article0_.id as id1_0_, 
    article0_.title as title2_0_, 
    article0_.author as author3_0_, 
    article0_.content as content4_0_, 
    article0_.create_time as create_t5_0_ 
from 
    article article0_ 
where 
    article0_.author=? and create_time>?

第三種方式是通過 JPA Criteria 進行查詢,JPA Criteria 具有類型安全、面向?qū)ο蠛驼Z義化的特點。使用 JPA Criteria,我們可以用寫 Java 代碼的方式進行數(shù)據(jù)庫操作,無需手寫 SQL。第二種方式和第三種方式進行的是同樣的查詢,所以生成的 SQL 區(qū)別不大,這里就不貼出來了。

下面看一下測試代碼的運行結(jié)果:

3.4.2 MyBatis VS Hibernate

在 Java 中,就持久層框架來說,MyBatis 和 Hibernate 都是很熱門的框架。關(guān)于這兩個框架孰好孰壞,在網(wǎng)上也有很廣泛的討論。不過就像我前面說到那樣,技術(shù)之間通常沒有高低之分,適不適合才是應(yīng)該關(guān)注的點。這兩個框架之前的區(qū)別是比較大的,下面我們來聊聊。

從映射關(guān)系上來說,Hibernate 是把實體類(POJO)和表進行了關(guān)聯(lián),是一種完整的 ORM (O/R mapping) 框架。而MyBatis 則是將數(shù)據(jù)訪問接口(Dao)與 SQL 進行了關(guān)聯(lián),本質(zhì)上算是一種 SQL 映射。從使用的角度來說,使用 Hibernate 通常不需要寫 SQL,讓框架自己生成就可以了。但 MyBatis 則不行,再簡單的數(shù)據(jù)庫訪問操作都需要有與之對應(yīng)的 SQL。另一方面,由于 Hibernate 可自動生成 SQL,所以進行數(shù)據(jù)庫移植時,代價要小一點。而由于使用 MyBatis 需要手寫 SQL,不同的數(shù)據(jù)庫在 SQL 上存在著一定的差異。這就導致進行數(shù)據(jù)庫移植時,可能需要更改 SQL 的情況。不過好在移植數(shù)據(jù)庫的情況很少見,可以忽略。

上面我從兩個維度對 Hibernate 和 MyBatis 進行了對比,但目前也只是說了他們的一些不同點。下面我們來分析一下這兩個框架的適用場景。

Hibernate 可自動生成 SQL,降低使用成本。但同時也要意識到,這樣做也是有代價的,會損失靈活性。比如,如果我們需要手動優(yōu)化 SQL,我們很難改變 Hibernate 生成的 SQL。因此對于 Hibernate 來說,它適用于一些需求比較穩(wěn)定,變化比較小的項目,譬如 OA、CRM 等。

與 Hibernate 相反,MyBatis 需要手動維護 SQL,這會增加使用成本。但同時,使用者可靈活控制 SQL 的行為,這為改動和優(yōu)化 SQL 提供了可能。所以 MyBatis 適合應(yīng)用在一些需要快速迭代,需求變化大的項目中,這也就是為什么 MyBatis 在互聯(lián)網(wǎng)公司中使用的比較廣泛的原因。除此之外,MyBatis 還提供了插件機制,使用者可以按需定制插件。這也是 MyBatis 靈活性的一個體現(xiàn)。

分析到這里,大家應(yīng)該清楚了兩個框架之前的區(qū)別,以及適用場景。樓主目前在一家汽車相關(guān)的互聯(lián)網(wǎng)公司,公司發(fā)展的比較快,項目迭代的也比較快,各種小需求也比較多。所以,相比之下,MyBatis 是一個比較合適的選擇。

3.5 本章小結(jié)

本節(jié)用了大量的篇幅介紹常見持久層框架的用法,并進行了較為詳細的分析和對比??赐赀@些,相信大家對這些框架應(yīng)該也有了更多的了解。好了,其他的就不多說了,我們繼續(xù)往下看吧。

4.如何使用 MyBatis

本章,我們一起來看一下 MyBatis 是如何使用的。在上一章,我簡單演示了一下 MyBatis 的使用方法。不過,那個太簡單了,本章我們來演示一個略為復(fù)雜的例子。不過,這個例子復(fù)雜度和真實的項目還是有差距,僅做演示使用。

本章包含兩節(jié)內(nèi)容,第一節(jié)演示多帶帶使用 MyBatis 的過程,第二節(jié)演示 MyBatis 是如何和 Spring 進行整合的。那其他的就不多說了,下面開始演示。

4.1 多帶帶使用

本節(jié)演示的場景是個人網(wǎng)站的作者和文章之間的關(guān)聯(lián)場景。在一個網(wǎng)站中,一篇文章對應(yīng)一名作者,一個作者對應(yīng)多篇文章。下面我們來看一下作者文章的定義,如下:

public class AuthorDO implements Serializable {
    private Integer id;
    private String name;
    private Integer age;
    private SexEnum sex;
    private String email;
    private List articles;

    // 省略 getter/setter 和 toString
}

public class ArticleDO implements Serializable {
    private Integer id;
    private String title;
    private ArticleTypeEnum type;
    private AuthorDO author;
    private String content;
    private Date createTime;

    // 省略 getter/setter 和 toString
}

如上,AuthorDO 中包含了對一組 ArticleDO 的引用,這是一對多的關(guān)系。ArticleDO 中則包含了一個對 AuthorDO 的引用,這是一對一的關(guān)系。除此之外,這里使用了兩個常量,一個用于表示性別,另一個用于表示文章類型,它們的定義如下:

public enum SexEnum {
    MAN,
    FEMALE,
    UNKNOWN;
}

public enum ArticleTypeEnum {
    JAVA(1),
    DUBBO(2),
    SPRING(4),
    MYBATIS(8);

    private int code;

    ArticleTypeEnum(int code) {
        this.code = code;
    }

    public int code() {
        return code;
    }

    public static ArticleTypeEnum find(int code) {
        for (ArticleTypeEnum at : ArticleTypeEnum.values()) {
            if (at.code == code) {
                return at;
            }
        }

        return null;
    }
}

本篇文章使用了兩張表,分別用于存儲文章和作者信息。這兩種表的內(nèi)容如下:

下面來看一下數(shù)據(jù)庫訪問層的接口定義,如下:

public interface ArticleDao {
    ArticleDO findOne(@Param("id") int id);
}

public interface AuthorDao {
    AuthorDO findOne(@Param("id") int id);
}

與這兩個接口對應(yīng)的 SQL 被配置在了下面的兩個映射文件中。我們先來看一下第一個映射文件 AuthorMapper.xml 的內(nèi)容。




    
        
        
        
        
        
    

    
        
        
        
        
        
        
    

    

注意看上面的配置,這個標簽中包含了一個一對多的配置,這個配置引用了一個 id 為articleResult。除了要注意一對多的配置,這里還要下面這行配置:

前面說過 AuthorDO 的sex屬性是一個枚舉,但這個屬性在數(shù)據(jù)表中是以整型值進行存儲的。所以向數(shù)據(jù)表寫入或者查詢數(shù)據(jù)時,要進行類型轉(zhuǎn)換。寫入時,需要將SexEnum轉(zhuǎn)成int。查詢時,則需要把int轉(zhuǎn)成SexEnum。由于這兩個是完全不同的類型,不能通過強轉(zhuǎn)進行轉(zhuǎn)換,所以需要使用一個中間類進行轉(zhuǎn)換,這個中間類就是 EnumOrdinalTypeHandler。這個類會按照枚舉順序進行轉(zhuǎn)換,比如在SexEnum中,MAN的順序是0。存儲時,EnumOrdinalTypeHandler 會將MAN替換為0。查詢時,又會將0轉(zhuǎn)換為MAN。除了EnumOrdinalTypeHandler,MyBatis 還提供了另一個枚舉類型處理器EnumTypeHandler。這個則是按照枚舉的字面值進行轉(zhuǎn)換,比如該處理器將枚舉MAN和字符串 "MAN" 進行相互轉(zhuǎn)換。

上面簡單分析了一下枚舉類型處理器,接下來,繼續(xù)往下看。下面是 ArticleMapper.xml 的配置內(nèi)容:




    
        
        
        
        
        
    

    
        
        
        
        
        
        
    

    

如上,ArticleMapper.xml 中包含了一個一對一的配置,這個配置引用了另一個 id 為authorResult。除了一對一的配置外,這里還有一個自定義類型處理器ArticleTypeHandler需要大家注意。這個自定義類型處理器用于處理ArticleTypeEnum枚舉類型。大家如果注意看前面貼的ArticleTypeEnum的源碼,會發(fā)現(xiàn)每個枚舉值有自己的編號定義。比如JAVA的編號為1,DUBBO的編號為2,SPRING的編號為8。所以這里我們不能再使用EnumOrdinalTypeHandlerArticleTypeHandler進行類型轉(zhuǎn)換,需要自定義一個類型轉(zhuǎn)換器。那下面我們來看一下這個類型轉(zhuǎn)換器的定義。

public class ArticleTypeHandler extends BaseTypeHandler {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, ArticleTypeEnum parameter, JdbcType jdbcType)
        throws SQLException {
        // 獲取枚舉的 code 值,并設(shè)置到 PreparedStatement 中
        ps.setInt(i, parameter.code());
    }

    @Override
    public ArticleTypeEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 從 ResultSet 中獲取 code
        int code = rs.getInt(columnName);
        // 解析 code 對應(yīng)的枚舉,并返回
        return ArticleTypeEnum.find(code);
    }

    @Override
    public ArticleTypeEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return ArticleTypeEnum.find(code);
    }

    @Override
    public ArticleTypeEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return ArticleTypeEnum.find(code);
    }
}

對于自定義類型處理器,可繼承 BaseTypeHandler,并實現(xiàn)相關(guān)的抽象方法。上面的代碼比較簡單,我也進行了一些注釋。應(yīng)該比較好理解,這里就不多說了。

前面貼了實體類,數(shù)據(jù)訪問類,以及 SQL 映射文件。最后還差一個 MyBatis 的配置文件,這里貼出來。如下:



    

    
        
        
    

    
        
    

    
        
            
            
                
                
                
                
            
        
    

    
        
        
    

下面通過一個表格簡單解釋配置中出現(xiàn)的一些標簽。

標簽名稱 用途
properties 用于配置全局屬性,這樣在配置文件中,可以通過占位符 ${} 進行屬性值配置
typeAliases 用于定義別名。如上所示,這里把xyz.coolblog.model.ArticleDO的別名定義為Article,這樣在 SQL 映射文件中,就可以直接使用別名,而不用每次都輸入長長的全限定類名了
typeHandlers 用于定義全局的類型處理器,如果這里配置了,SQL 映射文件中就不需要再次進行配置。前面為了講解需要,我在 SQL 映射文件中也配置了 ArticleTypeHandler,其實是多余的
environments 用于配置事務(wù),以及數(shù)據(jù)源
mappers 用于配置 SQL 映射文件的位置信息

以上僅介紹了一些比較常用的配置,更多的配置信息,建議大家去閱讀MyBatis 官方文檔。

到這里,我們把所有的準備工作都做完了。那么接下來,寫點測試代碼測試一下。

public class MyBatisTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void prepare() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        inputStream.close();
    }
    
    @Test
    public void testOne2One() {
        SqlSession session = sqlSessionFactory.openSession();
        try {
            ArticleDao articleDao = session.getMapper(ArticleDao.class);
            ArticleDO article = articleDao.findOne(1);

            AuthorDO author = article.getAuthor();
            article.setAuthor(null);

            System.out.println();
            System.out.println("author info:");
            System.out.println(author);
            System.out.println();
            System.out.println("articles info:");
            System.out.println(article);
        } finally {
            session.close();
        }
    }

    @Test
    public void testOne2Many() {
        SqlSession session = sqlSessionFactory.openSession();
        try {
            AuthorDao authorDao = session.getMapper(AuthorDao.class);
            AuthorDO author = authorDao.findOne(1);

            List arts = author.getArticles();
            List articles = Arrays.asList(arts.toArray(new ArticleDO[arts.size()]));
            arts.clear();

            System.out.println();
            System.out.println("author info:");
            System.out.println(author);
            System.out.println();
            System.out.println("articles info:");
            articles.forEach(System.out::println);
        } finally {
            session.close();
        }
    }
}

第一個測試方法用于從數(shù)據(jù)庫中查詢某篇文章,以及相應(yīng)作者的信息。它的運行結(jié)果如下:

第二個測試方法用于查詢某位作者,及其所寫的所有文章的信息。它的運行結(jié)果如下:

到此,MyBatis 的使用方法就介紹完了。由于我個人在平時的工作中,也知識使用了 MyBatis 的一些比較常用的特性,所以本節(jié)的內(nèi)容也比較淺顯。另外,由于演示示例比較簡單,這里也沒有演示 MyBatis 比較重要的一個特性 -- 動態(tài) SQL。除了以上所述,有些特性由于沒有比較好的場景去演示,這里也就不介紹了。比如 MyBatis 的插件機制,緩存等。對于一些較為生僻的特性,比如對象工廠,鑒別器。如果不是因為閱讀了 MyBatis 的文檔和一些書籍,我還真不知道它們的存在,孤陋寡聞了。所以,對于這部分特性,本文也不會進行說明。

綜上所述,本節(jié)所演示的是一個比較簡單的示例,并非完整示例,望周知。

4.2 在 Spring 中使用

在上一節(jié),我演示了多帶帶使用 MyBatis 的過程。在實際開發(fā)中,我們一般都會將 MyBatis 和 Spring 整合在一起使用。這樣,我們就可以通過 bean 注入的方式使用各種 Dao 接口。MyBatis 和 Spring 原本是兩個完全不相關(guān)的框架,要想把兩者整合起來,需要一個中間框架。這個框架一方面負責加載和解析 MyBatis 相關(guān)配置。另一方面,該框架還會通過 Spring 提供的拓展點,把各種 Dao 接口及其對應(yīng)的對象放入 bean 工廠中。這樣,我們才可以通過 bean 注入的方式獲取到這些 Dao 接口對應(yīng)的 bean。那么問題來了,具有如此能力的框架是誰呢?答案是mybatis-spring。那其他的不多說了,下面開始演示整合過程。

我的測試項目是基于 Maven 構(gòu)建的,所以這里先來看一下 pom 文件的配置。


    

    
        4.3.17.RELEASE
    

    
        
            org.mybatis
            mybatis
            3.4.6
        
        
            org.mybatis
            mybatis-spring
            1.3.2
        

        
            org.springframework
            spring-core
            ${spring.version}
        
        
            org.springframework
            spring-beans
            ${spring.version}
        
        
            org.springframework
            spring-context
            ${spring.version}
        
        
            org.springframework
            spring-jdbc
            ${spring.version}
        
        
            org.springframework
            spring-test
            ${spring.version}
            test
        

        
    

為了減少配置文件所占的文章篇幅,上面的配置經(jīng)過了一定的簡化,這里只列出了 MyBatis 和 Spring 相關(guān)包的坐標。繼續(xù)往下看,下面將 MyBatis 中的一些類配置到 Spring 的配置文件中。



    

    
    
        
        
        
        
    

    
    
        
        
        
        
        
        
    

    
    
        
        
    

如上,上面就是將 MyBatis 整合到 Spring 中所需的一些配置。這里,我們將數(shù)據(jù)源配置到 Spring 配置文件中。配置完數(shù)據(jù)源,接下來配置 SqlSessionFactory,SqlSessionFactory 的用途大家都知道,不用過多解釋了。再接下來是配置 MapperScannerConfigurer,這個類顧名思義,用于掃描某個包下的數(shù)據(jù)訪問接口,并將這些接口注冊到 Spring 容器中。這樣,我們就可以在其他的 bean 中注入 Dao 接口的實現(xiàn)類,無需再從 SqlSession 中獲取接口實現(xiàn)類。至于 MapperScannerConfigurer 掃描和注冊 Dao 接口的細節(jié),這里先不說明,后續(xù)我會專門寫一篇文章分析。

將 MyBatis 配置到 Spring 中后,為了讓我們的程序正常運行,這里還需要為 MyBatis 提供一份配置。相關(guān)配置如下:



    
        
    
    
    
        
        
    

    
        
    

這里的 mybatis-config.xml 和上一節(jié)的配置不太一樣,移除了數(shù)據(jù)源和 SQL 映射文件路徑的配置。需要注意的是,對于 必須配置在 mybatis-config.xml 中。其他的配置都不是必須項,可放在 Spring 的配置文件中,這里偷了個懶。

到此,Spring 整合 MyBatis 的配置工作就完成了,接下來寫點測試代碼跑跑看。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-mybatis.xml")
public class SpringWithMyBatisTest implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    /** 自動注入 AuthorDao,無需再通過 SqlSession 獲取 */ 
    @Autowired
    private AuthorDao authorDao;

    @Autowired
    private ArticleDao articleDao;

    @Before
    public void printBeanInfo() {
        ListableBeanFactory lbf = applicationContext;
        String[] beanNames = lbf.getBeanDefinitionNames();
        Arrays.sort(beanNames);

        System.out.println();
        System.out.println("----------------☆ bean name ☆---------------");
        Arrays.asList(beanNames).subList(0, 5).forEach(System.out::println);
        System.out.println();

        AuthorDao authorDao = (AuthorDao) applicationContext.getBean("authorDao");
        ArticleDao articleDao = (ArticleDao) applicationContext.getBean("articleDao");

        System.out.println("-------------☆ bean class info ☆--------------");
        System.out.println("AuthorDao  Class: " + authorDao.getClass());
        System.out.println("ArticleDao Class: " + articleDao.getClass());
        System.out.println("
--------xxxx---------xxxx---------xxx---------
");
    }


    @Test
    public void testOne2One() {
        ArticleDO article = articleDao.findOne(1);

        AuthorDO author = article.getAuthor();
        article.setAuthor(null);

        System.out.println();
        System.out.println("author info:");
        System.out.println(author);
        System.out.println();
        System.out.println("articles info:");
        System.out.println(article);
    }

    @Test
    public void testOne2Many() {
        AuthorDO author = authorDao.findOne(1);

        System.out.println();
        System.out.println("author info:");
        System.out.println(author);
        System.out.println();
        System.out.println("articles info:");
        author.getArticles().forEach(System.out::println);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

如上代碼,為了證明我們的整合配置生效了,上面專門寫了一個方法,用于輸出ApplicationContextbean的信息。下面來看一下testOne2One測試方法的輸出結(jié)果。

如上所示,bean name 的前兩行就是我們的 Dao 接口的名稱,它們的實現(xiàn)類則是 JDK 的動態(tài)代理生成的。然后testOne2One方法也正常運行了,由此可知,我們的整合配置生效了。

5.總結(jié)

到此,本篇文章就接近尾聲了。本篇文章對 MyBatis 是什么,為何要使用,以及如何使用等三個方面進行闡述和演示。總的來說,本文的篇幅應(yīng)該說清楚了這三個問題。本篇文章的篇幅比較大,讀起來應(yīng)該比較辛苦。不過好在內(nèi)容不難,理解起來應(yīng)該沒什么問題。本篇文章的篇幅超出了我之前的預(yù)期,文章太大,出錯的概率也會隨之上升。所以如果文章有錯誤的地方,希望大家能夠指明。

好了,本篇文章就到這里了,感謝大家的閱讀。

參考

MyBatis 官方文檔

MyBatis從入門到精通 - 劉增輝

MyBatis和Hibernate相比,優(yōu)勢在哪里?- 知乎

mybatis 與 hibernate 的區(qū)別和應(yīng)用場景 - 無法確定文章作者,就不貼鏈接了,請自行搜索

附錄:MyBatis 源碼分析系列文章列表
更新時間 標題
2018-07-16 MyBatis 源碼分析系列文章導讀
2018-07-20 MyBatis 源碼分析 - 配置文件解析過程
本文在知識共享許可協(xié)議 4.0 下發(fā)布,轉(zhuǎn)載需在明顯位置處注明出處
作者:coolblog.xyz
本文同步發(fā)布在我的個人博客:http://www.coolblog.xyz


本作品采用知識共享署名-非商業(yè)性使用-禁止演繹 4.0 國際許可協(xié)議進行許可。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/71623.html

相關(guān)文章

  • Spring AOP 源碼分析系列文章導讀

    摘要:在寫完容器源碼分析系列文章中的最后一篇后,沒敢懈怠,趁熱打鐵,花了天時間閱讀了方面的源碼。從今天開始,我將對部分的源碼分析系列文章進行更新。全稱是,即面向切面的編程,是一種開發(fā)理念。在中,切面只是一個概念,并沒有一個具體的接口或類與此對應(yīng)。 1. 簡介 前一段時間,我學習了 Spring IOC 容器方面的源碼,并寫了數(shù)篇文章對此進行講解。在寫完 Spring IOC 容器源碼分析系列...

    張春雷 評論0 收藏0
  • MyBatis 源碼分析系列文章合集

    摘要:簡介我從七月份開始閱讀源碼,并在隨后的天內(nèi)陸續(xù)更新了篇文章??紤]到超長文章對讀者不太友好,以及拆分文章工作量也不小等問題。經(jīng)過兩周緊張的排版,一本小小的源碼分析書誕生了。我在寫系列文章中,買了一本書作為參考,這本書是技術(shù)內(nèi)幕。 1.簡介 我從七月份開始閱讀MyBatis源碼,并在隨后的40天內(nèi)陸續(xù)更新了7篇文章。起初,我只是打算通過博客的形式進行分享。但在寫作的過程中,發(fā)現(xiàn)要分析的代碼...

    Crazy_Coder 評論0 收藏0
  • Spring IOC 容器源碼分析 - 余下的初始化工作

    摘要:簡介本篇文章是容器源碼分析系列文章的最后一篇文章,本篇文章所分析的對象是方法,該方法用于對已完成屬性填充的做最后的初始化工作。后置處理器是拓展點之一,通過實現(xiàn)后置處理器接口,我們就可以插手的初始化過程。 1. 簡介 本篇文章是Spring IOC 容器源碼分析系列文章的最后一篇文章,本篇文章所分析的對象是 initializeBean 方法,該方法用于對已完成屬性填充的 bean 做最...

    Alfred 評論0 收藏0
  • Spring IOC 容器源碼分析系列文章導讀

    摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續(xù)的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實例,比如下面的測試代碼測試結(jié)果如下本小節(jié),我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業(yè)級應(yīng)用開發(fā)框架,于 2004 年由 Rod Johnson 發(fā)布了 1.0 版本。經(jīng)過十幾年的迭代,現(xiàn)在的 Spring 框架已經(jīng)非常成熟了...

    NSFish 評論0 收藏0
  • 寫這么多系列博客,怪不得找不到女朋友

    摘要:前提好幾周沒更新博客了,對不斷支持我博客的童鞋們說聲抱歉了。熟悉我的人都知道我寫博客的時間比較早,而且堅持的時間也比較久,一直到現(xiàn)在也是一直保持著更新狀態(tài)。 showImg(https://segmentfault.com/img/remote/1460000014076586?w=1920&h=1080); 前提 好幾周沒更新博客了,對不斷支持我博客的童鞋們說聲:抱歉了!。自己這段時...

    JerryWangSAP 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<