开发目标

今天我们使用MyBatis和Spring MVC开发一个简单的问答网站,你可以使用Markdown来发布问题和答案。完整的教程请参考《基于MyBatis和Spring MVC搭建问答网站》

这篇文章可以教会你以下知识:

  • MyBatis配置和使用最简单的姿势
  • MyBatis+Spring MVC实现一个简单的Web页面
  • MyBatis的核心类是干嘛用的(SqlSessionFactoryBuilderSqlSessionFactorySqlSession
  • MyBatis-Spring如何使用,帮你做了哪些工作?
  • MyBatis Spring Boot Starter如何支持MyBatis,让你使用MyBatis的门槛降到最低

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

最终的效果图如下:

Clipboard Image.png

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进行数据库访问的正常流程可以分为以下几步:

  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我们推荐使用EclipseIntelliJ 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图如下:

alter-text

UML中用菱形+横线的方式,表示包含关系。同样是包含,大家注意左侧的菱形是填充的,而右侧没有填充,这两者的区别在于:

  • Question消失时,Answer必然也消失了,它们有相同的生命周期,皮之不存,毛将焉附嘛
  • Question消失时,Tag则可以仍然存在,因为Tag还可以标注其他问题

现在我们暂时不管AnswerTag,只关注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;
}

这里Question实现了Serializable接口,在这一节的练习中不是必须的。实现这个接口是为了后面缓存查询结果是需要。

数据库设计

除了设计用于QuestionAnswerTag数据的表,我们还需要定义一张维护QuestionTag之间多对多关系的表。最终数据库设计如下:

alter-text

这个阶段你只需关注question表即可。

添加MyBatis的配置

接下来我们添加MyBatis配置,在resources目录下建立mybatis.xml文件:

<?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>

这就是一个最简单的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;

import com.tianmaying.qa.model.Question;
import org.apache.ibatis.annotations.Param;

public interface QuestionMapper {

    Question findOne(@Param("id") Long id);

}

@Param是MyBatis提高的一个标注,表示id会解析成SQL语句(SQL语句会在XML配置或者标注中)的参数。

使用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" 
"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>

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();
    }
}

把这个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来实现前端页面。

这里有一个非常适合新手看得Spring MVC的快速入门。更深入地学习Spring MVC请参考Spring MVC实战练习

这里我们先写出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";
    }

}

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;
    }
}

基于这样一个辅助类,获取应用的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();
        }
    }

}

启动Spring Boot应用

修改MybatisQaApplication的代码,使之成为一个Spring Boot应用:

@SpringBootApplication
@Controller
public class MybatisQaApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisQaApplication.class, args);
    }

}

resources目录下增加一个应用配置文件application.properties,内容为:

spring.thymeleaf.mode=LEGACYHTML5

这一行配置是为了让Thymeleaf支持HTML5。

此时你在命令行或者IDE中运行mvn spring-boot:run就可以启动应用,访问http:localhost:8080/1这样的URL就能获取id1的问题的详细信息了。

什么是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来进行配置,有关Spring Java配置的知识请参考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;
    }
}

在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";
    }

}

你再也看不到一堆创建SqlSessionFactorySqlSession的代码了,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"
        "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>

另一种方式是,你可以直接设置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粉墨登场了!

其实再往大了说,人类就是不断发明和创造出更好的工具来生存和发展的。细到程序员这个群体也是,我们非常“懒”,遇到"不爽"的地方,就会出现更新更好的技术、平台、工具或者框架来解决问题。这样一种技术发展动力从天码营的另外一篇文章《Web开发技术的发展历史》也能窥见一斑。

关于Spring Boot请参考以下资料:

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;
    }
}

没错,把这个类的代码贴出来,是想告诉你,你可以和这个类Say Goodbye啦!

登录发表评论 注册

ichenfeng

有springboot + mysql + mybatis + schema.sql这样的参考代码?

反馈意见