MyBatis入門實例:整合Spring MVC與MyBatis開發問答網站
開發目標
今天我們使用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框架。
ORM是什么?
ORM是Object Relation Mapping的縮寫,顧名思義,即對象關系映射。
ORM是一種以面向對象的方式來進行數據庫操作的技術。Web開發中常用的語言,都會有對應的ORM框架。而MyBatis就是Java開發中一種常用ORM框架。
簡單地理解,通過Java進行數據庫訪問的正常流程可以分為以下幾步:
- 準備好SQL語句
- 調用JDBC的API傳入SQL語句,設置參數
- 解析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 。
IDE
IDE我們推薦使用 Eclipse 或 IntelliJ IDEA (當然還有很多別的選擇),它們對Maven項目的支持非常完善,自動提示、補全功能異常強大,對于開發效率的提升非常明顯。
參考 Eclipse安裝和使用
Maven
Maven是Java世界中最流行的項目構建工具,理論上來說在安裝了IDE后,IDE內部會自帶一個Maven的安裝版本,如果想在命令行工具中使用Maven命令,可以單獨進行安裝。
參考:
如果想深入了解,推薦 Maven實戰 。
H2
我們使用內嵌的數據庫H2,如果希望轉換成其他數據庫,比如MySQL,只需修改數據庫連接串即可。當然別忘了要在依賴中增加相應的數據庫訪問驅動包。H2數據庫不需要你任何額外的安裝工作。
創建項目
接下來我們開始創建第一個MyBatis項目吧。新建一個Maven項目,項目結構如下:
.
├── pom.xml
├── src
│ ├── main
│ │ └── java
│ │ └── com
│ │ └── tianmaying
接下來引入MyBatis和H2的依賴:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.192</version>
<scope>runtime</scope>
</dependency>
既然是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 {
private Long id;
private String title;
private String description;
private Date createdTime;
private List<Tag> tags;
private List<Answer> answers;
}</code></pre>
這里Question實現了Serializable接口,在這一節的練習中不是必須的。實現這個接口是為了后面緩存查詢結果是需要。
數據庫設計
除了設計用于Question,Answer和Tag數據的表,我們還需要定義一張維護Question和Tag之間多對多關系的表。最終數據庫設計如下:

這個階段你只需關注question表即可。
添加MyBatis的配置
接下來我們添加MyBatis配置,在resources目錄下建立mybatis.xml文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
";
<configuration>
<properties>
<property name="url" value="jdbc:h2:mem:dataSource;INIT=RUNSCRIPT FROM 'classpath:database/schema.sql'\;RUNSCRIPT FROM 'classpath:database/data.sql'" />
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="org.h2.Driver"/>
<property name="url" value="${url}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/tianmaying/qa/mapper/QuestionMapper.xml"/>
</mappers>
</configuration></code></pre>
這就是一個最簡單的MyBatis配置文件,定義了數據源和mapper文件的位置。
注意H2的連接串中執行了創建數據庫表和插入初始化數據的腳本,天碼營已經為你準備了一些數據。
關于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;
import com.tianmaying.qa.model.Question;
import org.apache.ibatis.annotations.Param;
public interface QuestionMapper {
Question findOne(@Param("id") Long id);
}</code></pre>
<p>@Param是MyBatis提高的一個標注,表示id會解析成SQL語句(SQL語句會在XML配置或者標注中)的參數。</p>
使用XML定義Mapper
對應于Mapper接口,還需要通過XML來給出Mapper的實現。我們將映射文件一般放在resources目錄下的com/tianmaying/qa/mapper/子目錄(這是一個四層目錄,與Mapper的包名一致)。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
";
<mapper namespace="com.tianmaying.qa.mapper.QuestionMapper">
<cache />
<select id="findOne" resultType="com.tianmaying.qa.model.Question">
SELECT * FROM question WHERE question.id = #{id}
</select>
</mapper></code></pre>
Mapper的配置是MyBatis的精華,我們暫時不做詳解。這里你注意兩點即可:
- 對應于每一個Mapper的Java接口方法,XML配置中有對應的一個元素來描述其SQL語句
- resultMap元素定義了數據庫返回(一行記錄)如何映射到一個Java對象
使用Mapper
已經有了一個可以根據id獲取問題的接口方法,接下來就可以進行調用了:
public static void main(String[] args) {
// 準備工作
InputStream inputStream = null;
try {
// CONFIG_LOCATION的值即為MyBatis配置文件的路徑
inputStream = Resources.getResourceAsStream(CONFIG_LOCATION);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 獲取mapper
QuestionMapper questionMapper = sqlSession.getMapper(QuestionMapper.class);
// 調用mapper方法
Question question = questionMapper.findOne((long) 1);
System.out.println(question);
} finally {
// 最后一定關閉SqlSession
sqlSession.close();
}
}</code></pre>
把這個Main方法Run起來你就能看到結果了。調用看起來有點復雜,這里涉及MyBatis的幾個關鍵類:
- SqlSessionFactoryBuilder
- SqlSessionFactory
- SqlSession
其實后面我們會引入Spring Boot,你會發現這幾個類都被屏蔽掉了,你只需專注于寫Mapper即可。不過了解一下這幾個類,對于我們調試程序和理解MyBatis都是大有裨益的。
引入Spring Boot
上一個練習我們了解了MyBatis的基本用法,但是一個控制臺應用顯然不夠酷,我們接下來就開始在Web應用中來使用MyBatis吧。
首先我們引入Spring Boot,為項目的POM文件添加一個父POM,在POM文件中的project根元素中添加:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
<relativePath/>
</parent>
然后添加以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
關于Spring Boot請參考以下資料:
編寫Controller和頁面
我們使用Spring MVC和Thymeleaf來實現前端頁面。
這里我們先寫出Controller的代碼骨架:
@Controller
public class QuestionController {
@GetMapping("/{id}")
public String showQuestion(@PathVariable Long id, Model model) {
// 1. 獲取Mapper
// 2. 調用Mapper方法獲取Question
// 3. 填充用以渲染頁面的模型,這里即是Question實例
model.addAttribute("question", question);
// 返回模板名稱
return "question";
}
}</code></pre>
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;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class SqlSessionFactoryManager {
private static final String CONFIG_LOCATION = "mybatis.xml";
private static SqlSessionFactory sqlSessionFactory;
// 靜態單例模式
public static SqlSessionFactory getSqlSessionFactory() {
if (sqlSessionFactory == null) {
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(CONFIG_LOCATION);
} catch (IOException e) {
e.printStackTrace();
return null;
}
// ** SqlSessionFactoryBuilder用完即扔
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
return sqlSessionFactory;
}
}</code></pre>
基于這樣一個輔助類,獲取應用的SqlSessionFactory實例可以如下方式:
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryManager.getSqlSessionFactory();
SqlSession
SqlSession實例不是線程安全的,因此是不能被共享的,所以它的最佳的范圍是請求或方法范圍。不能將SqlSession實例的引用放在一個類的靜態域,甚至一個類的實例變量也不行。也不能將SqlSession實例的引用放在任何類型的管理范圍中,比如Serlvet中的HttpSession。如果你現在正在使用一種 Web 框架,要考慮 SqlSession 放在一個和 HTTP 請求對象相似的范圍中。換句話說,每次收到的 HTTP 請求,就可以打開一個 SqlSession,返回一個響應,就關閉它。這個關閉操作是很重要的,應該把這個關閉操作放到 finally 塊中以確保每次都能執行關閉。下面的示例就是一個確保 SqlSession 關閉的標準模式:
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
了解了以上三個類之后,Controller最終的代碼實現為:
@Controller
public class QuestionController {
@GetMapping("/{id}")
public String showQuestion(@PathVariable Long id, Model model) {
SqlSession sqlSession = SqlSessionFactoryManager.getSqlSessionFactory().openSession();
try {
QuestionMapper questionMapper = sqlSession.getMapper(QuestionMapper.class);
model.addAttribute("question", questionMapper.findOne(id));
return "question";
} finally {
sqlSession.close();
}
}
}</code></pre>
啟動Spring Boot應用
修改MybatisQaApplication的代碼,使之成為一個Spring Boot應用:
@SpringBootApplication
@Controller
public class MybatisQaApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisQaApplication.class, args);
}
}</code></pre>
在resources目錄下增加一個應用配置文件application.properties,內容為:
spring.thymeleaf.mode=LEGACYHTML5
這一行配置是為了讓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>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
XML配置
MyBatis-Spring引入了SqlSessionFactoryBean來創建SqlSessionFactory,這是一個工廠Bean,在XML中如下配置:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
注意SqlSessionFactoryBean不是MyBatis本身的類,是MyBatis-Spring這個模塊引入的類,還記得上一個練習中我們直接使用MyBatis時,是通過SqlSessionFactoryBuilder來創建SqlSessionFactory的吧。
SqlSessionFactoryBean必須設置一個數據源,數據源的配置方式和普通的Spring應用沒有任何區別。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url"
value="jdbc:h2:mem:dataSource;INIT=RUNSCRIPT FROM 'classpath:database/schema.sql'\;RUNSCRIPT FROM 'classpath:database/data.sql'" />
</bean>
配置好SqlSessionFactoryBean之后,可以配置Mapper成為Spring Bean,這樣就能在Controller中使用了。
<bean id="questionMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.tianmaying.qa.QuestionMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
mapperInterface屬性所指定的映射器類必須是一個接口,不能是一個具體的實現類,QuestionMapper顯然符合這個要求。
這樣在Controller中就可以使用questionMapper這個Bean了。
Java配置
上一小節是通過XML來配置數據源、SqlSessionFactoryBean和Mapper。我們也可以通過Java來進行配置。
這里我們創建一個AppConfig配置類來專門放置這些配置代碼:
package com.tianmaying.qa;
import com.tianmaying.qa.mapper.QuestionMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import javax.sql.DataSource;
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:database/schema.sql")
.addScript("classpath:database/data.sql")
.build();
}
@Bean
public SqlSessionFactory sqlSessionFactory() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
try {
return sqlSessionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Bean
public MapperFactoryBean questionMapperFactoryBean() {
MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(QuestionMapper.class);
mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory());
return mapperFactoryBean;
}
}</code></pre>
在Java配置中要注意工廠方法的Bean的寫法,要通過調用getObject()返回目標Bean,而不是創建目標的工廠Bean。
使用Java配置時,為了使用EmbeddedDatabaseBuilder,你需要在POM文件中增加Spring JDBC相關的依賴,這里我們把JDBC Starter加入進來即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
Controller層的代碼
此時Controller的代碼變得前所未有的簡單:
@Controller
public class QuestionController {
@Autowired
QuestionMapper questionMapper;
@GetMapping("/{id}")
public String showQuestion(@PathVariable Long id, Model model) {
model.addAttribute("question", questionMapper.findOne(id));
return "question";
}
}</code></pre>
你再也看不到一堆創建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">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:classpath:mybatis-config.xml" />
</bean>
與此同時,在mybatis-config.xml中要進行進行lazyLoadingEnabled設置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
";
<configuration>
<properties>
<property name="url" value="jdbc:h2:mem:dataSource;INIT=RUNSCRIPT FROM 'classpath:database/schema.sql'\;RUNSCRIPT FROM 'classpath:database/data.sql'" />
</properties>
<!-- ** 注意這里設置了lazyLoadingEnabled -->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="org.h2.Driver"/>
<property name="url" value="${url}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/tianmaying/qa/mapper/QuestionMapper.xml"/>
</mappers>
</configuration></code></pre>
另一種方式是,你可以直接設置configuation屬性,下面的配置和前面的做法是等價的,好處是不用修改MyBatis配置文件。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="lazyLoadingEnabled" value="true"/>
</bean>
</property>
</bean>
這些配置你在這個練習中并不會涉及。你需要注意的一點是: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粉墨登場了!
其實再往大了說,人類就是不斷發明和創造出更好的工具來生存和發展的。細到程序員這個群體也是,我們非常“懶”,遇到"不爽"的地方,就會出現更新更好的技術、平臺、工具或者框架來解決問題。
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>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
有了這個依賴,MyBatis和MyBatis-Spring的依賴都不再需要,他們會通過mybatis-spring-boot-starter間接地引入。
此外還需在application.properties中添加幾項配置:
spring.datasource.schema=classpath:database/schema.sql
spring.datasource.data=classpath:database/data.sql
mybatis.config-location=classpath:mybatis.xml
相比于原來的數據源配置:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url"
value="jdbc:h2:mem:dataSource;INIT=RUNSCRIPT FROM 'classpath:database/schema.sql'\;RUNSCRIPT FROM 'classpath:database/data.sql'" />
</bean>
已經得到了很大的簡化,而且H2數據庫初始化的語法也幫我們屏蔽了。
此外,mybatis.config-location=classpath:mybatis.xml在仍然還需要引用MyBatis的配置文件的情況下需要進行設置。也就是說修改MyBatis本身的一些行為,你還是需要借助于MyBatis的配置文件的,那么為了讓MyBatis Spring Boot Starter知道你對配置文件的修改,你需要做這個設置。
事實上在最后一次練習之前,這個配置基本都是不需要的。
更簡潔的代碼
為了讓Spring Boot能夠自動掃描到Mapper類,為其創建Bean實例,只需給Mapper添加@Mapper標注。
@Mapper
public interface QuestionMapper {
Question findOne(@Param("id") Long id);
}
可以發現,MyBatis-Spring的引入消除了SqlSessionFactoryManager類,而這一次Starter的引入則使得AppConfig整個配置類(或者對應的Spring XML配置)此時也不再需要。
回顧一下AppConfig這個類,這里面做的所有事情Spring Boot都已經幫我們做好了,這也可以讓我們更清楚第一小節中所說的Spring Boot Starter的引入如何簡化開發。
package com.tianmaying.qa;
import com.tianmaying.qa.mapper.QuestionMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import javax.sql.DataSource;
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:database/schema.sql")
.addScript("classpath:database/data.sql")
.build();
}
@Bean
public SqlSessionFactory sqlSessionFactory() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
try {
return sqlSessionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Bean
public MapperFactoryBean questionMapperFactoryBean() {
MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(QuestionMapper.class);
mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory());
return mapperFactoryBean;
}
}</code></pre>
來自:https://zhuanlan.zhihu.com/p/23517856