文章目录
一、前期环境准备
由于并不打算从零开始,只是为了验证一下我设计的接口。所以最便捷的方法是使用一个用了mybatis-plus的项目,github上随便找,从里面开始着手,而且数据库我已经建立好了。
二、接口设计:展示所有信息的列表
类型 GET
描述 : 查询出所有单位的详细信息
请求参数:无
URL http://127.0.0.1:8080/api/show-all-tasks
Response
{
"status": 200,
"message": "Success",
"tasks": [
{
"case_id": "0001",
......
"upload_time": "2023-10-01T12:00:00Z",
"data_overview": {
"app_cpu_avg": "20",
......
"b_temp_range": "25"
}
},
{
"case_id": "0002",
......
"upload_time": "2023-10-02T12:00:00Z",
"data_overview": {
"app_cpu_avg": "25",
......
"b_temp_range": "28"
}
},
{
"case_name": "用例3",
......
"upload_time": "2023-10-03T12:00:00Z",
"data_overview": {
"app_cpu_avg": "32",
......
"b_temp_range": "34"
}
}
]
}
三、详细步骤
1.接口分析
这个接口涉及到多表查询,用entity肯定不能实现我的需求,到底是用VO还是DTO呢?
选择VO还是DTO
VO(View Object):
用途:主要用于前端展示,包含前端需要展示的所有数据。
特点:通常包含较多的字段,可能包含一些计算或聚合的数据。
DTO(Data Transfer Object):
用途:主要用于数据传输,包含后端与前端之间传输的数据。
特点:通常包含较少的字段,只包含必要的数据。
根据我的接口设计,响应包含了多个字段,包括任务信息和数据概览,这些数据可能来自多个表。因此,使用VO更为合适,因为它可以包含所有前端需要展示的数据。
1.设计出我的VO:
TaskVO.java
package cn.ityao.cat.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.Date;
@Data
public class TaskVO {
/**
* 用例编号
*/
private String caseId;
//...........
//注意和数据库中的字段对齐
/**
* 上传时间
*/
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "GMT+8")
private Date uploadTime;
/**
* 数据概览
*/
private DataOverviewVO dataOverview;
}
2.controller 层
TaskController.java
package cn.ityao.cat.controller;
import cn.ityao.cat.service.TaskService;
import cn.ityao.cat.vo.TaskVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class TaskController {
@Autowired
private TaskService taskService;
@GetMapping("/show-all-tasks")
public ResponseEntity<Map<String, Object>> showAllTasks() {
List<TaskVO> tasks = taskService.getAllTasks();
Map<String, Object> response = new HashMap<>();
response.put("status", 200);
response.put("message", "Success");
response.put("tasks", tasks);
return ResponseEntity.ok(response);
}
}
3.service 层
TaskService.java
package cn.ityao.cat.service;
import cn.ityao.cat.vo.TaskVO;
import java.util.List;
public interface TaskService {
List<TaskVO> getAllTasks();
}
TaskServiceImpl.java
package cn.ityao.cat.service.impl;
import cn.ityao.cat.mapper.TaskMapper;
import cn.ityao.cat.service.TaskService;
import cn.ityao.cat.vo.TaskVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TaskServiceImpl implements TaskService {
@Autowired
private TaskMapper taskMapper;
@Override
public List<TaskVO> getAllTasks() {
return taskMapper.selectAllTasks();
}
}
4.mapper 层
TaskMapper.java
package cn.ityao.cat.mapper;
import cn.ityao.cat.common.DataOverviewTypeHandler;
import cn.ityao.cat.entity.Task;
import cn.ityao.cat.vo.DataOverviewVO;
import cn.ityao.cat.vo.TaskVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.type.JdbcType;
import java.util.List;
public interface TaskMapper extends BaseMapper<Task> {
@Select("SELECT t.*, d.device_mode, d.platform " +
"FROM case_list_table t " +
"JOIN device_detail_table d ON t.device_id = d.device_id")
@Results({
@Result(column = "data_overview", property = "dataOverview", javaType = DataOverviewVO.class, jdbcType = JdbcType.VARCHAR, typeHandler = DataOverviewTypeHandler.class)
})
List<TaskVO> selectAllTasks();
}
四、疑惑和思考
1️⃣数据库中的json字段如何匹配实体类?
1.使用一个VO专门对应json中的字段
2.使用转换类
package cn.ityao.cat.common;
import cn.ityao.cat.vo.DataOverviewVO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(DataOverviewVO.class)
public class DataOverviewTypeHandler extends BaseTypeHandler<DataOverviewVO> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, DataOverviewVO parameter, JdbcType jdbcType) throws SQLException {
try {
ps.setString(i, objectMapper.writeValueAsString(parameter));
} catch (JsonProcessingException e) {
throw new SQLException("Error converting DataOverviewVO to JSON string", e);
}
}
@Override
public DataOverviewVO getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return json == null ? null : fromJson(json);
}
@Override
public DataOverviewVO getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
return json == null ? null : fromJson(json);
}
@Override
public DataOverviewVO getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
return json == null ? null : fromJson(json);
}
private DataOverviewVO fromJson(String json) {
try {
return objectMapper.readValue(json, DataOverviewVO.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Error converting JSON string to DataOverviewVO", e);
}
}
}
3.//另外配置在mapper中
@Results({
@Result(column = "data_overview", property = "dataOverview", javaType = DataOverviewVO.class, jdbcType = JdbcType.VARCHAR, typeHandler = DataOverviewTypeHandler.class)
})
2️⃣接口响应为啥没有时间这一栏?
因为在实体类的时间属性设置了@JsonProperty(access = JsonProperty.Access.WRITE_ONLY):
这个注解设置access属性为WRITE_ONLY,意味着该字段只在反序列化(即从 JSON 转换为 Java 对象)时被读取,而在序列化(即从 Java 对象转换为 JSON)时不会被包含在输出中。
3️⃣关于mapper里面可以不使用注解还可以怎么查询数据库?
方法一:使用注解
@Select("SELECT t.*, d.device_mode, d.platform, c.case_name " +
"FROM case_list_table t " +
"JOIN device_detail_table d ON t.device_id = d.device_id " +
"JOIN case_detail_table c ON t.case_id = c.case_id")
@Results({
@Result(column = "data_overview", property = "dataOverview", javaType = DataOverviewVO.class, jdbcType = JdbcType.VARCHAR, typeHandler = DataOverviewTypeHandler.class)
})
方法二:使用 XML 映射文件
<resultMap id="TaskVOMap" type="cn.ityao.cat.vo.TaskVO">
<result column="data_overview" property="dataOverview" typeHandler="cn.ityao.cat.common.DataOverviewTypeHandler"/>
</resultMap>
<select id="selectAllTasks" resultMap="TaskVOMap">
SELECT t.*, d.device_mode, d.platform, c.case_name
FROM case_list_table t
JOIN device_detail_table d ON t.device_id = d.device_id
JOIN case_detail_table c ON t.case_id = c.case_id
</select>
方法三:使用 MyBatis Plus 通用 Mapper 方法
继承通用接口,本查询继承了但感觉和前面两种方法差不多
public interface TaskMapper extends BaseMapper<Task> {
List<TaskVO> selectAllTasks();
}
追问:三种方法对查询效率有影响吗?三种方法的适配场景?
三种方法在查询效率上的影响通常可以忽略不计,因为它们最终都会生成相同的 SQL 语句并在数据库中执行。查询效率主要取决于 SQL 语句的优化和数据库的性能,而不是 MyBatis 的实现方式。
使用注解:
适用:简单的查询和小型项目。
优点:代码简洁,快速开发。
缺点:复杂查询时,代码可能变得冗长。
使用 XML 映射文件:
适用:复杂的查询和大型项目。
优点:集中管理 SQL 和映射关系,便于维护。
缺点:需要维护 XML 文件,对简单查询可能繁琐。
使用 MyBatis Plus 通用 Mapper 方法:
适用:需要快速开发和处理复杂查询的中大型项目。
优点:提供通用 CRUD 方法,结合 MyBatis 的灵活性和 MyBatis Plus 的便捷性。
缺点:需要学习 MyBatis Plus,复杂查询可能仍需 XML。
4️⃣三个表查询怎么提升查询效率?
如果经常需要根据多个字段进行查询,可以考虑创建复合索引(例如,CREATE INDEX idx_case_list_device_case ON case_list_table(device_id, case_id);),这可能比单独的索引更有效。
使用EXPLAIN命令来分析你的查询计划,看看这些索引是否被实际使用
5️⃣如何自定义URL(协议➕主机名➕端口号➕路径➕查询参数➕片段标识符)?
application.yml 中设置 server.address 和port 。一般是自己的IP和自定义的端口。
但是假如你设置为 192.168.1.100,那么服务器将尝试绑定到这个 IP 地址。如果这个 IP 地址在你的网络中不存在或者不可达,服务器将无法启动。
6️⃣如图启动时候显示的日志怎么自定义的?
如果你没有在 application.yml 中配置banner,但启动时输出了 banner.txt 的内容,这通常是因为Spring Boot默认加载了类路径下的 banner.txt 文件。你可以通过配置文件或代码来禁用或自定义banner。
7️⃣http的丢包怎么控制的,数据量太大怎么办?
在处理HTTP请求时,特别是当数据量较大时,可能会遇到丢包问题。以下是一些控制丢包和处理大数据量的策略:
1. 使用分页(Pagination)
分页是一种常见的方法,用于处理大量数据。通过分页,可以将数据分成多个较小的部分,每次只返回一部分数据。
示例:
GET http://127.0.0.1:8080/api/list?page=1&size=10
Response:
{
"status": 200,
"message": "Success",
"tasks": [
// 返回第1页的10条记录
],
"page": 1,
"size": 10,
"totalPages": 3,
"totalElements": 25
}
2. 使用压缩(Compression)
启用HTTP压缩可以显著减少数据传输的大小。常见的压缩算法包括Gzip和Deflate。
示例:
在服务器端启用Gzip压缩:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CommonsRequestLoggingFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("format").
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
@Bean
public FilterRegistrationBean<GzipFilter> gzipFilter() {
FilterRegistrationBean<GzipFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new GzipFilter());
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
}
3. 使用缓存(Caching)
通过缓存可以减少对数据库的重复查询,从而提高性能并减少数据传输。
示例:
在Spring Boot中启用缓存:
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
}
在服务方法上使用缓存注解:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class TaskService {
@Cacheable("tasks")
public List<Task> getTasks() {
// 查询数据库并返回任务列表
}
}
4. 优化数据库查询
优化数据库查询可以减少数据传输量和提高查询效率。
示例:
使用索引、优化SQL查询、避免全表扫描等。
5. 使用WebSocket
对于实时数据传输,可以考虑使用WebSocket,它可以提供更稳定的双向通信。
示例:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyWebSocketHandler(), "/websocket");
}
}
*** 6. 监控和日志***
监控HTTP请求和响应,记录日志,以便及时发现和解决问题。
示例:
在Spring Boot中配置日志记录:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CommonsRequestLoggingFilter;
@Configuration
public class RequestLoggingFilterConfig {
@Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(false);
filter.setAfterMessagePrefix("REQUEST DATA : ");
return filter;
}
}