业务背景:一般时序数据会有保存数据周期,例如三个月或者是半年之久,一般方案是定时任务调用dao层删除数据,不方便统一管理,扩展性也不够好,这里通过并发流线程池实现并行执行删除逻辑.
一、接口定义
package com.boot.skywalk.task;
/**
* 通用数据清理任务接口
*/
@FunctionalInterface
public interface ICleanData {
/**
* 清理数据
*/
void cleanData();
}
二、业务层模拟Dao层删除逻辑
@Slf4j
@Component
public class ConfigService implements ICleanData {
@Autowired
private ConfigDao configDao;
@Override
public void cleanData() {
configDao.deleteConfigData();
log.info("ConfigService Clean Success");
}
}
@Slf4j
@Component
public class StatService implements ICleanData {
@Autowired
private StatDao statDao;
@Override
public void cleanData() {
statDao.deleteStatData();
log.info("StatService Clean Success");
}
}
三、清理任务
@Slf4j
@Component
public class CleanDataTask {
/**
* 并发流定时任务清理过期数据,集群部署时候,分布式任务只在一个节点运行即可,XXL-JOB和Quartz中
*/
public void clean(){
// 获取所有需要实现业务清理数据的Bean
List<ICleanData> dataList =CommonBeanUtils.getBeanList(ICleanData.class);
Instant start=Instant.now();
log.info("start clean data");
// 并发流同时执行业务层数据清理任务
dataList.parallelStream().forEach(cleanable->{
// 具体异常在各自实现逻辑中单独捕获
cleanable.cleanData();
});
Instant end=Instant.now();
long costTime = Duration.between(start, end).getSeconds();
log.info("finish clean data,cost time={}", costTime);
}
}
四、测试运行

任务并行执行删除逻辑耗时2秒钟,并发流线程池ForkJoinPool.业务开发只需继承数据清理接口实现各自自己的数据清理逻辑即可,这里可以采用自定义线程池+CountDownLatch来实现,业务线程逻辑更加好控制.
【附录SpringBoot项目时间处理】
建表SQL如下:新增和修改时候自动更新时间
create table user_info (
id int primary key auto_increment comment '主键ID',
user_name varchar(50) not null comment '用户名称',
create_time datetime not null default CURRENT_TIMESTAMP comment '创建时间',
update_time datetime not null default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '更新时间'
) engine = Innodb default charset = utf8mb4 comment '用户信息表';
新增修改都是自动更新时间.

基于MyBatis-Plus的方式的三层
Mapper层:
package com.boot.skywalk.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.boot.skywalk.entity.UserInfo;
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
Xml:
<?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.boot.skywalk.mapper.UserInfoMapper">
</mapper>
service
package com.boot.skywalk.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.boot.skywalk.entity.UserInfo;
public interface UserInfoService extends IService<UserInfo> {
}
impl
package com.boot.skywalk.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.boot.skywalk.entity.UserInfo;
import com.boot.skywalk.mapper.UserInfoMapper;
import com.boot.skywalk.service.UserInfoService;
import org.springframework.stereotype.Service;
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
}
controller
package com.boot.skywalk.controller;
import com.boot.skywalk.entity.UserInfo;
import com.boot.skywalk.service.UserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Slf4j
@RestController
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
/**
* 查询全部列表
* @return
*/
@RequestMapping("/boot/users")
public List<UserInfo> getUserInfoList(){
return userInfoService.list();
}
}
查询列表返回数据.

时间处理策略:
①、前段处理展示.
②、SimpleDateFormat格式化或者是DateTimeFormatter格式化来增加字段处理,大型项目有专门的TimeUtil来转换各种时间,项目中的时间是point来存储时间戳然后进行转化.
package com.boot.skywalk.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import javax.persistence.Table;
import java.util.Date;
@Data
@Table(name="user_info")
public class UserInfo {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField("user_name")
private String userName;
@TableField("create_time")
@JsonIgnore// 输出结果时隐藏此字段
private Date createTime;
@TableField("update_time")
@JsonIgnore// 输出结果时隐藏此字段
private Date updateTime;
// 时间格式化后的字段,数据库不存在的字段
@TableField(exist = false)
private String ctime;
// 时间格式化后的字段,数据库不存在的字段
@TableField(exist = false)
private String utime;
}
controller修改.
package com.boot.skywalk.controller;
import com.boot.skywalk.entity.UserInfo;
import com.boot.skywalk.service.UserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.List;
@Slf4j
@RestController
public class UserInfoController {
// 定义时间格式化对象和定义格式化样式
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Autowired
private UserInfoService userInfoService;
/**
* 查询全部列表
* @return
*/
@RequestMapping("/boot/users")
public List<UserInfo> getUserInfoList(){
List<UserInfo> list = userInfoService.list();
list.forEach(user->{
user.setCtime(dateFormat.format(user.getCreateTime()));
user.setUtime(dateFormat.format(user.getUpdateTime()));
});
return list;
}
}
③、使用@JsonFormat添加对应注解即可
@TableField("update_time")
//@JsonIgnore// 输出结果时隐藏此字段
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date updateTime;
④、或者在全局配置文件中进行配置
【附录定时任务创建分表】也可以基于并发流创建然后配置完整的告警管理,实现统一接口然后并发流创建.
Xml
<mapper namespace="com.boot.skywalk.mapper.SupportMapper">
<update id="createTable" parameterType="String">
create table ${tableName} (
id int(11) auto_increment primary key,
user_name varchar(20) not null,
user_password varchar(20) not null
);
</update>
</mapper>
package com.boot.skywalk.mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface SupportMapper {
/**
* createTable
* @param tableName
* @return int
*/
int createTable(@Param("tableName") String tableName);
}
控制台执行日志:
information_schema.TABLE表中查看

【验证数据库字符串处理时间】
数据库建表字段 为timestamp/datatime,前段时间为字符串处理

{
"name": "Dubbo",
"address": "GuangZhou",
"create_time": "2023-01-14 20:19:24"
}
create table result(
id int(11) auto_increment primary key,
`name` varchar(10) not null,
address varchar(30) not null,
create_time timestamp
);
接口层时间获取json数据,@RequestBody接收,比较简单.
@PostMapping("/insert/resultVo")
public String saveResultVo(@RequestBody CustomResultVo customResultVo){
try {
resultMapper.save(customResultVo);
} catch (Exception e) {
log.info("save error",e);
}
return "Success";
}
这里测试使用jdbcType的DATE类型
<!--数据 -->
<insert id="save" useGeneratedKeys="true" keyProperty="id" parameterType="com.boot.skywalk.vo.CustomResultVo">
insert into result(`name`,`address`,`create_time`) values(#{name,jdbcType=VARCHAR},#{address,jdbcType=VARCHAR},#{createTime,jdbcType=DATE})
</insert>
使用Java的Date接收前端时间json字符串参数
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CustomResultVo {
private int id;
private String name;
private String address;
/**
* 设置时间格式,
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@JsonProperty("create_time")
private Date createTime;
}
MyBatis的JdbcType枚举,这里转换使用TimeStamp

数据库建表语句


查询数据库,发现只有年月日

XML中修改为TimeStamp
再次查询数据,保存正常。

MyBatis处理MySQL字段类型date与datetime
JdbcTemplate获取MySQL源表信息.
// 查询JDBC的所有表
public List<String> getAllTableList(){
List<String> tableList = jdbcTemplate.query("SHOW TABLES", new RowMapper<String>() {
@Override
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getString(1);
}
});
return tableList;
}
// 查询指定数据库的所有表以及表类型信息
public List<Table> queryTable(){
KeyHolder keyHolder = new GeneratedKeyHolder();
List<Table> query = jdbcTemplate.query("SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_KEY, IS_NULLABLE\n" +
"FROM INFORMATION_SCHEMA.COLUMNS\n" +
"WHERE TABLE_SCHEMA = 'test'", new TableRowMapper());
return query;
}
class TableRowMapper implements RowMapper<Table>{
@Override
public Table mapRow(ResultSet rs, int rowNum) throws SQLException {
Table table = new Table();
table.setTableName(rs.getString("TABLE_NAME"));
table.setColumnName(rs.getString("COLUMN_NAME"));
table.setDateType(rs.getString("DATA_TYPE"));
table.setColumnKey(rs.getString("COLUMN_KEY"));
table.setIsNullAble(rs.getString("IS_NULLABLE"));
return table;
}
}
class Table{
private String tableName;
private String columnName;
private String dateType;
private String columnKey;
private String isNullAble;
public Table() {
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getDateType() {
return dateType;
}
public void setDateType(String dateType) {
this.dateType = dateType;
}
public String getColumnKey() {
return columnKey;
}
public void setColumnKey(String columnKey) {
this.columnKey = columnKey;
}
public String getIsNullAble() {
return isNullAble;
}
public void setIsNullAble(String isNullAble) {
this.isNullAble = isNullAble;
}
@Override
public String toString() {
return "Table{" +
"tableName='" + tableName + '\'' +
", columnName='" + columnName + '\'' +
", dateType='" + dateType + '\'' +
", columnKey='" + columnKey + '\'' +
", isNullAble='" + isNullAble + '\'' +
'}';
}
返回JSON信息如下:

SQL查询语句如下:
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_KEY, IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'test';
SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'test'
查询表字段类型:

写入数据库的datetime类型数据处理
①、使用mybatis的xml.
<insert id="saveUser" parameterType="com.boot.skywalk.entity.UserInfo">
insert into user_info(user_name,create_time,update_time) VALUES (#{userName},#{createTime,jdbcType=TIMESTAMP},#{updateTime,jdbcType=TIMESTAMP})
</insert>
上层传递参数.
UserInfo userInfo = new UserInfo();
userInfo.setId(5);
userInfo.setUserName("Puck");
Date startTime = new Date();
Date endTime = new Date();
userInfo.setCreateTime(startTime);
userInfo.setUpdateTime(endTime);
userInfoService.saveUser(userInfo);
全局开启日期格式化
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
②、使用JdbcTemplate处理
使用具名参数: 使用 SqlParameterSource作为参数
UserInfo userInfo = new UserInfo();
userInfo.setId(7);
userInfo.setUserName("Boot");
Date startTime = new Date();
Date endTime = new Date();
userInfo.setCreateTime(startTime);
userInfo.setUpdateTime(endTime);
// JdbcTemplate的写入datetime,使用参数占位方式
String sql="insert into user_info(user_name,create_time,update_time) values(:userName,:createTime,:updateTime)";
SqlParameterSource sqlParameterSource=new BeanPropertySqlParameterSource(userInfo);
jdbcTemplate.update(sql,sqlParameterSource);
使用Map参数占位
UserInfo userInfo = new UserInfo();
userInfo.setId(7);
userInfo.setUserName("Boot");
Date startTime = new Date();
Date endTime = new Date();
userInfo.setCreateTime(startTime);
userInfo.setUpdateTime(endTime);
// JdbcTemplate的写入datetime,使用in方式
String sql="insert into user_info(user_name,create_time,update_time) values(:user_name,:create_time,:update_time)";
HashMap paramMap = new HashMap<>();
paramMap.put("user_name",userInfo.getUserName());
paramMap.put("create_time",userInfo.getCreateTime());
paramMap.put("update_time",userInfo.getUpdateTime());
jdbcTemplate.update(sql,paramMap);
jdbcTemplate.update(sql,sqlParameterSource);
查看事务提交


五、总结归纳
不仅仅是数据清理,也包括一些需要并行的逻辑可以采用并发流的方式来执行,注意是IO密集型还是CPU密集型选择对应的框架和线程池即可.

本文介绍如何通过Spring Boot和并发流技术,利用ICleanData接口实现数据清理,简化定时任务管理,并演示了如何在业务层模拟Dao操作。重点涉及数据库维护策略和时间处理,包括自动时间更新及日期格式化。


4661

被折叠的 条评论
为什么被折叠?



