首先说一下,这个功能其实也不算是很少见,但是也没那么常见。
有的时候涉及到的数据量比较大,我们会用一个很时髦的词“分表”来实现。但是其实更多时候这个表是按照时间来“分”的,所以我这里是一种不那么动态,但是也实现了分表的方式实现的。
先说一下大概的情况:我们这里是硬件设备数据的存储,每个设备开机的情况下30s推送一次数据。简单算一下,如果100个设备同时开机,一个月的记录数就是:
每分钟2条 * 60分钟 * 24小时 *100台机器 * 31天
所以说这个按照时间分表确实是有必要的。甚至说我这里按照月分只是一个根据具体情况定下来的一个行为,如果有必要按照天分也ok啊,当然了,不管是按照月,还是天,甚至星期我这个方法都是可以的.下面说下具体的做法。
首先我这里简单说下我用的是spring boot+mybatis (plus)框架,所以很多实现也是基于这两个框架的实现。还有我这里只是提供一个思路,具体的实现不建议和我学,容易被骂。
创建表
既然要按照月分表,起码每个月的表是要有的吧,但是一口气创建几十个未来月份的表也太扯了,所以这里是用定时任务创建表实现的。
这个定时任务不难,创建表也不难,但是刚刚说的不要和我学确实真的, 真的我用了一种非常偷懒的方式,我在mybatis的注解里直接写了sql语句,大家有时间的话还是建议在xml里写,我从定时任务开始贴代码:
(顺便复习一下spring boot中的定时器)
- 启动类加上支持定时任务的注释:@EnableScheduling
- 随便写一个类,纳入容器中,然后cron表达式写清楚时间,里面就是具体定时执行的代码了:
@Component
public class Quartz {
@Autowired
UserEquipDao userEquipDao;
@Scheduled(cron = "0 0 0 28 * ?")
public void createTable() {
Date date = DateUtils.addDateDays(new Date(), 5);
String name = new SimpleDateFormat("yyyyMM").format(date);
userEquipDao.createTable(name);
}
}
注意:我这里是每个月是28号启动定时任务(因为这个版本不支持cron中L表示最后一天,因为每个月都有28号,所以愉快的定在了每月28执行),然后这个月生成的是下个月的记录表,所以我这里是把年月当成参数传的,每月28日加五天,肯定是下个月的月份,这都是细节处理,具体要根据情况做。
然后注意了,这个UserEquipDao userEquipDao其实就是随便一个能操作数据库的接口,在这里只是借用一下而已,你用 用户表啊,订单表啊都是可以的。
接下来我让你看看具体这个方法里怎么写的:
当当当!惊不惊喜?意不意外?套着insert的壳子,里面就是一个创建表的语句。只要这个sql语句能正常运行就没问题啦!
有两个小的注意点:
- 这个name参数要用注解,不然会说找不到
- 在sql语句中用的是{}是单纯的文字替换,不能阻挡注入攻击,但是#{}是set进去的字符串,平时我们用#{}是更好的结果,但是因为这个语句的特殊,所以一定要用${}(我这块说的比较唯心,原理也没说清楚,反正不懂的记住就行了)。
然後只要写法没问题,这个创建表的语句是不会有问题的啦,建议大家可以定时到一个比较近的时间试一遍看看能不能正常的创建表,一般不会有问题的。
对了,我建议大家把sql语句写到xml文件里,毕竟我这样写法不规范。
对表的crud操作
其实刚刚的表创建完了,剩下的就没啥难的了。主要就是crud操作了。查询的话最后说。
首先一个和表对应的实体类是必不可少的,这个就不用多说了,只不过实体的类名字可以自由一点点了。我的话也没那么放飞自己,只是实体里,没有表中后面的时间而已。属性名我是严格按照数据库字段来的,这个没啥好说的。
但是因为dao和表名不一样,所以所有的数据库操作都用sql语句实现的。并且增加和查询都要具体到某个表中,所以这个时间参数还是要传过来,并且用${}拼接到sql语句中的。
下面是简单的两个demo:
/**
* <p>
* Mapper 接口
* </p>
*
* @author lisijia
* @since 2020-07-29
*/
@Mapper
public interface SysRecordDao extends BaseMapper<SysRecord> {
@Insert("INSERT INTO sys_record${name} (no,content,create_time) VALUES(#{sysRecord.no},#{sysRecord.content},#{sysRecord.createTime})")
void addRecord(@Param("name")String name,SysRecord sysRecord);
@Select("SELECT * FROM sys_record${name} WHERE no = #{no} ORDER BY create_time DESC LIMIT 1")
SysRecord selectLastRecord(@Param("name")String name,@Param("no")String no);
}
细节上注意两个参数的拼接方式的不同。但是基本用法应该很好理解了。而且这个也要看需求和细节,比如我这边因为最后一条记录一定是3分钟前,所以只要查当月的就行了,这个name参数是我用yyyyMM获取来的。
因为暂时这个只是一个记录的作用,所以是没有修改删除操作的,所以这里也不多说了。
查询操作
查询一直都是一个大问题,而且很多时候查询操作确实挺多,但是像是这种按照时间分表了,一般多是记录多的,也不会出现一查查好几个月的数据,反正我们这个要求查询时间跨度不能超过三天,所以我这里也是一种极简的方式。起始时间不过不在一个月,则分两次查询,结果集再放到一起,如果起始时间是一个月,就是一个查询而已。
当然了,这块其实也可以用union连表查询,我这算是偷懒了吧,反正是勉强把功能实现了。
特定的情况有特定的处理措施,这也是我最开始就把这么做的背景讲出来的原因,这个方法适合我这样的,但是不一定也适合别的,做之前要仔细思量。然後今天的笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利,java技术交流群130031711,欢迎各位踊跃加入!