从零开始的springboot项目

  • 开发环境
  • 使用IDEA创建项目及配置依赖
  • 生成启动类和配置文件
  • 配置springboot配置文件
  • 配置启动类
  • 配置knife4j及Json工具类
  • Json工具类
  • 开始编写数据表相关mapper类
  • 编写service类
  • 编写controller类
  • 编写完毕
  • 遇到的问题
  • 数据库字段问题:
  • knife4j配置问题:


开发环境

IDEA,JDK 11,maven,springboot,剩下具体见依赖

全程将使用注释补充说明

使用IDEA创建项目及配置依赖

创建项目应该都会就略过

依赖包:

<!-- 用的2.3.3版的springboot -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.3.RELEASE</version>
</parent>

<!-- 使用knife4j生成测试文档,swagger2的升级版,更美观的界面 -->
<dependencyManagement>
    <dependencies>
        <!--knife4j-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-dependencies</artifactId>
            <version>2.0.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>


<dependencies>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- mysql数据库相关依赖,这里引的8.0.16版本,根据需要选择版本 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <!-- 使用druid连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!-- 使用通用mybatis与数据库交互,并使用其通用Mapper简化开发 -->
    <dependency>
        <groupId>tk.mybatis</groupId>
        <artifactId>mapper-spring-boot-starter</artifactId>
        <version>2.1.5</version>
    </dependency>
    <!-- 使用lombok简化代码 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!-- 使用redis数据库做缓存 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- 存入redis使用String类型,通过已有Json工具转化 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.74</version>
    </dependency>
    <!-- knife4j -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

生成启动类和配置文件

这里使用IDEA里JBLSpringbootAppGen插件快捷生成

配置springboot配置文件

# 配置服务的端口
server:
  port: 9001
spring:
  # 配置服务名称
  application:
    name: springboot-demo
  # 配置mysql
  datasource:
    url: jdbc:mysql://ip:port/库名?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 配置druid连接池
    druid:
      # 初始化大小,最小,最大
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存时间
      min-evictable-idle-time-millis: 300000
      validation-query: select 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开 PSCache,并且指定每个连接上 PSCache 的大小
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20
      # 配置监控统计拦截的 Filter,去掉后监控界面 SQL 无法统计,wall 用于防火墙, 如果有日志,也需要过滤
      #filters: stat,wall
      # 通过 connection-properties 属性打开 mergeSql 功能;慢 SQL 记录
      #connection-properties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
      # 配置 DruidStatFilter
      web-stat-filter:
        enabled: true
        #url-pattern: /*
        exclusions: .js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
      # 配置 DruidStatViewServlet
      stat-view-servlet:
        url-pattern: /druid/* #监控地址,默认 /druid/*
        # IP 白名单,没有配置或者为空,则允许所有访问
        # allow: 127.0.0.1
        # IP 黑名单,若白名单也存在,则优先使用
        # deny: 192.168.31.253
        # 禁用 HTML 中 Reset All 按钮
        # reset-enable: false
        # 登录用户名/密码
        login-username: admin
        login-password: admin
  # 配置redis
  redis:
    host: ip
    port: 6379
# mybatis配置
mybatis:
  # 配置类别名
  type-aliases-package: com.demo.springboot.pojo
  # 各个mapper.xml的存放位置
  mapper-locations: classpath:mapper/*.xml
# knife4j配置,设为false开启文档
knife4j:
  production: false #生产环境设为true关闭swagger

配置启动类

@SpringBootApplication
@MapperScan("包.mapper") // mapper类扫描位置
@EnableSwagger2 // 启用knife4j
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

配置knife4j及Json工具类

需注意的是,knife4j版本不同,注解的使用也不同

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableSwagger2
@EnableKnife4j
@Profile("dev")
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfig {

    @Bean("测试平台")
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("测试平台")
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.demo.springboot.controller"))
                .paths(PathSelectors.any())
                .build()
                .securityContexts(securityContext())
                .securitySchemes(securitySchemes());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("测试平台 RESTful APIs")
                .description("测试平台 api接口文档")
                .version("1.0")
                .build();
    }

    private List<ApiKey> securitySchemes() {
        List<ApiKey> list = new ArrayList();
        list.add(new ApiKey("loginToken", "loginToken", "header"));
        return list;

    }

    private List<SecurityContext> securityContext() {
        List<SecurityContext> list = new ArrayList();
        SecurityContext securityContext = SecurityContext.builder()
                .securityReferences(securityReferences())
                .forPaths(PathSelectors.regex("/.*"))
                .build();
        list.add(securityContext);
        return list;
    }

    List<SecurityReference> securityReferences() {
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[] { new AuthorizationScope("global", "accessEverything") };
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("loginToken", authorizationScopes));
        return securityReferences;
    }
}

Json工具类

定义了常用的三个方法

public class JsonUtils {

    /**
     * 定义jackson对象
     */
    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 将对象转换成json字符串。
     * <p>
     * Title: pojoToJson
     * </p>
     * <p>
     * Description:
     * </p>
     *
     * @param data
     * @return
     */
    public static String objectToJson(Object data) {
        try {
            return MAPPER.writeValueAsString(data);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 将json结果集转化为对象
     *
     * @param jsonData json数据
     * @return
     */
    public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
        try {
            T t = MAPPER.readValue(jsonData, beanType);
            return t;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将json数据转换成pojo对象list
     * <p>
     * Title: jsonToList
     * </p>
     * <p>
     * Description:
     * </p>
     *
     * @param jsonData
     * @param beanType
     * @return
     */
    public static <T> List<T> jsonToList(String jsonData, Class<T> beanType) {
        JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
        try {
            List<T> list = MAPPER.readValue(jsonData, javaType);
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

开始编写数据表相关mapper类

创建mapper包

在编写mapper之前先生成对应表用来存放数据类

这里以一个tag表做例子

@Data // lombok简化代码
@Table(name = "tag") // 使用mybatis的自动sql需要的注解,该类对应的表名
@ApiModel(description = "tag表的数据") // swagger相关注解,类的说明
public class Tag {

    /**
     * Id not null 主键
     */
    @ApiModelProperty("主键,自动生成") //swagger相关注解,属性的说明
    @Id // mybatis注解,需要配置一个id
    @Column(name = "Id") // 属性名与字段名不一致时使用该注解,字段名为mysql的关键词时需加数据库对应的转义符:@Column(name = "`from`")
    @GeneratedValue(strategy = GenerationType.IDENTITY) // mybatis注解id属性需要配置的自增类型
    private String id;

    /**
     * TagTo default null
     */
    @ApiModelProperty("默认:null")
    @Column(name = "TagTo")
    private String tagTo;

    /**
     * Name default null
     */
    @ApiModelProperty("默认:null")
    @Column(name = "Name")
    private String name;

    /**
     * is_delete default 0 0-未删除,1-已删除
     */
    @ApiModelProperty(value = "0-未删除,1-已删除 默认:0")
    @Column(name = "is_delete")
    private Boolean isDelete;
}

mapper类

import com.demo.springboot.pojo.Tag;
import tk.mybatis.mapper.common.Mapper;
/**
 * 继承通用Mapper,使用已经写好的方法实现最简单的CRUD
 * 可以自己编写方法,并在对应mapper.xml文件中写sql,或使用注解编写sql
 */
public interface TagMapper extends Mapper<Tag> {
}

编写service类

我这里写了个BaseService统一写了些常用方法

public interface BaseService<T> {

    /**
     * 添加
     * @param t
     */
    void insert(T t);

    /**
     * 修改
     * @param t
     */
    void update(T t);

    /**
     * 删除
     * @param id
     */
    void delete(Object id);

    /**
     * 查找一个
     * @param id
     * @return
     */
    T selectOne(Object id);

    /**
     * 查找所有
     * @return
     */
    List<T> selectAll();
}

TagService直接继承BaseService,在需要时在做拓展

public interface TagService extends BaseService<Tag> {
}

实现类

实现类中查询方法均在 redis 缓存中获取

删除方法使用的是逻辑删除,即修改is_delete字段的值来修改该条数据的有效性

删除,修改,增加方法都需更新redis缓存,所以将数据库的操作抽取为一个方法,读取数据库后保存进redis,所以执行完删除,修改,增加方法都执行该方法

@Service
public class TagServiceImpl implements TagService {

    @Autowired
    private TagMapper tagMapper;
    
    // 执行redis相关操作的变量
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 添加
     *
     * @param tag
     */
    @Override
    public void insert(Tag tag) {
       	// 自动生成id
        tag.setId(UUID.randomUUID().toString());
        // 设置默认值
        tag.setIsDelete(false);
        tagMapper.insert(tag);
        // 更新redis数据
        selectInSql(null);
    }

    /**
     * 修改
     *
     * @param tag
     */
    @Override
    public void update(Tag tag) {
        tagMapper.updateByPrimaryKey(tag);
        // 更新redis数据
        selectInSql(tag.getId());
        selectInSql(null);
    }

    /**
     * 删除
     *
     * @param id
     */
    @Override
    public void delete(Object id) {
        Tag tag = selectOne(id);
        tag.setIsDelete(true);
        update(tag);
    }

    /**
     * 查找一个
     *
     * @param id
     * @return
     */
    @Override
    public Tag selectOne(Object id) {
        // 从redis获取
        String json = (String) redisTemplate.opsForHash().get("data:tag", id);
        if (json != null) {
            // 有则返回
            return JsonUtils.jsonToPojo(json, Tag.class);
        }
        // 无则从数据库获取并存入redis
        return (Tag) selectInSql(id);
    }

    /**
     * 查找所有
     *
     * @return
     */
    @Override
    public List<Tag> selectAll() {
        List<Tag> data;
        // 从redis获取
        String all = (String) redisTemplate.opsForHash().get("data:tag", "all");
        if (all != null)  {
            // 有则返回
            data = JsonUtils.jsonToList(all, Tag.class);
            return data;
        }
        // 无则从数据库获取并存入redis
        data = (List<Tag>) selectInSql(null);
        return data;
    }

    /**
     * 从数据库获取并存入redis
     * @param id 查询所有则传 null
     * @return 查询所有返回的是 List,否则返回 pojo
     */
    private Object selectInSql(Object id) {
        // id
        if (id != null) {
            Tag tag = new Tag();
            tag.setId((String) id);
            tag.setIsDelete(false);
            tag = tagMapper.selectOne(tag);
            redisTemplate.opsForHash().put("data:tag", id, JsonUtils.objectToJson(tag));
            return tag;
        }
        // all
        Tag tag = new Tag();
        tag.setIsDelete(false);
        List<Tag> data = tagMapper.select(tag);
        redisTemplate.opsForHash().put("data:tag", "all", JsonUtils.objectToJson(data));
        return data;
    }
}

编写controller类

编写Response来统一controller返回的数据结构

@Data
@ApiModel(description = "统一controller返回的数据结构")
public class Response<T> {

    @ApiModelProperty("状态码,200标识正常,500为异常")
    public int code;
    @ApiModelProperty("code为200时可忽略消息,为500时可查看消息查找原因")
    public String message;
    public T data;

    public Response() {
        code = ResultConstCode.NORMAL;
        message = "操作成功";
    }

    public Response(int code) {
        this.code = code;
    }

    public Response(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public Response(int code, T data) {
        this.code = code;
        this.data = data;
    }

    public Response(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }
}

返回结果的统一状态码

public interface ResultConstCode {

    /**
     * 正常情况
     */
    int NORMAL = 200;

    /**
     * 发生了错误
     */
    int ERROR = 500;
}

这里只以findAll方法为例

@RestController
@RequestMapping("/tag")
@Api(tags = "tag表相关操作") // swagger中该类的名字
public class TagController {

    @Autowired
    private TagService tagService;

    @GetMapping("/findAll")
    @ApiOperation(value = "findAll", notes = "查询所有") // 说明方法
    public Response<List> findAll() {
        try {
            return new Response<>(ResultConstCode.NORMAL, "操作成功", tagService.selectAll());
        } catch (Exception e) { // 出现错误时返回错误码及错误信息
            return new Response<>(ResultConstCode.ERROR, e.getMessage());
        }
    }
}

编写完毕

启动服务,使用localhost:9001/doc.html 进入接口文档进行测试

遇到的问题

数据库字段问题:

字段名为关键词且没使用转义符则会报sql语句错误

@Column(name = "Id") 
private String id;
// 属性名与字段名不一致时使用该注解,字段名为mysql的关键词时需加数据库对应的转义符:@Column(name = "`from`")

knife4j配置问题:

knife4j版本不同,注解的配置也不同,需要注意,启动类可能需加@EnableSwagger2注解