我学Springboot(1)——快速将一个整合MyBatis的Demo运行起来
简介
学习过程看了很多博文教程,看得不太懂,昨天研究了一天终于可以把Demo运行起来了,把我的理写下来,希望可以帮助到像我一样小白的朋友。代码详情在这里。
跟着这个Demo做,将学到:
1、Springboot项目的创建
2、将MyBatis整合到Springboot里面,实现数据库的增删改查
步骤
一、新建项目
1、打开IDEA->新建项目
后,选择使用Spring initalizr
创建项目。

2、等待一段时间之后,会提示设置项目,这里选择Gradle构建项目,其实也可以选择Maven,不过我觉得Gradle的依赖项更简洁一些,Next之后是设置项目路径,按照自己喜好设置就行了。

3、创建好项目,IDEA会去Spring官网下载一个Demo.zip,并需要你按下图提示设置Gradle。Gradle Home
填的是Gradle的目录地址。

4、最后我们就得到了初始的项目结构。

4、创建几个包/文件夹,就像这样:

以数据库为最底层,自下而上介绍这几个包:
mapper
负责封装数据库操作逻辑,按照百度百科来说,这就属于持久层,Hibernate
和MyBatis
都是持久层的框架,我对于这样分层的理解是:
1、将业务逻辑与具体数据库实现的解耦。业务逻辑不再依赖数据库操作的具体实现,以后换数据库只需要修改mapper里面的代码就成了。
2、提高程序健壮性。把CRUD抽象成功能,我觉得不同技术水平的开发者,写出来来的SQL语句质量肯定是不一样的,把关键点的SQL代码交给资深工程师开发,避免了初级工程师因为经验不足写出低效代码的风险。
3、提高代码复用性。相同功能的SQL只需要写一遍,以后就可以反复使用了,出了问题也方便排查。
domain
数据实体,也就是下面说的Pojo类。
service
负责业务逻辑部分。我的理解是以具体的业务为粒度划分,里面有多个mapper类,一起完成某一业务需求。
controller
包是放控制层逻辑的,它的功能是通过调用service层提供的服务,响应浏览器的请求。
resources
resources/mapper是放mapper的配置文件的文件夹,配置文件是用来写mapper的SQL语句的。
二、创建数据库
在MySql的客户端运行下面的代码,创建表结构并且插入测试数据:
CREATE TABLE `consumer` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(32) NOT NULL ,
`sex` VARCHAR(2) DEFAULT NULL ,
`address` VARCHAR(256) DEFAULT NULL ,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `consumer` (name, sex, address) VALUES
("王一", "男", "北京"),
("王二", "女", "南京"),
("王三", "男", "东京"),
("王四", "女", "上海"),
("王五", "男", "广州");
三、创建Pojo类
Pojo类就是一些只有属性和setter、getter方法的类,不包含业务逻辑。本Demo中的Pojo类只有一个——Consumer,我们在domain包里面新建Consumer.java
,里面写上Consumer的4个属性,代码如下:
@Alias("consumer")
public class Consumer {
private Long id;
private String name;
private String sex;
private String address;
public Consumer set(Long id, String name, String sex, String address) {
this.id = id;
this.name = name;
this.sex = sex;
this.address = address;
return this;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex == null ? null : sex.trim();
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address == null ? null : address.trim();
}
@Override
public String toString() {
return "Consumer{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
需要解释一下,开头的@Alias("consumer")
意思是给这个Pojo类设置一个别名,否则在配置mapper.xml的时候就要使用它的全路径名了——com.example.demo.domain.Consumer
,每次使用都要写这么长的名称,这么做就很麻烦。
还有我加多了一个set方法的原因,是我发现后边有时会需要自己创建新的consumer实例,默认构造好像又是由Springboot生成的,不让我自己加,所以只好写一个set方法了。(待再验证)
四、添加依赖项
不出意外的话,上面的@Alias("consumer")
会报错,就像下面一样:

这是因为我们还没有给项目添加依赖项,比如@Alias
用到的org.apache.ibatis.type.Alias
,现在我们给Gradle添加依赖配置。
打开项目根目录下的build.gradle
文件,找到dependencies
关键词,你会看到已经有2项初始依赖了:
dependencies {
compile('org.springframework.boot:spring-boot-starter') // spring-boot的基础包,包含spring框架之类的
testCompile('org.springframework.boot:spring-boot-starter-test') // spring-boot测试包,包含junit之类的
}
接下来我们在下面加上下面的代码:
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.10.RELEASE' // spring-boot的web包,包含tomcat之类的
compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6' // mysql的链接驱动
compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '1.3.1' // mybatis的spring整合包
最终的效果就是这样:
dependencies {
compile('org.springframework.boot:spring-boot-starter')
testCompile('org.springframework.boot:spring-boot-starter-test')
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.10.RELEASE'
compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6'
compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '1.3.1'
}
上面提到Gradle比Maven的依赖配置更简洁,假如用Maven来写上面的3个依赖的的话,要像这样:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.10.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
是不是长很多?
五、创建Mapper类
现在开始编写Mapper类,先在mapper文件夹下新建一个接口类ConsumerMapper.java
,并在里面声明需要提供的服务。在本Demo里面,我们要实现增删改查,再加一个查询所有,一共5个功能,代码如下:
public interface ConsumerMapper {
List<Consumer> selectAll();
Consumer selectById(Long id);
Long insert(Consumer consumer);
Long update(Consumer consumer);
Long delete(long id);
}
就这么简单,需要多少个CRUD功能,就声明多少个方法。注意,这里一定要声明为接口,因为MyBatis会使用这个接口实现一个新的类注入到service里面。
六、创建Service类
在service
包里面新建ConsumerService.java
,由于增删改查不涉及什么复杂逻辑,全都是直接调用mapper的功能,所以很简单:
@Service("ConsumerService")
public class ConsumerService {
@Autowired
private ConsumerMapper consumerMapper;
public List<Consumer> getAll() {
return consumerMapper.selectAll();
}
public Consumer get(Long id){
return consumerMapper.selectById(id);
}
public Long add(Consumer consumer){
consumer.setId(0L);
consumerMapper.insert(consumer);
return consumer.getId();
}
public Boolean delete(Long id){
return 0 != consumerMapper.delete(id);
}
public Boolean update(Consumer consumer){
return 0 != consumerMapper.update(consumer);
}
}
七、创建Controller类
Controller类负责组织我们HTTP请求,和配置servlet-mapping差不多。我们有5个功能,所以对应也实现5个方法,绑定到想应URL上。可以直接看代码,应该很容易理解:
@RequestMapping("/consumer")
@RestController
public class ConsumerController {
@Autowired
private ConsumerService consumerService;
@GetMapping
public ResultBean<?> getAll() {
return new ResultBean<>().setBean(consumerService.getAll());
}
@GetMapping("/{id}")
public ResultBean<?> get(@PathVariable("id") Long id) {
return new ResultBean<>().setBean(consumerService.get(id));
}
@PostMapping
public ResultBean<?> create(@RequestBody Consumer consumer) {
return new ResultBean<>().setBean(consumerService.add(consumer));
}
@PutMapping
public ResultBean<?> update(@RequestBody Consumer consumer) {
return new ResultBean<>().setBean(consumerService.update(consumer));
}
@DeleteMapping("/{id}")
public ResultBean<?> delete(@PathVariable("id") Long id) {
return new ResultBean<>().setBean(consumerService.delete(id));
}
}
我觉得需要解释是几个注解:
@RestController
是指HTTP应答格式为JSON。@XXXMapping
是指HTTP请求到类型,后面的参数是URL。值得注意的是{id}
,它的意思是捕获URL中的参数,传入方法getConsumer(@PathVariable("id") Integer id)
。@Autowired
是指自动注入,Spring会自动为它生成实例。
另一个值得注意的点是:我把所有的返回都进行了封装
,每一个请求都是返回ResultBean数据格式,这是我在阅读知乎专栏我的编码习惯 - Controller规范学到的,之前公司也是这么做的,我觉得确实很有道理,一个统一的借口可以让系统间的通信更灵活稳定。
ResultBean
我没有完全使用原作者的ResultBean代码,而是把原来构造体的代码都移到了setBean里面,这样初始化ResultBean可以灵活一点,但是也造成了每次增加一种特殊的data类型,都要修改public ResultBean<?> setBean(T data)
方法,好在特殊类型总是有限的,这种情况不会频繁发生:
public ResultBean() {
super();
}
public ResultBean<?> setBean(T data) {
if (null == data) {
return this.setBean(null, StatesCode.NULL);
} else {
if (data.getClass() == Boolean.class) {
return this.setBean(data,
data.equals(true) ? StatesCode.SUCCESS : StatesCode.FAIL);
} else if (data.getClass() == Throwable.class) {
return this.setBean(data, StatesCode.FAIL, data.toString());
} else {
return this.setBean(data, StatesCode.SUCCESS);
}
}
}
public ResultBean<?> setBean(T data, StatesCode code) {
return setBean(data, code, code.toString());
}
public ResultBean<?> setBean(T data, StatesCode code, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
return this;
}
八、编写Xml配置
现在到了最后配置环节了,在这个Demo中,只有2个文件需要配置:
1、application.properties
这个文件是Springboot的配置文件,里面写一些框架属性,现在我们在这个文件里面配置JDBC和MyBatis。
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=learnjava
spring.datasource.password=123
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=cn.hiyujie.mybatisspringbootsamplexml.domain
解释一下第二段配置MyBatis的含义。
mybatis.mapper-locations
属性指的是mapper的配置文件在哪一个文件夹里面,本Demo在resources/mapper
文件夹里面,所以填classpath:mapper/*.xml
。mybatis.type-aliases-package
属性指的是Pojo类的包的位置,这样在mapper.xml里面就可以使用别名了(我的猜测是MyBatis会去扫描这个包里面的类,遇到@Alias
注解的类就记录下来它的别名)。
2、ConsumerMapper.xml
这个就是mapper配置文件了,这也是最重要的一个文件了,里面配置了mapper执行的CRUD代码。先在resources/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.example.demo.mapper.ConsumerMapper">
<resultMap id="ConsumerMap" type="consumer">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="sex" property="sex" jdbcType="VARCHAR" />
<result column="address" property="address" jdbcType="VARCHAR" />
</resultMap>
<sql id="BaseColumnList" >
name, sex, address
</sql>
<select id="selectById" parameterType="long" resultType="consumer">
SELECT * FROM consumer WHERE id = #{id};
</select>
<select id="selectAll" resultMap="ConsumerMap">
SELECT * FROM consumer;
</select>
<insert id="insert"
parameterType="consumer"
useGeneratedKeys = "true"
keyProperty="id">
INSERT INTO consumer (<include refid="BaseColumnList"/>)
VALUES (#{name}, #{sex}, #{address});
</insert>
<update id="update" parameterType="consumer">
UPDATE consumer SET
name = #{name}
<if test="sex != null">,sex = #{sex}</if>
<if test="address != null">,address = #{address}</if>
WHERE id = #{id};
</update>
<delete id="delete" parameterType="long">
DELETE FROM consumer WHERE id =#{id};
</delete>
</mapper>
下面我解释一下几个我觉得比较重要的点:
<mapper namespace>
。它表示当前配置的是哪一个包的mapper,需要写包的全路径名称。<resultMap>
。它声明了mapper函数返回集合的类型,其实就是指明Pojo类的属性。<sql>
。这个就是设置一段SQL语句的别称,可以看到后面的SQL语句可以直接使用了:
INSERT INTO consumer (<include refid="BaseColumnList"/>) VALUES (#{name}, #{sex}, #{address});
<insert>
标签内部使用了这两个属性,keyProperty
和useGeneratedKeys
,作用是返回行ID。<update>
标签展示了MyBatis的动态SQL的用法,在配置文件里面使用条件语句。
运行
在Postman
发送请求到http://localhost:8080
,应该就可以看到打印出数据了。
总结
可能出现的问题
1、我遇到次数最多的错误是mapper.xml配置错误,集中在类型名那里,MyBatis为常用类型起来很多别名,详情可以看这里。
2、我犯的第二个错误是我给Controller类加了一个构造体方法:
public Consumer(String name, String sex, String address) {
this.name = name;
this.sex = sex;
this.address = address;
}
结果报错,说找不到匹配public Consumer(int id, String name, String sex, String address)
的构造体,这个应该是框架的问题,待我研究源码再解释一下。
3、所有Pojo类的字段都需要sette和getter。
开发流程
1、为每个数据表新建一个相对应的Pojo类。
2、设计好系统提供的API功能细节,及其调用URL。
3、根据API功能编写Mapper类,并配置好mapper.xml文件。
4、根据业务逻辑完成Service类。
4、根据URL编写Controller类,让其通过Service类实例实现功能。