开发目标
今天我们使用MyBatis和Spring MVC开发一个简单的问答网站,你可以使用Markdown来发布问题和答案。
这篇文章可以教会你以下知识:
- MyBatis配置和使用最简单的姿势
- MyBatis+Spring MVC实现一个简单的Web页面
- MyBatis的核心类是干嘛用的(SqlSessionFactoryBuilder、SqlSessionFactory和SqlSession)
- MyBatis-Spring如何使用,帮你做了哪些工作?
- MyBatis Spring Boot Starter如何支持MyBatis,让你使用MyBatis的门槛降到最低
相信看完本身是你开始使用MyBatis的一个非常棒的开始!
最终的效果图如下:
Web端的开发我们使用Spring MVC,对Spring MVC还不太熟悉的同学,可以看看天码营的Spring MVC实战练习。当然,Spring MVC的部分不是这个练习的重点,也基本不影响大家的理解和练习。
这样一个系统麻雀虽小,五张俱全,会涉及MyBatis的很多核心知识。MyBatis确实是一个非常简单易学的ORM框架,很适合作为你学习的第一款Java ORM框架。
完整的教程请参考《基于MyBatis和Spring MVC搭建问答网站》。网站Demo请见MyBatis问答小网站。
ORM是什么?
ORM是Object Relation Mapping的缩写,顾名思义,即对象关系映射。
ORM是一种以面向对象的方式来进行数据库操作的技术。Web开发中常用的语言,都会有对应的ORM框架。而MyBatis就是Java开发中一种常用ORM框架。
简单地理解,通过Java进行数据库访问的正常流程可以分为以下几步:
- 准备好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项目的支持非常完善,自动提示、补全功能异常强大,对于开发效率的提升非常明显。
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;
}
这里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"
"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就能获取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来进行配置,有关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";
}
}
你再也看不到一堆创建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"
"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啦!
想进一步深入了MyBatis问答网站的更多细节知识,请阅读完整的教程
欢迎关注天码营微信公众号: TMY-EDU
小编重点推荐: