MyBatis入门实例:整合Spring MVC与MyBatis开发问答网站

CiaraSchnel 7年前
   <h2>开发目标</h2>    <p>今天我们使用MyBatis和Spring MVC开发一个简单的问答网站,你可以使用Markdown来发布问题和答案。</p>    <p>这篇文章可以教会你以下知识:</p>    <ul>     <li>MyBatis配置和使用最简单的姿势</li>     <li>MyBatis+Spring MVC实现一个简单的Web页面</li>     <li>MyBatis的核心类是干嘛用的(SqlSessionFactoryBuilder、SqlSessionFactory和SqlSession)</li>     <li>MyBatis-Spring如何使用,帮你做了哪些工作?</li>     <li>MyBatis Spring Boot Starter如何支持MyBatis,让你使用MyBatis的门槛降到最低</li>    </ul>    <p>相信看完本身是你开始使用MyBatis的一个非常棒的开始!</p>    <p>最终的效果图如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/afc9fb91f00660d3c29e427e5b06cace.png"></p>    <p>Web端的开发我们使用Spring MVC,对Spring MVC还不太熟悉的同学,可以看看天码营的 Spring MVC实战练习 。当然,Spring MVC的部分不是这个练习的重点,也基本不影响大家的理解和练习。</p>    <p>这样一个系统麻雀虽小,五张俱全,会涉及MyBatis的很多核心知识。MyBatis确实是一个非常简单易学的ORM框架,很适合作为你学习的第一款Java ORM框架。</p>    <h2><strong>ORM是什么?</strong></h2>    <p>ORM是Object Relation Mapping的缩写,顾名思义,即对象关系映射。</p>    <p>ORM是一种以面向对象的方式来进行数据库操作的技术。Web开发中常用的语言,都会有对应的ORM框架。而MyBatis就是Java开发中一种常用ORM框架。</p>    <p>简单地理解,通过Java进行数据库访问的正常流程可以分为以下几步:</p>    <ol>     <li>准备好SQL语句</li>     <li>调用JDBC的API传入SQL语句,设置参数</li>     <li>解析JDBC返回的结果</li>    </ol>    <p>这个过程实际上非常麻烦,比如:</p>    <ul>     <li>在Java代码中拼接SQL非常麻烦,而且易于出错</li>     <li>JDBC的代码调用有很多重复性的代码</li>     <li>从JDBC返回的结果转换成领域模型的Java对象很繁琐</li>    </ul>    <p>而使用ORM框架,则可以让我们用面向对象的方式来操作数据库,比如通过一个简单的函数调用就完成上面整个流程,直接返回映射为Java对象的结果。这个流程中很大一部分工作其实交给ORM自动化地帮我们执行了。</p>    <h2><strong>MyBatis简介</strong></h2>    <p>MyBatis的入门知识最好的材料是其 官方网站 ,而且其绝大部分内容都有中文版本。</p>    <p>官方网站上如下介绍MyBatis:</p>    <p>MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或标注,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。</p>    <p>简单地理解,你可以认为MyBatis将SQL语句,以及SQL返回结果到Java对象的映射,都放到了一个易于配置的XML文件里了,你的Java代码就会变得异常简单。当然,除了XML,MyBatis同时也支持基于标注的方式,但是功能上会有一些限制。总体来说,我们推荐使用XML方式,一些简单的SQL使用标注会更方便一些。</p>    <h2><strong>开发环境</strong></h2>    <p>工欲善其事,必先利其器</p>    <p>首先让我们搭建好本地的开发环境,这里不会事无巨细地描述环境中每一种工具的安装步骤和用法,你可以从参考材料以及Google中获取有用的信息。</p>    <h3><strong>Java</strong></h3>    <p>我们推荐安装 <a href="/misc/goto?guid=4959724405195759096" rel="nofollow,noindex"> JavaSE Development Kit 8 </a> 。</p>    <p>参考 <a href="/misc/goto?guid=4959724405303890000" rel="nofollow,noindex"> Java开发环境安装与配置 </a></p>    <h3><strong>IDE</strong></h3>    <p>IDE我们推荐使用 <a href="/misc/goto?guid=4959724405389352348" rel="nofollow,noindex"> Eclipse </a> 或 <a href="/misc/goto?guid=4959724405481161286" rel="nofollow,noindex"> IntelliJ IDEA </a> (当然还有很多别的选择),它们对Maven项目的支持非常完善,自动提示、补全功能异常强大,对于开发效率的提升非常明显。</p>    <p>参考 <a href="/misc/goto?guid=4959724405577638660" rel="nofollow,noindex"> Eclipse安装和使用 </a></p>    <h3><strong>Maven</strong></h3>    <p>Maven是Java世界中最流行的项目构建工具,理论上来说在安装了IDE后,IDE内部会自带一个Maven的安装版本,如果想在命令行工具中使用Maven命令,可以单独进行安装。</p>    <p>参考:</p>    <ul>     <li><a href="/misc/goto?guid=4959724405669808622" rel="nofollow,noindex">Maven基本概念 </a></li>     <li><a href="/misc/goto?guid=4959724405760104701" rel="nofollow,noindex">如何通过Maven构建项目 </a></li>     <li><a href="/misc/goto?guid=4959724405855255938" rel="nofollow,noindex">通过Maven进行项目构建与管理 </a></li>    </ul>    <p>如果想深入了解,推荐 <a href="/misc/goto?guid=4959724405947154543" rel="nofollow,noindex"> Maven实战 </a> 。</p>    <h3><strong>H2</strong></h3>    <p>我们使用内嵌的数据库H2,如果希望转换成其他数据库,比如MySQL,只需修改数据库连接串即可。当然别忘了要在依赖中增加相应的数据库访问驱动包。H2数据库不需要你任何额外的安装工作。</p>    <h2><strong>创建项目</strong></h2>    <p>接下来我们开始创建第一个MyBatis项目吧。新建一个Maven项目,项目结构如下:</p>    <pre>  <code class="language-java">.  ├── pom.xml  ├── src  │   ├── main  │   │   └── java  │   │       └── com  │   │           └── tianmaying</code></pre>    <p>接下来引入MyBatis和H2的依赖:</p>    <pre>  <code class="language-java"><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></code></pre>    <p>既然是ORM的练习,那么我们先设计Object,在设计Relation,最后来来看怎么做Mapping。</p>    <h2>领域类设计</h2>    <p>关于领域类设计, 贪吃蛇的游戏的练习 中有一个简单讲解,请参考 如何开始面向对象设计 。</p>    <p>分析我们的业务场景,可以设计三个类:</p>    <ul>     <li>Question表示问题</li>     <li>Answer表示回答</li>     <li>Tag表示问题标签</li>    </ul>    <p>其中:</p>    <ul>     <li>Question可以有多个Answer,一个Answer对应唯一一个Question,一对多的关系</li>     <li>Question可以有多个Tage,一个Tag可用于多个Question,多对多的关系</li>    </ul>    <p>这三个类的关系用UML图如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d6300237b3ba511793db6cf883e1b080.png"></p>    <p>UML中用菱形+横线的方式,表示包含关系。同样是包含,大家注意左侧的菱形是填充的,而右侧没有填充,这两者的区别在于:</p>    <ul>     <li>Question消失时,Answer必然也消失了,它们有相同的生命周期,皮之不存,毛将焉附嘛</li>     <li>Question消失时,Tag则可以仍然存在,因为Tag还可以标注其他问题</li>    </ul>    <p>现在我们暂时不管Answer和Tag,只关注Question,先从最简单的单表情况开始学习。Question的代码如下:</p>    <pre>  <code class="language-java">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>    <p>这里Question实现了Serializable接口,在这一节的练习中不是必须的。实现这个接口是为了后面缓存查询结果是需要。</p>    <h2><strong>数据库设计</strong></h2>    <p>除了设计用于Question,Answer和Tag数据的表,我们还需要定义一张维护Question和Tag之间多对多关系的表。最终数据库设计如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/52b23b4cddfca4505348e4566e479639.png"></p>    <p>这个阶段你只需关注question表即可。</p>    <h2><strong>添加MyBatis的配置</strong></h2>    <p>接下来我们添加MyBatis配置,在resources目录下建立mybatis.xml文件:</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="UTF-8" ?>  <!DOCTYPE configuration          PUBLIC "-//mybatis.org//DTD Config 3.0//EN"          "http://mybatis.org/dtd/mybatis-3-config.dtd">    <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>    <p>这就是一个最简单的MyBatis配置文件,定义了数据源和mapper文件的位置。</p>    <p>注意H2的连接串中执行了创建数据库表和插入初始化数据的脚本,天码营已经为你准备了一些数据。</p>    <h3><strong>关于MyBatis配置</strong></h3>    <p>后面你会发现有了Spring和Spring Boot支持,很多配置不需要我们手动设置了,比如映射文件位置、数据源和事务管理器等。这个文件需要的内容非常少,甚至可以不需要这个文件了。</p>    <p>需要修改MyBatis配置文件的几种常见情况包括:</p>    <ul>     <li>要增加插件(比如后面我们看到的分页插件)</li>     <li>修改MyBatis的运行时行为,参考settings的选项</li>     <li>重写类型处理器或创建自定义的类型处理器来处理非标准的类型,天码营的这个练习中不会涉及</li>    </ul>    <p><mapper resource="com/tianmaying/qa/mapper/QuestionMapper.xml"/>中表示com/tianmaying/qa/mapper/目录下的QuestionMapper.xml是定义对象关系映射的XML文件,马上就能看到它长什么样子。</p>    <h2><strong>定义Mapper接口</strong></h2>    <p>先来定义Mapper的Java接口,这是我们数据库访问的接口:</p>    <pre>  <code class="language-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>    <h2><strong>使用XML定义Mapper</strong></h2>    <p>对应于Mapper接口,还需要通过XML来给出Mapper的实现。我们将映射文件一般放在resources目录下的com/tianmaying/qa/mapper/子目录(这是一个四层目录,与Mapper的包名一致)。</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="UTF-8"?>  <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"   "http://mybatis.org/dtd/mybatis-3-mapper.dtd">    <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>    <p>Mapper的配置是MyBatis的精华,我们暂时不做详解。这里你注意两点即可:</p>    <ul>     <li>对应于每一个Mapper的Java接口方法,XML配置中有对应的一个元素来描述其SQL语句</li>     <li>resultMap元素定义了数据库返回(一行记录)如何映射到一个Java对象</li>    </ul>    <h2><strong>使用Mapper</strong></h2>    <p>已经有了一个可以根据id获取问题的接口方法,接下来就可以进行调用了:</p>    <pre>  <code class="language-java">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>    <p>把这个Main方法Run起来你就能看到结果了。调用看起来有点复杂,这里涉及MyBatis的几个关键类:</p>    <ul>     <li>SqlSessionFactoryBuilder</li>     <li>SqlSessionFactory</li>     <li>SqlSession</li>    </ul>    <p>其实后面我们会引入Spring Boot,你会发现这几个类都被屏蔽掉了,你只需专注于写Mapper即可。不过了解一下这几个类,对于我们调试程序和理解MyBatis都是大有裨益的。</p>    <h2><strong>引入Spring Boot</strong></h2>    <p>上一个练习我们了解了MyBatis的基本用法,但是一个控制台应用显然不够酷,我们接下来就开始在Web应用中来使用MyBatis吧。</p>    <p>首先我们引入Spring Boot,为项目的POM文件添加一个父POM,在POM文件中的project根元素中添加:</p>    <pre>  <code class="language-java"><parent>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-parent</artifactId>      <version>1.4.1.RELEASE</version>      <relativePath/>   </parent></code></pre>    <p>然后添加以下依赖:</p>    <pre>  <code class="language-java"><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></code></pre>    <p>关于Spring Boot请参考以下资料:</p>    <ul>     <li><a href="/misc/goto?guid=4959724406034286650" rel="nofollow,noindex">如何创建Spring Boot项目 </a></li>     <li><a href="/misc/goto?guid=4959724406127147760" rel="nofollow,noindex">Spring Boot——开发新一代Spring Java应用 </a></li>    </ul>    <h2><strong>编写Controller和页面</strong></h2>    <p>我们使用Spring MVC和Thymeleaf来实现前端页面。</p>    <p>这里我们先写出Controller的代码骨架:</p>    <pre>  <code class="language-java">@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>    <p>Thymeleaf的页面细节这里不再详述。我们把上节练习中main函数的代码放到Controller中来就基本完成一个Web页面的开发了。</p>    <p>不过这里我们可以做一些优化,而不是简单把代码搬过来用。优化的过程中来进一步理解MyBatis的几个核心类。</p>    <h2><strong>SqlSessionFactory和SelSessionFactoryBuilder</strong></h2>    <h3><strong>SqlSessionFactoryBuilde</strong>r</h3>    <p>SqlSessionFactoryBuilder这个类是用来创建SqlSessionFactory的,一旦创建了SqlSessionFactory实例,就不再需要它了。因此SqlSessionFactoryBuilder 实例的最佳范围是方法范围(也就是局部方法变量)。可以重用SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好不要让其一直存在,比如通过一个类的成员去引用是不推荐的。</p>    <h3><strong>SqlSessionFactory</strong></h3>    <p>SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用SqlSessionFactory的最佳实践是在应用运行期间不要重复创建多次,多次重建SqlSessionFactory被视为一种代码“坏味道(bad smell)”。因此SqlSessionFactory的最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。</p>    <h3><strong>通过单例创建SqlSessionFactory</strong></h3>    <p>可见,SqlSessionFactoryBuilder应该用完即扔,而SqlSessionFactory应该只创建一次且在整个应用内存在。因此我们这里使用单例模式来获得SqlSessionFactory实例。</p>    <pre>  <code class="language-java">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>    <p>基于这样一个辅助类,获取应用的SqlSessionFactory实例可以如下方式:</p>    <pre>  <code class="language-java">SqlSessionFactory sqlSessionFactory = SqlSessionFactoryManager.getSqlSessionFactory();</code></pre>    <h2><strong>SqlSession</strong></h2>    <p>SqlSession实例不是线程安全的,因此是不能被共享的,所以它的最佳的范围是请求或方法范围。不能将SqlSession实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也不能将SqlSession实例的引用放在任何类型的管理范围中,比如Serlvet中的HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的范围中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。下面的示例就是一个确保 SqlSession 关闭的标准模式:</p>    <pre>  <code class="language-java">SqlSession session = sqlSessionFactory.openSession();  try {    // do work  } finally {    session.close();  }</code></pre>    <p>了解了以上三个类之后,Controller最终的代码实现为:</p>    <pre>  <code class="language-java">@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>    <h2><strong>启动Spring Boot应用</strong></h2>    <p>修改MybatisQaApplication的代码,使之成为一个Spring Boot应用:</p>    <pre>  <code class="language-java">@SpringBootApplication  @Controller  public class MybatisQaApplication {        public static void main(String[] args) {          SpringApplication.run(MybatisQaApplication.class, args);      }    }</code></pre>    <p>在resources目录下增加一个应用配置文件application.properties,内容为:</p>    <pre>  <code class="language-java">spring.thymeleaf.mode=LEGACYHTML5</code></pre>    <p>这一行配置是为了让Thymeleaf支持HTML5。</p>    <p>此时你在命令行或者IDE中运行mvn spring-boot:run就可以启动应用,访问http:localhost:8080/1这样的URL就能获取id为1的问题的详细信息了。</p>    <h2><strong>什么是MyBatis-Spring</strong></h2>    <p>MyBatis官方文档中这样介绍 MyBatis-Spring :</p>    <p>MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。 使用这个类库中的类, Spring 将会加载必要的 MyBatis 工厂类和 session 类。 这个类库也提供一个简单的方式来注入 MyBatis 数据映射器和 SqlSession 到业务层的 bean 中。 而且它也会处理事务, 翻译 MyBatis 的异常到 Spring 的 DataAccessException 异常(数据访问异常,译者注)中。最终,它不会依赖于 MyBatis,Spring 或 MyBatis-Spring 来构建应用程序代码。</p>    <p>总之MyBatis-Spring帮我们做了很多事情,最核心的有两点:</p>    <ul>     <li> <p>Spring 将会加载必要的 MyBatis 工厂类和 session 类:这意味着不需要我们自己去创建SqlSessionFactory实例和SqlSession实例了,帮我们从MyBatis底层API中解放出来啦</p> </li>     <li> <p>提供一个简单的方式来注入 MyBatis 数据映射器和 SqlSession 到业务层的 bean 中:使用Spring当然要使用依赖注入了,不需要我们自己手动创建Mapper了,直接注入即可</p> </li>    </ul>    <p>如何理解下面这句话呢?</p>    <p>最终,它不会依赖于 MyBatis,Spring 或 MyBatis-Spring 来构建应用程序代码。</p>    <p>正因为MyBatis-Spring帮我们做了这些事,我们的业务代码就可以完全屏蔽MyBatis的API了,对应于Controller的代码,直观上理解就是你将不会看到关于MyBatis相关类的import了。</p>    <p>来看实例吧。</p>    <h2><strong>引入MyBatis-Spring</strong></h2>    <p>要使用MyBatis-Spring模块,只需在POM文件中引入必要的依赖即可,这里我们引入1.3.0版本:</p>    <pre>  <code class="language-java"><dependency>    <groupId>org.mybatis</groupId>    <artifactId>mybatis-spring</artifactId>    <version>1.3.0</version>  </dependency></code></pre>    <h2><strong>XML配置</strong></h2>    <p>MyBatis-Spring引入了SqlSessionFactoryBean来创建SqlSessionFactory,这是一个工厂Bean,在XML中如下配置:</p>    <pre>  <code class="language-java"><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">    <property name="dataSource" ref="dataSource" />  </bean></code></pre>    <p>注意SqlSessionFactoryBean不是MyBatis本身的类,是MyBatis-Spring这个模块引入的类,还记得上一个练习中我们直接使用MyBatis时,是通过SqlSessionFactoryBuilder来创建SqlSessionFactory的吧。</p>    <p>SqlSessionFactoryBean必须设置一个数据源,数据源的配置方式和普通的Spring应用没有任何区别。</p>    <pre>  <code class="language-java"><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></code></pre>    <p>配置好SqlSessionFactoryBean之后,可以配置Mapper成为Spring Bean,这样就能在Controller中使用了。</p>    <pre>  <code class="language-java"><bean id="questionMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">    <property name="mapperInterface" value="com.tianmaying.qa.QuestionMapper" />    <property name="sqlSessionFactory" ref="sqlSessionFactory" />  </bean></code></pre>    <p>mapperInterface属性所指定的映射器类必须是一个接口,不能是一个具体的实现类,QuestionMapper显然符合这个要求。</p>    <p>这样在Controller中就可以使用questionMapper这个Bean了。</p>    <h2><strong>Java配置</strong></h2>    <p>上一小节是通过XML来配置数据源、SqlSessionFactoryBean和Mapper。我们也可以通过Java来进行配置。</p>    <p>这里我们创建一个AppConfig配置类来专门放置这些配置代码:</p>    <pre>  <code class="language-java">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>    <p>在Java配置中要注意工厂方法的Bean的写法,要通过调用getObject()返回目标Bean,而不是创建目标的工厂Bean。</p>    <p>使用Java配置时,为了使用EmbeddedDatabaseBuilder,你需要在POM文件中增加Spring JDBC相关的依赖,这里我们把JDBC Starter加入进来即可:</p>    <pre>  <code class="language-java"><dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-jdbc</artifactId>  </dependency></code></pre>    <h2><strong>Controller层的代码</strong></h2>    <p>此时Controller的代码变得前所未有的简单:</p>    <pre>  <code class="language-java">@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>    <p>你再也看不到一堆创建SqlSessionFactory和SqlSession的代码了,MyBatis-Spring都已经帮你做了,你要做的就是自动装配一个Mapper,然后调用方法操作数据库。</p>    <p>所以你现在应该更清楚为什么官方文档这么说了吧:</p>    <p>最终,它不会依赖于 MyBatis,Spring 或 MyBatis-Spring 来构建应用程序代码。</p>    <p>SqlSessionFactoryManager这个类此时也没有存在的必要了。</p>    <h2><strong>关于SqlSessionFactoryBean</strong></h2>    <p>SessionFactoryBean还有一个属性是configLocation,它是用来指定MyBatis的XML配置文件路径的。</p>    <p>如果你希望修改MyBatis的一些基础配置,比如你希望修改Mybatis配置文件中的<settings> 或<typeAliases>部分,那么修改后的配置文件的路径要在configLocation属性中设置。</p>    <p>比如为了启动延迟加载,可以如下配置:</p>    <pre>  <code class="language-java"><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">    <property name="dataSource" ref="dataSource" />    <property name="mapperLocations" value="classpath*:classpath:mybatis-config.xml" />  </bean></code></pre>    <p>与此同时,在mybatis-config.xml中要进行进行lazyLoadingEnabled设置:</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="UTF-8" ?>  <!DOCTYPE configuration          PUBLIC "-//mybatis.org//DTD Config 3.0//EN"          "http://mybatis.org/dtd/mybatis-3-config.dtd">    <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>    <p>另一种方式是,你可以直接设置configuation属性,下面的配置和前面的做法是等价的,好处是不用修改MyBatis配置文件。</p>    <pre>  <code class="language-java"><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></code></pre>    <p>这些配置你在这个练习中并不会涉及。你需要注意的一点是:MyBatis的配置文件中的数据源和事务相关的配置将会被MyBatis-Spring忽略,即MyBatis XML配置中的<enviroment>元素会被忽略,MyBatis-Spring只会使用Spring Context下定义的数据源和事务管理器。</p>    <h2><strong>MyBatis Spring Boot Starter</strong></h2>    <p>软件技术发展的一个驱动力之一就是不断屏蔽底层的复杂性,使用更自然更易于理解的模型。这样一个道理从我们这个简单的练习课程就可见一斑,比如:</p>    <ul>     <li>最早的数据存储技术是直接读写文件,为了屏蔽物理数据读写的复杂性,出现了数据库技术;</li>     <li>为了支持使用更自然的面向对象模型来访问数据库,出现了第一个练习中介绍的ORM框架,比如MyBatis;</li>     <li>为了屏蔽MyBatis底层的API,MyBatis-Spring出现,让使用MyBatis的代码更加易于维护,第二个练习中你可以发现Controller的代码因为MyBatis-Spring变得更加简洁了;</li>     <li>即使使用MyBatis-Spring,我们发现还需做一些麻烦的事情,比如配置数据源、 配置SqlSessionFactoryBean和配置映射器(Mapper)等工作;如何屏蔽这些麻烦的事情呢,这就需要MyBatis Spring Boot Stater粉墨登场了!</li>    </ul>    <p>其实再往大了说,人类就是不断发明和创造出更好的工具来生存和发展的。细到程序员这个群体也是,我们非常“懒”,遇到"不爽"的地方,就会出现更新更好的技术、平台、工具或者框架来解决问题。</p>    <p>Spring Boot的Starter可以认为是一种方便的依赖描述符,需要集成某种框架或者功能(如集成JPA、Thymeleaf或者MongoDB)时,无需关注各种依赖库的处理,无需具体的配置信息,由Spring Boot自动扫描类路径创建所需的Bean。比如把spring-boot-starter-data-jpa的依赖添加到POM文件中,你就可以方便地使用JPA进行数据库访问了。</p>    <p>MyBatis Spring Boot Starter可以使得你使用MyBatis变得无比简单,比如:</p>    <ul>     <li>自动扫描类路径下数据库驱动类,创建DataSouce实例</li>     <li>基于DataSource自动创建SqlSessionFactoryBean实例</li>     <li>自动扫描映射器相关类,生成所需要Mapper实例</li>    </ul>    <p>总是呢,就是自动帮你做了很多事,让你开发更加轻松惬意。</p>    <h2><strong>引入MyBatis Spring Boot Starter</strong></h2>    <p>添加一个依赖即可引入MyBatis Spring Boot Starter:</p>    <pre>  <code class="language-java"><dependency>      <groupId>org.mybatis.spring.boot</groupId>      <artifactId>mybatis-spring-boot-starter</artifactId>  </dependency></code></pre>    <p>有了这个依赖,MyBatis和MyBatis-Spring的依赖都不再需要,他们会通过mybatis-spring-boot-starter间接地引入。</p>    <p>此外还需在application.properties中添加几项配置:</p>    <pre>  <code class="language-java">spring.datasource.schema=classpath:database/schema.sql  spring.datasource.data=classpath:database/data.sql  mybatis.config-location=classpath:mybatis.xml</code></pre>    <p>相比于原来的数据源配置:</p>    <pre>  <code class="language-java"><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></code></pre>    <p>已经得到了很大的简化,而且H2数据库初始化的语法也帮我们屏蔽了。</p>    <p>此外,mybatis.config-location=classpath:mybatis.xml在仍然还需要引用MyBatis的配置文件的情况下需要进行设置。也就是说修改MyBatis本身的一些行为,你还是需要借助于MyBatis的配置文件的,那么为了让MyBatis Spring Boot Starter知道你对配置文件的修改,你需要做这个设置。</p>    <p>事实上在最后一次练习之前,这个配置基本都是不需要的。</p>    <h2><strong>更简洁的代码</strong></h2>    <p>为了让Spring Boot能够自动扫描到Mapper类,为其创建Bean实例,只需给Mapper添加@Mapper标注。</p>    <pre>  <code class="language-java">@Mapper  public interface QuestionMapper {      Question findOne(@Param("id") Long id);  }</code></pre>    <p>可以发现,MyBatis-Spring的引入消除了SqlSessionFactoryManager类,而这一次Starter的引入则使得AppConfig整个配置类(或者对应的Spring XML配置)此时也不再需要。</p>    <p>回顾一下AppConfig这个类,这里面做的所有事情Spring Boot都已经帮我们做好了,这也可以让我们更清楚第一小节中所说的Spring Boot Starter的引入如何简化开发。</p>    <pre>  <code class="language-java">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>    <p> </p>    <p>来自:https://zhuanlan.zhihu.com/p/23517856</p>    <p> </p>