MyBatis入门实例:整合Spring MVC与MyBatis开发问答网站
CiaraSchnel
8年前
<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>