从零开始的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注解