我学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

负责封装数据库操作逻辑,按照百度百科来说,这就属于持久层HibernateMyBatis都是持久层的框架,我对于这样分层的理解是:

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>标签内部使用了这两个属性,keyPropertyuseGeneratedKeys,作用是返回行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类实例实现功能。

posted @ 2018-02-06 21:27  hiyujie  阅读(303)  评论(0编辑  收藏  举报