基於MyBatis與Spring MVC開發類知乎的簡易問答網站

開發目標

今天我們使用MyBatis和Spring MVC開發一個簡單的問答網站,你可以使用Markdown來發布問題和答案。

這篇文章可以教會你以下知識:

  • MyBatis配置和使用最簡單的姿勢
  • MyBatis+Spring MVC實現一個簡單的Web頁面
  • MyBatis的核心類是幹嘛用的(SqlSessionFactoryBuilder、SqlSessionFactory和SqlSession)
  • MyBatis-Spring如何使用,幫你做了哪些工作?
  • MyBatis Spring Boot Starter如何支持MyBatis,讓你使用MyBatis的門檻降到最低

相信看完本身是你開始使用MyBatis的一個非常棒的開始!

最終的效果圖如下:

Web端的開發我們使用Spring MVC,對Spring MVC還不太熟悉的同學,可以看看天碼營的Spring MVC實戰練習。當然,Spring MVC的部分不是這個練習的重點,也基本不影響大家的理解和練習。

這樣一個系統麻雀雖小,五張俱全,會涉及MyBatis的很多核心知識。MyBatis確實是一個非常簡單易學的ORM框架,很適合作為你學習的第一款Java ORM框架。

完整的教程請參考《基於MyBatis和Spring MVC搭建問答網站》。網站Demo請見MyBatis問答小網站。

ORM是什麼?

ORM是Object Relation Mapping的縮寫,顧名思義,即對象關係映射。

ORM是一種以面向對象的方式來進行資料庫操作的技術。Web開發中常用的語言,都會有對應的ORM框架。而MyBatis就是Java開發中一種常用ORM框架。

簡單地理解,通過Java進行資料庫訪問的正常流程可以分為以下幾步:

  1. 準備好SQL語句
  2. 調用JDBC的API傳入SQL語句,設置參數
  3. 解析JDBC返回的結果

這個過程實際上非常麻煩,比如:

  • 在Java代碼中拼接SQL非常麻煩,而且易於出錯
  • JDBC的代碼調用有很多重複性的代碼
  • 從JDBC返回的結果轉換成領域模型的Java對象很繁瑣

而使用ORM框架,則可以讓我們用面向對象的方式來操作資料庫,比如通過一個簡單的函數調用就完成上面整個流程,直接返回映射為Java對象的結果。這個流程中很大一部分工作其實交給ORM自動化地幫我們執行了。

MyBatis簡介

MyBatis的入門知識最好的材料是其官方網站,而且其絕大部分內容都有中文版本。

官方網站上如下介紹MyBatis:

MyBatis 是支持定製化 SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或標註,將介面和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成資料庫中的記錄。

簡單地理解,你可以認為MyBatis將SQL語句,以及SQL返回結果到Java對象的映射,都放到了一個易於配置的XML文件里了,你的Java代碼就會變得異常簡單。當然,除了XML,MyBatis同時也支持基於標註的方式,但是功能上會有一些限制。總體來說,我們推薦使用XML方式,一些簡單的SQL使用標註會更方便一些。

開發環境

工欲善其事,必先利其器

首先讓我們搭建好本地的開發環境,這裡不會事無巨細地描述環境中每一種工具的安裝步驟和用法,你可以從參考材料以及Google中獲取有用的信息。

Java

我們推薦安裝JavaSE Development Kit 8。

參考Java開發環境安裝與配置

IDE

IDE我們推薦使用Eclipse或IntelliJ IDEA(當然還有很多別的選擇),它們對Maven項目的支持非常完善,自動提示、補全功能異常強大,對於開發效率的提升非常明顯。

參考Eclipse安裝和使用

Maven

Maven是Java世界中最流行的項目構建工具,理論上來說在安裝了IDE後,IDE內部會自帶一個Maven的安裝版本,如果想在命令行工具中使用Maven命令,可以單獨進行安裝。

參考:

  • Maven基本概念
  • 如何通過Maven構建項目
  • 通過Maven進行項目構建與管理

如果想深入了解,推薦Maven實戰。

H2

我們使用內嵌的資料庫H2,如果希望轉換成其他資料庫,比如MySQL,只需修改資料庫連接串即可。當然別忘了要在依賴中增加相應的資料庫訪問驅動包。H2資料庫不需要你任何額外的安裝工作。

創建項目

接下來我們開始創建第一個MyBatis項目吧。新建一個Maven項目,項目結構如下:

.n├── pom.xmln├── srcn│ ├── mainn│ │ └── javan│ │ └── comn│ │ └── tianmayingn

接下來引入MyBatis和H2的依賴:

<dependency>n <groupId>org.mybatis</groupId>n <artifactId>mybatis</artifactId>n <version>3.4.0</version>n</dependency>n<dependency>n <groupId>com.h2database</groupId>n <artifactId>h2</artifactId>n <version>1.4.192</version>n <scope>runtime</scope>n</dependency>n

既然是ORM的練習,那麼我們先設計Object,在設計Relation,最後來來看怎麼做Mapping。

領域類設計

關於領域類設計,貪吃蛇的遊戲的練習中有一個簡單講解,請參考如何開始面向對象設計。

分析我們的業務場景,可以設計三個類:

  • Question表示問題
  • Answer表示回答
  • Tag表示問題標籤

其中:

  • Question可以有多個Answer,一個Answer對應唯一一個Question,一對多的關係
  • Question可以有多個Tage,一個Tag可用於多個Question,多對多的關係

這三個類的關係用UML圖如下:

UML中用菱形+橫線的方式,表示包含關係。同樣是包含,大家注意左側的菱形是填充的,而右側沒有填充,這兩者的區別在於:

  • Question消失時,Answer必然也消失了,它們有相同的生命周期,皮之不存,毛將焉附嘛
  • Question消失時,Tag則可以仍然存在,因為Tag還可以標註其他問題

現在我們暫時不管Answer和Tag,只關注Question,先從最簡單的單表情況開始學習。Question的代碼如下:

public class Question implements Serializable {nn private Long id;n private String title;n private String description;n private Date createdTime;n private List<Tag> tags;n private List<Answer> answers;n}n

這裡Question實現了Serializable介面,在這一節的練習中不是必須的。實現這個介面是為了後面緩存查詢結果是需要。

資料庫設計

除了設計用於Question,Answer和Tag數據的表,我們還需要定義一張維護Question和Tag之間多對多關係的表。最終資料庫設計如下:

這個階段你只需關注question表即可。

添加MyBatis的配置

接下來我們添加MyBatis配置,在resources目錄下建立mybatis.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>n<!DOCTYPE configurationn PUBLIC "-//mybatis.org//DTD Config 3.0//EN"n "http://mybatis.org/dtd/mybatis-3-config.dtd">nn<configuration>nn <properties>n <property name="url" value="jdbc:h2:mem:dataSource;INIT=RUNSCRIPT FROM classpath:database/schema.sql;RUNSCRIPT FROM classpath:database/data.sql" />n </properties>nn <environments default="development">n <environment id="development">n <transactionManager type="JDBC"/>n <dataSource type="POOLED">n <property name="driver" value="org.h2.Driver"/>n <property name="url" value="${url}"/>n </dataSource>n </environment>n </environments>n <mappers>n <mapper resource="com/tianmaying/qa/mapper/QuestionMapper.xml"/>n </mappers>n</configuration>n

這就是一個最簡單的MyBatis配置文件,定義了數據源和mapper文件的位置。

注意H2的連接串中執行了創建資料庫表和插入初始化數據的腳本,天碼營已經為你準備了一些數據。

關於MyBatis配置

關於MyBatis本身的配置請參加官方文檔的介紹。

後面你會發現有了Spring和Spring Boot支持,很多配置不需要我們手動設置了,比如映射文件位置、數據源和事務管理器等。這個文件需要的內容非常少,甚至可以不需要這個文件了。

需要修改MyBatis配置文件的幾種常見情況包括:

  • 要增加插件(比如後面我們看到的分頁插件)
  • 修改MyBatis的運行時行為,參考settings的選項
  • 重寫類型處理器或創建自定義的類型處理器來處理非標準的類型,天碼營的這個練習中不會涉及

<mapper resource="com/tianmaying/qa/mapper/QuestionMapper.xml"/>中表示com/tianmaying/qa/mapper/目錄下的QuestionMapper.xml是定義對象關係映射的XML文件,馬上就能看到它長什麼樣子。

定義Mapper介面

先來定義Mapper的Java介面,這是我們資料庫訪問的介面:

package com.tianmaying.qa.mapper;nnimport com.tianmaying.qa.model.Question;nimport org.apache.ibatis.annotations.Param;nnpublic interface QuestionMapper {nn Question findOne(@Param("id") Long id);nn}n

@Param是MyBatis提高的一個標註,表示id會解析成SQL語句(SQL語句會在XML配置或者標註中)的參數。

使用XML定義Mapper

對應於Mapper介面,還需要通過XML來給出Mapper的實現。我們將映射文件一般放在resources目錄下的com/tianmaying/qa/mapper/子目錄(這是一個四層目錄,與Mapper的包名一致)。

<?xml version="1.0" encoding="UTF-8"?>n<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" n"http://mybatis.org/dtd/mybatis-3-mapper.dtd">nn<mapper namespace="com.tianmaying.qa.mapper.QuestionMapper">n <cache />nn <select id="findOne" resultType="com.tianmaying.qa.model.Question">n SELECT * FROM question WHERE question.id = #{id}n </select>nn</mapper>n

Mapper的配置是MyBatis的精華,我們暫時不做詳解。這裡你注意兩點即可:

  • 對應於每一個Mapper的Java介面方法,XML配置中有對應的一個元素來描述其SQL語句
  • resultMap元素定義了資料庫返回(一行記錄)如何映射到一個Java對象

使用Mapper

已經有了一個可以根據id獲取問題的介面方法,接下來就可以進行調用了:

public static void main(String[] args) {nn // 準備工作n InputStream inputStream = null;n try {n // CONFIG_LOCATION的值即為MyBatis配置文件的路徑n inputStream = Resources.getResourceAsStream(CONFIG_LOCATION);n } catch (IOException e) {n e.printStackTrace();n }nn SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);n SqlSession sqlSession = sqlSessionFactory.openSession();nn try {n // 獲取mapper n QuestionMapper questionMapper = sqlSession.getMapper(QuestionMapper.class);n // 調用mapper方法n Question question = questionMapper.findOne((long) 1);n System.out.println(question);n } finally {n // 最後一定關閉SqlSessionn sqlSession.close();n }n}n

把這個Main方法Run起來你就能看到結果了。調用看起來有點複雜,這裡涉及MyBatis的幾個關鍵類:

  • SqlSessionFactoryBuilder
  • SqlSessionFactory
  • SqlSession

其實後面我們會引入Spring Boot,你會發現這幾個類都被屏蔽掉了,你只需專註於寫Mapper即可。不過了解一下這幾個類,對於我們調試程序和理解MyBatis都是大有裨益的。

引入Spring Boot

上一個練習我們了解了MyBatis的基本用法,但是一個控制台應用顯然不夠酷,我們接下來就開始在Web應用中來使用MyBatis吧。

首先我們引入Spring Boot,為項目的POM文件添加一個父POM,在POM文件中的project根元素中添加:

<parent>n <groupId>org.springframework.boot</groupId>n <artifactId>spring-boot-starter-parent</artifactId>n <version>1.4.1.RELEASE</version>n <relativePath/> n</parent>n

然後添加以下依賴:

<dependency>n <groupId>org.springframework.boot</groupId>n <artifactId>spring-boot-devtools</artifactId>n</dependency>n<dependency>n <groupId>org.springframework.boot</groupId>n <artifactId>spring-boot-starter-thymeleaf</artifactId>n</dependency>n<dependency>n <groupId>org.springframework.boot</groupId>n <artifactId>spring-boot-starter-web</artifactId>n</dependency>n<dependency>n <groupId>net.sourceforge.nekohtml</groupId>n <artifactId>nekohtml</artifactId>n</dependency>n

關於Spring Boot請參考以下資料:

  • 如何創建Spring Boot項目
  • Spring Boot——開發新一代Spring Java應用

編寫Controller和頁面

我們使用Spring MVC和Thymeleaf來實現前端頁面。

這裡有一個非常適合新手看得Spring MVC的快速入門。更深入地學習Spring MVC請參考Spring MVC實戰練習。

這裡我們先寫出Controller的代碼骨架:

@Controllernpublic class QuestionController {nn @GetMapping("/{id}")n public String showQuestion(@PathVariable Long id, Model model) {n // 1. 獲取Mappern // 2. 調用Mapper方法獲取Question n // 3. 填充用以渲染頁面的模型,這裡即是Question實例n model.addAttribute("question", question);nn // 返回模板名稱n return "question";n }nn}n

Thymeleaf的頁面細節這裡不再詳述。我們把上節練習中main函數的代碼放到Controller中來就基本完成一個Web頁面的開發了。

不過這裡我們可以做一些優化,而不是簡單把代碼搬過來用。優化的過程中來進一步理解MyBatis的幾個核心類。這幾個核心類的討論你可以參考官方文檔。

SqlSessionFactory和SelSessionFactoryBuilder

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder這個類是用來創建SqlSessionFactory的,一旦創建了SqlSessionFactory實例,就不再需要它了。因此SqlSessionFactoryBuilder 實例的最佳範圍是方法範圍(也就是局部方法變數)。可以重用SqlSessionFactoryBuilder 來創建多個 SqlSessionFactory 實例,但是最好不要讓其一直存在,比如通過一個類的成員去引用是不推薦的。

SqlSessionFactory

SqlSessionFactory一旦被創建就應該在應用的運行期間一直存在,沒有任何理由對它進行清除或重建。使用SqlSessionFactory的最佳實踐是在應用運行期間不要重複創建多次,多次重建SqlSessionFactory被視為一種代碼「壞味道(bad smell)」。因此SqlSessionFactory的最佳範圍是應用範圍。有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式。

通過單例創建SqlSessionFactory

可見,SqlSessionFactoryBuilder應該用完即扔,而SqlSessionFactory應該只創建一次且在整個應用內存在。因此我們這裡使用單例模式來獲得SqlSessionFactory實例。

package com.tianmaying.qa.mapper;nnimport org.apache.ibatis.io.Resources;nimport org.apache.ibatis.session.SqlSessionFactory;nimport org.apache.ibatis.session.SqlSessionFactoryBuilder;nnimport java.io.IOException;nimport java.io.InputStream;nnpublic class SqlSessionFactoryManager {nn private static final String CONFIG_LOCATION = "mybatis.xml";nn private static SqlSessionFactory sqlSessionFactory;nn // 靜態單例模式n public static SqlSessionFactory getSqlSessionFactory() {n if (sqlSessionFactory == null) {n InputStream inputStream = null;n try {n inputStream = Resources.getResourceAsStream(CONFIG_LOCATION);n } catch (IOException e) {n e.printStackTrace();n return null;n }n // ** SqlSessionFactoryBuilder用完即扔n sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);n }nn return sqlSessionFactory;n }n}n

基於這樣一個輔助類,獲取應用的SqlSessionFactory實例可以如下方式:

SqlSessionFactory sqlSessionFactory = SqlSessionFactoryManager.getSqlSessionFactory();n

SqlSession

SqlSession實例不是線程安全的,因此是不能被共享的,所以它的最佳的範圍是請求或方法範圍。不能將SqlSession實例的引用放在一個類的靜態域,甚至一個類的實例變數也不行。也不能將SqlSession實例的引用放在任何類型的管理範圍中,比如Serlvet中的HttpSession。如果你現在正在使用一種 Web 框架,要考慮 SqlSession 放在一個和 HTTP 請求對象相似的範圍中。換句話說,每次收到的 HTTP 請求,就可以打開一個 SqlSession,返回一個響應,就關閉它。這個關閉操作是很重要的,應該把這個關閉操作放到 finally 塊中以確保每次都能執行關閉。下面的示例就是一個確保 SqlSession 關閉的標準模式:

SqlSession session = sqlSessionFactory.openSession();ntry {n // do workn} finally {n session.close();n}n

了解了以上三個類之後,Controller最終的代碼實現為:

@Controllernpublic class QuestionController {nn @GetMapping("/{id}")n public String showQuestion(@PathVariable Long id, Model model) {n SqlSession sqlSession = SqlSessionFactoryManager.getSqlSessionFactory().openSession();nn try {n QuestionMapper questionMapper = sqlSession.getMapper(QuestionMapper.class);n model.addAttribute("question", questionMapper.findOne(id));n return "question";n } finally {n sqlSession.close();n }n }nn}n

啟動Spring Boot應用

修改MybatisQaApplication的代碼,使之成為一個Spring Boot應用:

@SpringBootApplicationn@Controllernpublic class MybatisQaApplication {nn public static void main(String[] args) {n SpringApplication.run(MybatisQaApplication.class, args);n }nn}n

在resources目錄下增加一個應用配置文件application.properties,內容為:

spring.thymeleaf.mode=LEGACYHTML5n

這一行配置是為了讓Thymeleaf支持HTML5。

此時你在命令行或者IDE中運行mvn spring-boot:run就可以啟動應用,訪問http:localhost:8080/1這樣的URL就能獲取id為1的問題的詳細信息了。

什麼是MyBatis-Spring

MyBatis官方文檔中這樣介紹MyBatis-Spring:

MyBatis-Spring 會幫助你將 MyBatis 代碼無縫地整合到 Spring 中。 使用這個類庫中的類, Spring 將會載入必要的 MyBatis 工廠類和 session 類。 這個類庫也提供一個簡單的方式來注入 MyBatis 數據映射器和 SqlSession 到業務層的 bean 中。 而且它也會處理事務, 翻譯 MyBatis 的異常到 Spring 的 DataAccessException 異常(數據訪問異常,譯者注)中。最終,它不會依賴於 MyBatis,Spring 或 MyBatis-Spring 來構建應用程序代碼。

總之MyBatis-Spring幫我們做了很多事情,最核心的有兩點:

  • Spring 將會載入必要的 MyBatis 工廠類和 session 類:這意味著不需要我們自己去創建SqlSessionFactory實例和SqlSession實例了,幫我們從MyBatis底層API中解放出來啦

  • 提供一個簡單的方式來注入 MyBatis 數據映射器和 SqlSession 到業務層的 bean 中:使用Spring當然要使用依賴注入了,不需要我們自己手動創建Mapper了,直接注入即可

如何理解下面這句話呢?

最終,它不會依賴於 MyBatis,Spring 或 MyBatis-Spring 來構建應用程序代碼。

正因為MyBatis-Spring幫我們做了這些事,我們的業務代碼就可以完全屏蔽MyBatis的API了,對應於Controller的代碼,直觀上理解就是你將不會看到關於MyBatis相關類的import了。

來看實例吧。

引入MyBatis-Spring

要使用MyBatis-Spring模塊,只需在POM文件中引入必要的依賴即可,這裡我們引入1.3.0版本:

<dependency>n <groupId>org.mybatis</groupId>n <artifactId>mybatis-spring</artifactId>n <version>1.3.0</version>n</dependency>n

XML配置

MyBatis-Spring引入了SqlSessionFactoryBean來創建SqlSessionFactory,這是一個工廠Bean,在XML中如下配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">n <property name="dataSource" ref="dataSource" />n</bean>n

注意SqlSessionFactoryBean不是MyBatis本身的類,是MyBatis-Spring這個模塊引入的類,還記得上一個練習中我們直接使用MyBatis時,是通過SqlSessionFactoryBuilder來創建SqlSessionFactory的吧。

SqlSessionFactoryBean必須設置一個數據源,數據源的配置方式和普通的Spring應用沒有任何區別。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" n destroy-method="close"> n <property name="driverClassName" value="org.h2.Driver" /> n <property name="url" n value="jdbc:h2:mem:dataSource;INIT=RUNSCRIPT FROM classpath:database/schema.sql;RUNSCRIPT FROM classpath:database/data.sql" /> n</bean>n

配置好SqlSessionFactoryBean之後,可以配置Mapper成為Spring Bean,這樣就能在Controller中使用了。

<bean id="questionMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">n <property name="mapperInterface" value="com.tianmaying.qa.QuestionMapper" />n <property name="sqlSessionFactory" ref="sqlSessionFactory" />n</bean>n

mapperInterface屬性所指定的映射器類必須是一個介面,不能是一個具體的實現類,QuestionMapper顯然符合這個要求。

這樣在Controller中就可以使用questionMapper這個Bean了。

Java配置

上一小節是通過XML來配置數據源、SqlSessionFactoryBean和Mapper。我們也可以通過Java來進行配置,有關Spring Java配置的知識請參考Java裝配方式。

這裡我們創建一個AppConfig配置類來專門放置這些配置代碼:

package com.tianmaying.qa;nnimport com.tianmaying.qa.mapper.QuestionMapper;nimport org.apache.ibatis.session.SqlSessionFactory;nimport org.mybatis.spring.SqlSessionFactoryBean;nimport org.mybatis.spring.mapper.MapperFactoryBean;nimport org.springframework.context.annotation.Bean;nimport org.springframework.context.annotation.Configuration;nimport org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;nimport org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;nnimport javax.sql.DataSource;nn@Configurationnpublic class AppConfig {nn @Beann public DataSource dataSource() {n return new EmbeddedDatabaseBuilder()n .setType(EmbeddedDatabaseType.H2)n .addScript("classpath:database/schema.sql")n .addScript("classpath:database/data.sql")n .build();n }nn @Beann public SqlSessionFactory sqlSessionFactory() {n SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();n sqlSessionFactoryBean.setDataSource(dataSource());nn try {n return sqlSessionFactoryBean.getObject();n } catch (Exception e) {n e.printStackTrace();n }n return null;n }nn @Beann public MapperFactoryBean questionMapperFactoryBean() {n MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(QuestionMapper.class);n mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory());n return mapperFactoryBean;n }n}n

在Java配置中要注意工廠方法的Bean的寫法,要通過調用getObject()返回目標Bean,而不是創建目標的工廠Bean。

使用Java配置時,為了使用EmbeddedDatabaseBuilder,你需要在POM文件中增加Spring JDBC相關的依賴,這裡我們把JDBC Starter加入進來即可:

<dependency>n <groupId>org.springframework.boot</groupId>n <artifactId>spring-boot-starter-jdbc</artifactId>n</dependency>n

Controller層的代碼

此時Controller的代碼變得前所未有的簡單:

@Controllernpublic class QuestionController {nn @Autowiredn QuestionMapper questionMapper;nn @GetMapping("/{id}")n public String showQuestion(@PathVariable Long id, Model model) {n model.addAttribute("question", questionMapper.findOne(id));n return "question";n }nn}n

你再也看不到一堆創建SqlSessionFactory和SqlSession的代碼了,MyBatis-Spring都已經幫你做了,你要做的就是自動裝配一個Mapper,然後調用方法操作資料庫。

所以你現在應該更清楚為什麼官方文檔這麼說了吧:

最終,它不會依賴於 MyBatis,Spring 或 MyBatis-Spring 來構建應用程序代碼。

SqlSessionFactoryManager這個類此時也沒有存在的必要了。

關於SqlSessionFactoryBean

SessionFactoryBean還有一個屬性是configLocation,它是用來指定MyBatis的XML配置文件路徑的。

如果你希望修改MyBatis的一些基礎配置,比如你希望修改Mybatis配置文件中的<settings> 或<typeAliases>部分,那麼修改後的配置文件的路徑要在configLocation屬性中設置。

比如為了啟動延遲載入,可以如下配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">n <property name="dataSource" ref="dataSource" />n <property name="mapperLocations" value="classpath*:classpath:mybatis-config.xml" />n</bean>n

與此同時,在mybatis-config.xml中要進行進行lazyLoadingEnabled設置:

<?xml version="1.0" encoding="UTF-8" ?>n<!DOCTYPE configurationn PUBLIC "-//mybatis.org//DTD Config 3.0//EN"n "http://mybatis.org/dtd/mybatis-3-config.dtd">nn<configuration>nn <properties>n <property name="url" value="jdbc:h2:mem:dataSource;INIT=RUNSCRIPT FROM classpath:database/schema.sql;RUNSCRIPT FROM classpath:database/data.sql" />n </properties>nn <!-- ** 注意這裡設置了lazyLoadingEnabled -->n <settings>n <setting name="lazyLoadingEnabled" value="true"/>n </settings>nn <environments default="development">n <environment id="development">n <transactionManager type="JDBC"/>n <dataSource type="POOLED">n <property name="driver" value="org.h2.Driver"/>n <property name="url" value="${url}"/>n </dataSource>n </environment>n </environments>n <mappers>n <mapper resource="com/tianmaying/qa/mapper/QuestionMapper.xml"/>n </mappers>n</configuration>n

另一種方式是,你可以直接設置configuation屬性,下面的配置和前面的做法是等價的,好處是不用修改MyBatis配置文件。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">n <property name="dataSource" ref="dataSource" />n <property name="configuration">n <bean class="org.apache.ibatis.session.Configuration">n <property name="lazyLoadingEnabled" value="true"/>n </bean>n </property>n</bean>n

這些配置你在這個練習中並不會涉及。你需要注意的一點是:MyBatis的配置文件中的數據源和事務相關的配置將會被MyBatis-Spring忽略,即MyBatis XML配置中的<enviroment>元素會被忽略,MyBatis-Spring只會使用Spring Context下定義的數據源和事務管理器。

MyBatis Spring Boot Starter

軟體技術發展的一個驅動力之一就是不斷屏蔽底層的複雜性,使用更自然更易於理解的模型。這樣一個道理從我們這個簡單的練習課程就可見一斑,比如:

  • 最早的數據存儲技術是直接讀寫文件,為了屏蔽物理數據讀寫的複雜性,出現了資料庫技術;
  • 為了支持使用更自然的面向對象模型來訪問資料庫,出現了第一個練習中介紹的ORM框架,比如MyBatis;
  • 為了屏蔽MyBatis底層的API,MyBatis-Spring出現,讓使用MyBatis的代碼更加易於維護,第二個練習中你可以發現Controller的代碼因為MyBatis-Spring變得更加簡潔了;
  • 即使使用MyBatis-Spring,我們發現還需做一些麻煩的事情,比如配置數據源、 配置SqlSessionFactoryBean和配置映射器(Mapper)等工作;如何屏蔽這些麻煩的事情呢,這就需要MyBatis Spring Boot Stater粉墨登場了!

其實再往大了說,人類就是不斷發明和創造出更好的工具來生存和發展的。細到程序員這個群體也是,我們非常「懶」,遇到"不爽"的地方,就會出現更新更好的技術、平台、工具或者框架來解決問題。這樣一種技術發展動力從天碼營的另外一篇文章《Web開發技術的發展歷史》也能窺見一斑。

關於Spring Boot請參考以下資料:

  • 如何創建Spring Boot項目
  • Spring Boot——開發新一代Spring Java應用

Spring Boot的Starter可以認為是一種方便的依賴描述符,需要集成某種框架或者功能(如集成JPA、Thymeleaf或者MongoDB)時,無需關注各種依賴庫的處理,無需具體的配置信息,由Spring Boot自動掃描類路徑創建所需的Bean。比如把spring-boot-starter-data-jpa的依賴添加到POM文件中,你就可以方便地使用JPA進行資料庫訪問了。

MyBatis Spring Boot Starter可以使得你使用MyBatis變得無比簡單,比如:

  • 自動掃描類路徑下資料庫驅動類,創建DataSouce實例
  • 基於DataSource自動創建SqlSessionFactoryBean實例
  • 自動掃描映射器相關類,生成所需要Mapper實例

總是呢,就是自動幫你做了很多事,讓你開發更加輕鬆愜意。

引入MyBatis Spring Boot Starter

添加一個依賴即可引入MyBatis Spring Boot Starter:

<dependency>n <groupId>org.mybatis.spring.boot</groupId>n <artifactId>mybatis-spring-boot-starter</artifactId>n</dependency>n

有了這個依賴,MyBatis和MyBatis-Spring的依賴都不再需要,他們會通過mybatis-spring-boot-starter間接地引入。

此外還需在application.properties中添加幾項配置:

spring.datasource.schema=classpath:database/schema.sqlnspring.datasource.data=classpath:database/data.sqlnmybatis.config-location=classpath:mybatis.xmln

相比於原來的數據源配置:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" n destroy-method="close"> n <property name="driverClassName" value="org.h2.Driver" /> n <property name="url" n value="jdbc:h2:mem:dataSource;INIT=RUNSCRIPT FROM classpath:database/schema.sql;RUNSCRIPT FROM classpath:database/data.sql" /> n</bean>n

已經得到了很大的簡化,而且H2資料庫初始化的語法也幫我們屏蔽了。

此外,mybatis.config-location=classpath:mybatis.xml在仍然還需要引用MyBatis的配置文件的情況下需要進行設置。也就是說修改MyBatis本身的一些行為,你還是需要藉助於MyBatis的配置文件的,那麼為了讓MyBatis Spring Boot Starter知道你對配置文件的修改,你需要做這個設置。

事實上在最後一次練習之前,這個配置基本都是不需要的。

更簡潔的代碼

為了讓Spring Boot能夠自動掃描到Mapper類,為其創建Bean實例,只需給Mapper添加@Mapper標註。

@Mappernpublic interface QuestionMapper {n Question findOne(@Param("id") Long id);n}n

可以發現,MyBatis-Spring的引入消除了SqlSessionFactoryManager類,而這一次Starter的引入則使得AppConfig整個配置類(或者對應的Spring XML配置)此時也不再需要。

回顧一下AppConfig這個類,這裡面做的所有事情Spring Boot都已經幫我們做好了,這也可以讓我們更清楚第一小節中所說的Spring Boot Starter的引入如何簡化開發。

package com.tianmaying.qa;nnimport com.tianmaying.qa.mapper.QuestionMapper;nimport org.apache.ibatis.session.SqlSessionFactory;nimport org.mybatis.spring.SqlSessionFactoryBean;nimport org.mybatis.spring.mapper.MapperFactoryBean;nimport org.springframework.context.annotation.Bean;nimport org.springframework.context.annotation.Configuration;nimport org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;nimport org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;nnimport javax.sql.DataSource;nn@Configurationnpublic class AppConfig {nn @Beann public DataSource dataSource() {n return new EmbeddedDatabaseBuilder()n .setType(EmbeddedDatabaseType.H2)n .addScript("classpath:database/schema.sql")n .addScript("classpath:database/data.sql")n .build();n }nn @Beann public SqlSessionFactory sqlSessionFactory() {n SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();n sqlSessionFactoryBean.setDataSource(dataSource());nn try {n return sqlSessionFactoryBean.getObject();n } catch (Exception e) {n e.printStackTrace();n }n return null;n }nn @Beann public MapperFactoryBean questionMapperFactoryBean() {n MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(QuestionMapper.class);n mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory());n return mapperFactoryBean;n }n}n

沒錯,把這個類的代碼貼出來,是想告訴你,你可以和這個類Say Goodbye啦!

想進一步深入了MyBatis問答網站的更多細節知識,請閱讀完整的教程

歡迎關注天碼營微信公眾號: TMY-EDU

小編重點推薦:

Spring MVC實戰入門訓練

Java貪吃蛇的設計與實現

一起來寫網易雲音樂Java爬蟲

Spring Data JPA實戰入門訓練

Java Web實戰訓練


推薦閱讀:

webservice,註解mybatis問題?
面試官會問關於spring的哪些問題?

TAG:SpringBoot | MyBatis | SpringMVC框架 |