Spring Boot 入门

Table of Contents

1 Spring Boot

Spring Boot 是一个可以让你轻松创建独立的、生产级的、基于 Spring 的 Web 应用程序。

2 单模块项目创建

创建一个单模块项目,我们可以使用 https://start.spring.io 网站生成,然后用IDEA打开。

spring-initializr.png

我们也可以直接用IDEA创建。

idea-new-project-001.png 左侧菜单栏选择 Spring Initializr,然后 Next。

idea-new-project-002.png 这里我们需要填写项目的Metadata信息1

GroupID    项目组织的唯一标识符,遵循Java包名规则,以反向域名开始。
ArtifactID 项目的唯一标识符,即项目名称。小写字母,不要有特殊字符。
Version    项目版本号,开发阶段使用版本号后接 -SNAPSHOT 方便开发,发布部署后去掉。
Name       项目名称,和 ArtifactID 保持一致。
Package    项目包名,一般为GroupID + ArtifactID。

idea-new-project-003.png 这里我们可以选择项目依赖,比如Spring Web。它使用Spring MVC框架,Tomcat作为默认嵌入式容器(服务器),并且包含RESTful。 idea-new-project-004.png idea-new-project-005.png 至此,我们就创建好单模块项目了。测试一下吧。 添加Greeting.java

package com.example.demo.controller;

public class Greeting {

    private final long id;
    private final String content;

    public Greeting(long id, String content) {
        this.id = id;
        this.content = content;
    }

    public long getId() {
        return id;
    }

    public String getContent() {
        return content;
    }
}

GreetingController.java

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.atomic.AtomicLong;

@RestController
public class GreetingController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @GetMapping("/greeting")
    public Greeting greeting(@RequestParam(value = "name", defaultValue = "world") String name) {
        return new Greeting(counter.incrementAndGet(), String.format(template, name));
    }
}

最后的目录结构如图 idea-new-project-006.png 试试在浏览器访问http://127.0.0.1:8080/greeting

3 多模块项目创建

一般情况下,大中型项目都会划分模块,一方面是为了遵循面向对象设计原则,同时也是因为模块化设计可以提高项目结构清晰度,方便扩展,重用等。那如何划分模块呢,一般根据项目类型决定,没有一定之规。但还是有一些规律可循,比如经典的MVC架构模式,即Model(模型)、View(视图)、Controller(控制器)。对应Spring Web 应用,我一般使用这样的设计及命名:

web web层一般位于应用程序的最上层,也就是入口层,用于处理用户输入及响应。其实就是MVC中的Controller。
service service层位于web层之下,充当事务边界。一般作为公共API。
domain  域模型[fn:2]通常是用于数据库的持久化,在 Java 中,它们通常符合 javabean 规范,即它们具有 get 和 set 方法来表示单个属性和一个无参数的构造函数。是领域驱动设计里的一个重要概念。
dao DAO全称数据访问对象,可以理解为对数据库 CRUD 的操作接口。

multiple-module-001.png 这次什么也不用选,因为我们是在创建外层结构。外层pom.xml属于管理和统筹项目信息及依赖,这里不需要Spring Web。或者说放到web模块的pom里更合适些。 multiple-module-002.png 删掉 .mvn 目录、 mvnw 及 mvnw.cmd 文件,保留.gitignore,demo.iml,pom.xml。.idea是IDEA编辑器的一些配置信息,也需要保留。src目录,暂时先不要删,我们待会需要将DemoApplication启动文件放到创建好的web模块的包下。这块有个细节要 注意DemoApplication启动文件,也就是主类应该放到根包中,位于其他类之上。它隐式地定义了一个基本的“搜索包”,如果您正在编写一个 JPA 应用程序,@enableautoconfiguration 注释类的包将用于搜索@entity 项。另外如果主类在根包中,不需要指定 basePackage 属性。 multiple-module-003.png 依次创建web模块,service模块,domain2模块3,dao模块。为了方便区分,我们在创建时加上前缀如demo-web、demo-service、demo-domain、demo-dao。

如果你现在启动,会发现如下图console所示, multiple-module-004.png 那是因为我们还没有添加 Spring Web 依赖。我们在demo-web下的pom.xml添加

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
</dependencies>

再次运行,可以发现tomcat为我们启动了8080端口。如下图。 multiple-module-005.png 同样,我们也添加下Greeting.java,GreetingController.java文件测试下。代码可以参照单项目创建示例。 multiple-module-006.png 在浏览器访问http://127.0.0.1:8080/greeting?name=Tom

现在其实也还是单模块运行,我们先整理下父pom.xml文件。一步步来。

  1. 删除 dependencies 标签及其依赖,因为 Spring Boot 提供的父工程已包含,并且父 pom 原则上都是通过 dependencyManagement 标签管理依赖包。
  2. 删除 build 标签及其中的所有内容。spring-boot-maven-plugin 插件作用是打一个可运行的包,多模块项目仅仅需要在入口类所在的模块添加打包插件,这里父模块不需要打包运行。而且该插件已被包含在 Spring Boot 提供的父工程中,这里删掉即可。
  3. 配置模块间依赖关系

在父pom文件里添加

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>demo-web</artifactId>
            <version>${demo.version}</version>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>demo-service</artifactId>
            <version>${demo.version}</version>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>demo-dao</artifactId>
            <version>${demo.version}</version>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>demo-domain</artifactId>
            <version>${demo.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

注意:为了方便及统一管理各子模块的版本,我们将它定义在properties标签里。通过 ${demo.version} 获取。

<properties>
    <java.version>1.8</java.version>
    <demo.version>0.0.1-SNAPSHOT</demo.version>
</properties>

根据依赖关系,我们在demo-dao模块的pom文件添加

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>demo-domain</artifactId>
    </dependency>
</dependencies>

在demo-service模块的pom文件里添加

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>demo-domain</artifactId>
    </dependency>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>demo-dao</artifactId>
    </dependency>
</dependencies>

在demo-web模块的pom文件里添加,这里因为我们已经在之前添加Spring Web依赖了,我们直接把dependency项追加到dependencies项就好。如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>demo-service</artifactId>
    </dependency>
</dependencies>

上面子模块中依赖无需添加版本号,它们会从父模块自动查找。

接着,我们测试下通过web层调取service层是否正常。

首先在demo-service层创建com.example.demo.service包,然后创建DemoService接口类及impl目录(用于存放接口实现类),接着创建接口实现类DemoServiceImpl DemoService.java

package com.example.demo.service;

public interface DemoService {
    String test();
}

DemoServiceImpl.java

package com.example.demo.service.impl;

import com.example.demo.service.DemoService;
import org.springframework.stereotype.Service;

@Service
public class DemoServiceImpl implements DemoService {

    @Override
    public String test() {
        return "interface test";
    }
}

这里使用到 @Service 注解,我们需要在demo-service模块的pom.xml依赖项里追加此依赖

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
</dependency>

我们打开web层的GreetingController文件,添加:

@Autowired
private DemoService demoService;

@GetMapping("/test")
public String test() {
   return demoService.test();
}

在浏览器访问 http://127.0.0.1:8080/hello/test 返回 interface test 表明一切正常。

4 集成MyBatis

在父pom.xml文件添加依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

添加到dependencyManagement下的依赖属于声明,并不会自动引入,所以需要在子模块下引入。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

通过mybatis-generator工具生成dao层相关文件,我这里使用的是IDEA插件 MyBatisCodeHelperPro 生成的。使用它需要先用IDEA连接数据库,然后选择表,右键选择Mybatis generator,配置存放位置。 mybatis-generator.png

我们在demo-service子模块调用,打开DemoServiceImpl文件,使用 @Autowired 注入,具体如下。

@Autowired
private SysDictMapper sysDictMapper;

@Override
public String test() {
    SysDict sysDict = sysDictMapper.selectByPrimaryKey(1);
    return sysDict.toString();
}

这种注入方式已不被推荐,所以也可以这样写

private final SysDictMapper sysDictMapper;

public DemoServiceImpl(SysDictMapper sysDictMapper) {
    this.sysDictMapper = sysDictMapper;
}

@Override
public String test() {
    SysDict sysDict = sysDictMapper.selectByPrimaryKey(1);
    return sysDict.toString();
}

在demo-web模块下的resources文件夹下创建属性配置文件: application.properties ,然后添加配置

spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
spring.datasource.username = root
spring.datasource.password = 

mybatis.mapper-locations = classpath:mybatis/*.xml
mybatis.type-aliases-package = com.example.demo.dao.entity

我们运行下发现会报错

Description:

Field sysDictMapper in com.example.demo.service.impl.DemoServiceImpl required a bean of type 'com.example.demo.dao.mapper.SysDictMapper' that could not be found.


Action:

Consider defining a bean of type 'com.example.demo.dao.mapper.SysDictMapper' in your configuration.

解决这个问题有两种方法

  1. 在SysDictMapper接口增加 @Mapper 注解,确保扫描注册时可以识别到这个接口。
  2. 在启动类上增加 @MapperScan 注解,并设置搜索包。
@MapperScan("com.example.demo.dao.mapper")

再次启动,访问http://127.0.0.1:8080/hello/test 可以发现我们获取到了数据库内的信息。

5 多环境属性配置

一般情况,我们都会有【开发】【测试】【正式】环境,所以不同环境间的配置也就不同。我们在上面已经新建了一个 application.properties 配置文件。接下来我们再新建三个配置文件,分别为:

application-dev.properties
application-test.properties
application-prod.properties

application.properties 为主配置,注意用于环境区分和公共配置。各环境配置文件为各自环境配置。 例如主配置为

server.port = 8080
spring.profiles.active=dev
spring.application.name=demo
app.id=demo


mybatis.mapper-locations = classpath:mybatis/*.xml
mybatis.type-aliases-package = com.example.demo.dao.entity

开发环境配置为

spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
spring.datasource.username = root
spring.datasource.password =

配置环境除了properties语法,还有一种yaml语法,看个人喜欢。

另外提醒下在连接mysql后面有一堆参数,一定要知道其含义再使用,否则会带来意想不到的问题

?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&autoReconnect=true&useAffectedRows=true

比如,useAffectedRows = true ,在判断更新、删除时,稍不注意就会掉坑里。详情可以看当JDBC执行删改时,会返回什么值

6 reference

Footnotes: