做网站广告费装饰网站案例
web/
2025/9/28 1:37:49/
文章来源:
做网站广告费,装饰网站案例,徐州网约车公司哪家好,wordpress代码优化插件缘由
MongoDB数据库如下#xff1a; 如上截图#xff0c;使用MongoDB客户端工具DataGrip#xff0c;在filter过滤框输入{ profiles.alias: 逆天子, profiles.channel: }#xff0c;即可实现昵称和渠道多个嵌套字段过滤查询。
现有业务需求#xff1a;用Java代码来查询…缘由
MongoDB数据库如下 如上截图使用MongoDB客户端工具DataGrip在filter过滤框输入{ profiles.alias: 逆天子, profiles.channel: }即可实现昵称和渠道多个嵌套字段过滤查询。
现有业务需求用Java代码来查询指定渠道和创建日期在指定时间区间范围内的数据。
注意到creationDate是一个一级字段方便理解profiles字段和creationDate属于同一级是一个数组而profiles.channel是一个嵌套字段。
Java应用程序查询指定渠道通过Query注解profiles.channel和指定日期的数据Dao层或叫Repository层接口Interface代码如下
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;Repository
public interface AccountRepository extends MongoRepositoryAccount, String {Query({ profiles.channel: ?0 })ListAccount findByProfileChannelAndCreationDateBetween(String channel, Date start, Date end);
}单元测试代码如下
Test
public void testFindByProfileChannelAndCreationDateBetween() {String time 2024-01-21;String startTime time DateUtils.DAY_START;String endTime time DateUtils.DAY_END;Date start new Date();Date end new Date();try {start DateUtils.parseThenUtc(startTime);end DateUtils.parseThenUtc(endTime);} catch (ParseException e) {log.error(test failed: {}, e.getMessage());}ListAccount accountList accountRepository.findByProfileChannelAndCreationDateBetween(ChannelEnum.DATONG_APP.getChannelCode(), start, end);log.info(size:{}, accountList.size());
}输出如下size:70829。
没有报错但是并不能说明没有问题。根据自己对于业务的理解数据量显然不对劲此渠道的全量数据是这么多才差不多。
也就是说上面的Interface接口查询方法只有渠道条件生效日期没有生效
至于为什么没有生效请继续往下看。想看结论的直接翻到文末。
排查
不生效
MongoRepository是Spring Data MongoDB提供的继承MongoRepository之后就可以使用IDEA的智能提示快速编写查询方法。如下图所示 但是上面的这种方式只能对一级字段生效。如果想要过滤查询嵌套字段则派不上用场。
此时需要使用一个更强大的Query注解。
但是Query和JPA方式不能一起使用。也就是上面的方法findByProfileChannelAndCreationDateBetween查询方法经过简化后只保留一级字段然后嵌套字段使用Query方式
Query({ profiles.channel: ?0 })
ListAccount findByCreationDateBetween(String channel, Date s1, Date s2);依旧是不生效的。
版本1
基于上面的结论有一版新的写法
Query({ profiles.channel: ?0, creationDate: {$gte: ?1, $lte: ?2} })
ListAccount findByChannelAndCreationDate(String channel, Date start, Date end);此时输出size:28。这个数据看起来才比较正常虽然后面的结论证明不是正确的。
WARN告警
如果不过滤渠道呢查询某个日期时间段内所有渠道的全量用户数据
两种写法都可以
long countByCreationDateBetween(Date start, Date end);Query({ creationDate: {$gte: ?0, $lte: ?1} })
long countByCreationDate(Date start, Date end);等等。怎么第一种写法IDEA给出一个WARN
MongoDB日期
上面IDEA给出的Warning显而易见。因为MongoDB数据库字段定义是Instant类型
Data
Document
public class Account {Idprotected String key;private Instant creationDate Instant.now();private ListProfile profiles new ArrayList();private boolean firstTimeUsage true;
}IDEA作为宇宙最强IDE给出WARN自然是有道理的。
作为一个代码洁癖症患者看到IDEA的shi黄色告警无法忍受。假设IDEA告警没有问题极端少数情况下IDEA告警也有可能误报参考记一次Kotlin Visibility Modifiers引发的问题为了消除告警有两种方式
修改Account数据库实体类creationDate类型定义Instant改成DateRepository层接口方法不使用Date类型传参而使用Instant类型传参。
那到底应该怎么修改呢才能屏蔽掉IDEA的shi黄色告警WARN呢
单元测试
数据库持久化实体PO类日期字段类型定义到底该使用Date还是Instant类型呢
在Google搜索关键词MongoDB日期的同时不妨写点单元测试来执行一下。注此时此处行文看起来思路挺清晰但在遇到陌生的问题是真的是无头苍蝇
在保持数据库PO实体类日期字段类型定义不变的前提下有如下两个查询Interface方法
long countByCreationDateBetween(Date start, Date end);Query({ creationDate: {$gte: ?0, $lte: ?1} })
long countByCreationDate(Instant start, Instant end);单元测试
Resource
private MongoTemplate mongoTemplate;
Resource
private IAccountRepository accountRepository;Test
public void testCompareDateAndInstant() {String time 2024-01-21;String startTime time DateUtils.DAY_START;String endTime time DateUtils.DAY_END;Date start new Date();Date end new Date();try {start DateUtils.parseThenUtc(startTime);end DateUtils.parseThenUtc(endTime);} catch (ParseException e) {log.error(testCompareDateAndInstant failed: {}, e.getMessage());}Criteria criteria Criteria.where(creationDate).gte(start).lte(end);long count1 mongoTemplate.count(new Query(criteria), Account.class);// idea warnlong count2 accountRepository.countByCreationDateBetween(start, end);long count3 accountRepository.countByCreationDate(DateUtils.getInstantFromDateTimeString(startTime), DateUtils.getInstantFromDateTimeString(endTime));long count4 accountRepository.countByCreationDate(DateUtils.parse(startTime).toInstant(), DateUtils.parse(endTime).toInstant());log.info(date:{},count1:{},count2:{},count3:{},count4:{}, time, count1, count2, count3, count4);
}单元测试执行后打印输出date:2024-01-21,count1:35,count2:35,count3:32,count4:29。
换几个不同的日期count1和count2都是一致的。也就是说不管是使用Template还是Repository方式使用Date类型日期查询MongoDB数据结果是一样的。count3和count4使用Instant类型查询MongoDB数据结果不一致并且和Date类型不一致。
为啥呢
Instant vs Date MongoDB中的日期使用Date类型表示在其内部实现中采用一个64位长的整数该整数代表的是自1970年1月1日零点时刻UTC以来所经过的毫秒数。Date类型的数值范围非常大可以表示上下2.9亿年的时间范围负值则表示1970年之前的时间。 MongoDB的日期类型使用UTCCoordinated Universal Time进行存储也就是0时区的时间。我们处于8时区北京标准时间因此真实时间值比ISODateMongoDB存储时间多8个小时。也就是说MongoDB存储的时间比ISODate早8小时。
验证8小时
通过DataGrip查看数据库集合字段类型是ISODate 其格式是yyyy-MM-ddTHH:mm:ss.SSSZ 然后再看看时区问题。
同一个用户产生的数据用户唯一ID都是65af62bee13f080008816500在MySQL和MongoDB里都有记录。
MySQL数据如下因为涉及敏感信息截图截得比较小熟悉DataGrip的同学看到Tx: Auto应该不难猜到就是MySQL 而MongoDB记录的数据如下同样也是出于截图敏感考虑主流数据库里使用到ObjectId的应该不多吧MongoDB是一个 不难发现。MySQL里记录的数据比MongoDB里记录的数据晚8小时也是一个符合实际的数据。
PS此处的所谓符合实际指的是符合用户习惯我们App是一款低频App极少有用户在半夜或凌晨使用而MongoDB里则记录着大量凌晨的数据实际上应该是北京时间早上的用户使用记录和数据。
从上面两个截图来看虽然有打码处理但依稀可以看到确实参考下面在线加解密工具网站是同一个用户手机号产生的两个不同数据库MySQL及MongoDB数据。
证明MongoDB里存储的数据确实比MySQL的数据早8小时。
解决方案
PO实体类保持Instant类型不变Repository层Interface接口方法传参Instant。平常使用的Date如何转换成Instant呢
直接toInstant()即可也就是上面的单元测试里面的第四种方式。方法定义
/*** 加不加Query注解都可以。* 加注解的话方法名随意见名知意即可。* 不加注解的话则需要保证查询字段是MongoDB一级字段并且满足JPA约定大于配置规范。*/
Query({ creationDate: {$gte: ?0, $lte: ?1} })
long countByCreationDate(Instant start, Instant end);查询方法
long count accountRepository.countByCreationDate(DateUtils.parse(startTime).toInstant(), DateUtils.parse(endTime).toInstant());源码分析
Date.toInstant()源码
private transient BaseCalendar.Date cdate;
private transient long fastTime;public Instant toInstant() {return Instant.ofEpochMilli(getTime());
}/*** Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT* represented by this Date object.*/
public long getTime() {return getTimeImpl();
}private final long getTimeImpl() {if (cdate ! null !cdate.isNormalized()) {normalize();}return fastTime;
}private final BaseCalendar.Date normalize() {if (cdate null) {BaseCalendar cal getCalendarSystem(fastTime);cdate (BaseCalendar.Date) cal.getCalendarDate(fastTime,TimeZone.getDefaultRef());return cdate;}// Normalize cdate with the TimeZone in cdate first. This is// required for the compatible behavior.if (!cdate.isNormalized()) {cdate normalize(cdate);}// If the default TimeZone has changed, then recalculate the// fields with the new TimeZone.TimeZone tz TimeZone.getDefaultRef();if (tz ! cdate.getZone()) {cdate.setZone(tz);CalendarSystem cal getCalendarSystem(cdate);cal.getCalendarDate(fastTime, cdate);}return cdate;
}Instant.java源码
/*** Constant for the 1970-01-01T00:00:00Z epoch instant.*/
public static final Instant EPOCH new Instant(0, 0);public static Instant ofEpochMilli(long epochMilli) {long secs Math.floorDiv(epochMilli, 1000);int mos Math.floorMod(epochMilli, 1000);return create(secs, mos * 1000_000);
}
private static Instant create(long seconds, int nanoOfSecond) {if ((seconds | nanoOfSecond) 0) {return EPOCH;}if (seconds MIN_SECOND || seconds MAX_SECOND) {throw new DateTimeException(Instant exceeds minimum or maximum instant);}return new Instant(seconds, nanoOfSecond);
}附
敏感数据加解密
上面截图MySQL表里对手机号没有加密处理直接明文存储而在MongoDB数据库里则进行ECB加密。加密工具类略
此处附上一个好用的在线加密工具网站可用于加密手机号等比较敏感的数据编码一般选择Base64位数、模式、填充、秘钥等信息和工具类保持一致除密钥外一般都是默认
工具类
DateUtils.java工具类源码如下
public static final String DAY_START 00:00:00;
public static final String DAY_END 23:59:59;
public static final String DATE_FULL_STR yyyy-MM-dd HH:mm:ss;/*** 使用预设格式提取字符串日期** param date 日期字符串*/
public static Date parse(String date) {return parse(date, DATE_FULL_STR);
}/*** 不建议使用1945-09-01 和 1945-09-02 with pattern yyyy-MM-dd 得到不一样的时间数据* 前者 CDT 后者 CST* 指定指定日期字符串*/
public static Date parse(String date, String pattern) {SimpleDateFormat df new SimpleDateFormat(pattern);try {return df.parse(date);} catch (ParseException e) {log.error(parse failed, e);return new Date();}
}public static Date parseThenUtc(String date, String dateFormat) throws ParseException {SimpleDateFormat format new SimpleDateFormat(dateFormat);Date start format.parse(date);Calendar calendar Calendar.getInstance();calendar.setTime(start);calendar.add(Calendar.HOUR, -8);return calendar.getTime();
}/*** 减 8 小时*/
public static Date parseThenUtc(String date) throws ParseException {return parseThenUtc(date, DATE_FULL_STR);
}中文解析
SimpleDateFormat作为Java开发中最常用的API之一。
你真的熟悉吗 线程安全问题 是否支持中文日期解析呢
具体来说是否支持如yyyy年MM月dd日格式的日期解析
测试程序
public static void main(String[] args) {log.info(getNowTime(yyyy年MM月dd日));
}public static String getNowTime(String type) {SimpleDateFormat df new SimpleDateFormat(type);return df.format(new Date());
}打印输出如下
2024年01月23日结论SimpleDateFormat支持对中文格式的日期进行解析。
看一下SimpleDateFormat的构造函数源码
public SimpleDateFormat(String pattern) {this(pattern, Locale.getDefault(Locale.Category.FORMAT));
}继续深入查看Locale.java源码
private static Locale initDefault(Locale.Category category) {Properties props GetPropertyAction.privilegedGetProperties();return getInstance(props.getProperty(category.languageKey,defaultLocale.getLanguage()),props.getProperty(category.scriptKey,defaultLocale.getScript()),props.getProperty(category.countryKey,defaultLocale.getCountry()),props.getProperty(category.variantKey,defaultLocale.getVariant()),getDefaultExtensions(props.getProperty(category.extensionsKey, )).orElse(defaultLocale.getLocaleExtensions()));
}大概得知SimpleDateFormat对于本地化语言的支持是通过Locale国际化实现的。
ISODate
另外在使用SimpleDateFormat解析这种时间时需要对T和Z加以转义。
public static final String FULL_UTC_STR yyyy-MM-ddTHH:mm:ssZ;
public static final String FULL_UTC_MIL_STR yyyy-MM-ddTHH:mm:ss.SSSZ;public static String getBirthFromUtc(String dateStr) {SimpleDateFormat df new SimpleDateFormat(FULL_UTC_STR);try {Date date df.parse(dateStr);Calendar calender Calendar.getInstance();calender.setTime(date);calender.add(Calendar.HOUR, 8);return date2Str(calender.getTime(), DATE_SMALL_STR);} catch (ParseException e) {throw new RuntimeException(e);}
}结论
几个结论
JPA写法对于单表查询非常简单借助于IDEA智能提示可以快速写出查询Interface方法JPA很强但对于关系型数据库的多表Join查询或MongoDB的嵌套字段查询则几乎派不上用场Query通过注解的方式可以大大简化API的使用Query写法和JPA写法不能混为一谈Query也不是万能的。必要时还是得使用QBEQuery By Example或Query Criteria
参考
MongoDB进阶与实战微服务整合、性能优化、架构管理
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/83068.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!