目录
吐槽:
原因:
思路:
代码片段:
建议:
最近有点工作说不忙吧也忙,说忙吧也不忙,对接各种卡,你能想象一下你平常开车能开到八十迈却因为堵车只能慢慢挪的节奏,蛋蛋的忧伤。
十一月份进项目起就发现各种表的主键都TM,UUID,真是各种骚操作都有了。因为种种原因,忍到现在趁着堵车的时间,搞了一套主键生成策略(PS:我以前老总监的东西,改改就拿来用了)。
因为这套主键策略是用于分布式场景业务(例如我售后订单业务,我希望售后业务ID格式位prefix + yyyy-MM-dd + 9位数自增数值,不满足0补位。生成结果:SH20190116000000001 ),所以我们需要考虑它的唯一性或者说分布式锁机制,大家都知道redis 针对操作来说是单线程的,所以这边我利用了它的自增特性来解决了这个核心的问题。关于redis 自增存取的key值 key = tableName+yyyy-MM-dd格式,例如:我库表有一百张表,每天生产的key 一百个,一年就是365 X 100=3.65W个key,随着时间的递增key会越来越多,所以建议这部分数据最好存取到指定区域(分片、hash取模)方便管理,后期也方便用来做统计报表。当然你只想做单纯分布式主键key = tableName就可以了。
一、DATE工具类
package com.honghu.cloud.utils;
import java.sql.Timestamp;
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Date Utility Class. And please use Apache common DateUtils to add, judge
* equality, round, truncate date.
* @Date 2019年1月14日 - 下午6:14:07
* @Info 初始版本
* @Version 1.0
* @see org.apache.commons.lang.time.DateUtils
*/
public class DateUtil
{
protected static final Log log = LogFactory.getLog(DateUtil.class);
/**
* FastDateFormat cache map
*/
protected static Map dateFormatMap = new HashMap();
/**
* Date format pattern
*/
public final static String FORMAT_DATE_DEFAULT = "yyyy-MM-dd";
public final static String FORMAT_DATE_YYYYMMDD = "yyyyMMdd";
public final static String FORMAT_DATE_YYYY_MM_DD = "yyyy-MM-dd";
public final static String FORMAT_DATE_SLASH_YYYY_MM_DD = "yyyy/MM/dd";
public final static String FORMAT_DATE_SLASH_YYYY_M_DD = "yyyy/M/dd";
public final static String FORMAT_DATE_SLASH_YYYY_MM_D = "yyyy/MM/d";
public final static String FORMAT_DATE_SLASH_YYYY_M_D = "yyyy/M/d";
public final static String FORMAT_DATE_YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
/**
* DateTime format pattern
*/
public final static String FORMAT_DATETIME_DEFAULT = "yyyy-MM-dd HH:mm";
public final static String FORMAT_DATETIME_YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
public final static String FORMAT_DATETIME_YYYY_MM_DD_HH_MM_SS_SSS = "yyyy-MM-dd HH:mm:ss.SSS";
public final static String FORMAT_DATETIME_YYYY_MM_DD_HHMM = "yyyy-MM-dd HHmm";
public final static String FORMAT_DATETIME_YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
public final static String FORMAT_DATETIME_YYYY_MM_DD_HHMMSS = "yyyy-MM-dd HHmmss";
public final static String FORMAT_DATETIME_YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public final static String FORMAT_DATETIME_EXIF = "yyyy:MM:dd HH:mm:ss";
public final static String FORMAT_DATETIME_YYYY_MM_DDHH_MM = "yyyy-MM-ddHH:mm";
/**
* Time format pattern
*/
public final static String FORMAT_TIME_DEFAULT = "HH:mm";
public final static String FORMAT_TIME_HH_MM = "HH:mm";
public final static String FORMAT_TIME_HHMM = "HHmm";
public final static String FORMAT_TIME_HH_MM_SS = "HH:mm:ss";
public final static String FORMAT_TIME_HHMMSS = "HHmmss";
/**
* Returns FastDateFormat according to given pattern which is cached.
* This method is not public due to that the FastDateFormat instance may be
* altered by outside.
*
* @param formatPattern date format pattern string
* @return Cached FastDateFormat instance which should not be altered in any
* way.
*/
protected static Format getCachedDateFormat(String formatPattern)
{
// Due to simpledateformat's non thread-local, return a new pattern
// every time.
FastDateFormat dateFormat = dateFormatMap.get(formatPattern);
if (dateFormat == null)
{
dateFormat = FastDateFormat.getInstance(formatPattern);
dateFormatMap.put(formatPattern, dateFormat);
}
return dateFormat;
}
/**
* 日期转换成时间
*
* @param date
* @param formatStr
* @return
*/
public static String dateToStr(Date date, String formatStr)
{
String str = "";
SimpleDateFormat format = null;
if (formatStr == null || formatStr.equals(""))
{
format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
else
{
format = new SimpleDateFormat(formatStr);
}
try
{
str = format.format(date);
}
catch (Exception e)
{
// System.out.print(e.toString());
}
return str;
}
/**
* Format the date according to the pattern.
*
* @param date Date to format. If it's null, the result will be null.
* @param formatPattern Date format pattern string. You can find common ones
* in DateUtils class.
* @return Formatted date in String
* @see DateUtil
*/
public static String formatDate(Date date, String formatPattern)
{
if (date == null)
{
return null;
}
return getCachedDateFormat(formatPattern).format(date);
}
/**
* Format the date in default pattern: yyyy-MM-dd.
*
* @param date Date to format. If it's null, the result will be null.
* @return Formatted date in String
* @see DateUtil#FORMAT_DATE_DEFAULT
* @see DateUtil#formatDate(Date, String)
*/
public static String formatDate(Date date)
{
return formatDate(date, FORMAT_DATE_DEFAULT);
}
/**
* Format the date in default date-time pattern: yyyy-MM-dd HH:mm:ss
*
* @param date Date to format. If it's null, the result will be null.
* @return Formatted date-time in String
* @see DateUtil#FORMAT_DATETIME_DEFAULT
* @see DateUtil#formatDate(Date, String)
*/
public static String formatDateTime(Date date)
{
return formatDate(date, FORMAT_DATETIME_DEFAULT);
}
/**
* Format the date in default time pattern: HH:mm:ss
*
* @param date Date to format. If it's null, the result will be null.
* @return Formatted time in String
* @see DateUtil#FORMAT_TIME_DEFAULT
* @see DateUtil#formatDate(Date, String)
*/
public static String formatTime(Date date)
{
return formatDate(date, FORMAT_TIME_DEFAULT);
}
/**
* Same as javascript library's Wkz.date.formatTimeRange logic.
*
* @param start
* @param end
* @return
*/
public static String formatTimeRange(Date start, Date end)
{
if (start == null) return null;
if (end == null) return formatDateTime(start);
if (DateUtils.isSameDay(start, end))
{
// same day
return formatDateTime(start) + '~' + formatTime(end);
}
else
{
// not same day
return formatDateTime(start) + '~' + formatDateTime(end);
}
}
/**
* Returns current system date.
*
* @return current system date.
* @see Date#Date()
*/
public static Date getCurrentDate()
{
return new Date();
}
/**
* Parse string value to Date by given format pattern. If parse failed, null
* would be returned.
*
* @param stringValue date value as string.
* @param formatPattern format pattern.
* @return Date represents stringValue, null while parse exception occurred.
*/
public static Date parse(String stringValue, String formatPattern)
{
try
{
return new SimpleDateFormat(formatPattern).parse(stringValue);
}
catch (ParseException e)
{
log.warn("parse error:" + e.toString());
return null;
}
}
/**
* 转换时间对象 匹配格式 yyyy-MM-dd; yyyy-MM-dd HH:mm; yyyy-MM-dd HH:mm:ss; EEE MMM dd HH:mm:ss 'CST' yyyy;
* @param stringValue
* @return
*/
public static Date parse(String stringValue)
{
Date date = null;
SimpleDateFormat format = new SimpleDateFormat(DateUtil.FORMAT_DATE_DEFAULT);
try
{
date = format.parse(stringValue);
}
catch (ParseException e)
{
}
if (date == null)
{
try
{
format.applyPattern(FORMAT_DATETIME_YYYY_MM_DD_HH_MM_SS);
date = format.parse(stringValue);
}
catch (ParseException e)
{
}
}
if (date == null)
{
try
{
format.applyPattern(FORMAT_DATETIME_YYYY_MM_DD_HH_MM);
date = format.parse(stringValue);
}
catch (ParseException e)
{
}
}
if (date == null)
{
try
{
format = new SimpleDateFormat("EEE MMM dd HH:mm:ss 'CST' yyyy", Locale.US);
date = format.parse(stringValue);
}
catch (ParseException e)
{
}
}
return date;
}
/**
* Parse string value to Date by given format pattern. If string value is
* null or parse failed, null would be returned.
*/
public static Date parseAvoidNull(String stringValue, String formatPattern) throws ParseException
{
if (stringValue == null || stringValue.trim().equals(""))
{
return null;
}
int index = stringValue.indexOf("-");
if (index > 0)
{
String str = stringValue.substring(0, index);
if (str.length() == 2)
{
stringValue = "20" + stringValue;
}
}
return new SimpleDateFormat(formatPattern).parse(stringValue);
}
/**
* Parses given Date to a Timestamp instance with same date in milliseconds
* precision.
*
* @param date Date to parse.
* @return Timestamp in milliseconds precision
*/
public static Timestamp parseTimestamp(Date date)
{
return new Timestamp(date.getTime());
}
/**
* Compares two dates based on the specified calendar field, and ignores
* those fields more trivial. Neither date could be null.
* For example, if calendarField is Calendar.Month, then result will be 0 if
* 2008-8-2 and 2008-8-10 is compared. But the result will be -1 if
*
* @param date1
* @param date2
* @param calenderField
* @return date1 < date2 : <0
* date1 = date2 : 0
* date1 > date2 : >0
* @throws IllegalArgumentException If either date is null.
* @see Calendar
*/
public static int compareDateToField(final Date date1, final Date date2, int calendarField)
{
if (date1 == null || date2 == null)
{
throw new IllegalArgumentException("Neither date could be null");
}
Date truncatedDate1 = DateUtils.truncate(date1, calendarField);
Date truncatedDate2 = DateUtils.truncate(date2, calendarField);
return truncatedDate1.compareTo(truncatedDate2);
}
/**
* 获取两日期相差天数
*
* @param date1
* @param date2
* @return 日期为空 返回0 否则返回正负整数
*/
public static int compareDaysBetween(final Date date1, final Date date2)
{
if (date1 == null || date2 == null) return 0;
Calendar calendar = Calendar.getInstance();
calendar.setTime(date1);
calendar.set(Calendar.HOUR, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
long begin = calendar.getTimeInMillis();
calendar.setTime(date2);
calendar.set(Calendar.HOUR, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
long end = calendar.getTimeInMillis();
int days = (int) ((begin - end) / 1000 / 3600 / 24);
return days;
}
/**
* 系统常用java日期格式转化为extjs对应的日期格式
*
* @param java2FastDateFormat
* @return
*/
public static String extjsFastDateFormat(String java2FastDateFormat)
{
Map extFastDateFormatMap = new HashMap();
extFastDateFormatMap.put(DateUtil.FORMAT_DATE_DEFAULT, "Y-m-d");
extFastDateFormatMap.put(DateUtil.FORMAT_DATE_YYYYMMDD, "Ymd");
extFastDateFormatMap.put(DateUtil.FORMAT_DATE_SLASH_YYYY_M_D, "Y/m/d");
extFastDateFormatMap.put(DateUtil.FORMAT_DATETIME_DEFAULT, "Y-m-d H:i:s");
extFastDateFormatMap.put(DateUtil.FORMAT_DATETIME_YYYYMMDDHHMMSS, "YmdHis");
return extFastDateFormatMap.get(java2FastDateFormat);
}
public static Date append(Date start, Integer appendDays)
{
Calendar calendar = Calendar.getInstance();
calendar.setTime(start);
calendar.add(Calendar.DAY_OF_YEAR, appendDays);
return calendar.getTime();
}
public static void main(String[] args)
{
Date d = DateUtil.parse("2009-01-25", FORMAT_DATE_DEFAULT);
// System.out.println(Calendar.getInstance(Locale.GERMANY).getMinimalDaysInFirstWeek());
// System.out.println(Calendar.getInstance(Locale.GERMANY).getFirstDayOfWeek());
Calendar cal = Calendar.getInstance();
// System.out.println(cal.getMinimalDaysInFirstWeek());
cal.setTime(d);
// System.out.println(cal.get(Calendar.WEEK_OF_YEAR));
// System.out.println(cal.getTime());
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
// System.out.println(cal.getTime());
// test speed
Date start = new Date();
for (int i = 0; i < 500000; i++)
{
DateUtil.formatDate(start);
}
// System.out.println(new Date().getTime() - start.getTime());
// final SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy",
// Locale.ENGLISH);
final String testdata[] = { "1999-04-21", "2001-01-05", "2007-12-30" };
// test Thread-safe
Runnable r[] = new Runnable[testdata.length];
for (int i = 0; i < r.length; i++)
{
final int i2 = i;
r[i] = new Runnable()
{
public void run()
{
for (int j = 0; j < 1000; j++)
{
String str = testdata[i2];
String str2 = null;
/* synchronized(df) */
{
Date d = DateUtil.parse(str, DateUtil.FORMAT_DATE_YYYY_MM_DD);
str2 = DateUtil.formatDate(d, DateUtil.FORMAT_DATE_YYYY_MM_DD);
}
if (!str.equals(str2))
{
throw new RuntimeException("date conversion failed after " + j + " iterations. Expected " + str + " but got " + str2);
}
}
}
};
new Thread(r[i]).start();
}
}
}
二、REDIS 工具类
package com.honghu.cloud.common.component.redis.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
/**
* redis通用工具类
*
*/
@SuppressWarnings("rawtypes")
@Component
public class RedisUtil {
/**
* 日志对象
*/
protected Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private RedisTemplate redisTemplate;
/**
* 获取自增ID序号
* @param tableName
* @param growthLength
* @return
*/
public Long getSeq(String tableName, Long growthLength) {
Long seq = null;
try {
seq = redisTemplate.opsForValue().increment(tableName, growthLength);
//超过理论上限置0
if (seq > 999999999) {
redisTemplate.opsForValue().set(tableName, "0");
}
} catch (Exception e) {
logger.error("redis is incr val Error!tableName:" + tableName);
}
return seq;
}
}
三、抽象接口
package com.honghu.cloud.sequence;
/**
*
* @author YouCai.Xiong
* @Date 2019年1月14日 - 下午6:15:01
* @Info 初始版本
* @Version 1.0
*/
public interface SequenceService {
public String getUpdateQuerySeq(String prefix, String tableName);
}
package com.honghu.cloud.sequence.impl;
import java.util.Calendar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.honghu.cloud.common.component.redis.utils.RedisUtil;
import com.honghu.cloud.sequence.SequenceService;
import com.honghu.cloud.utils.DateUtil;
import com.honghu.cloud.utils.SequenceUtil;
/**
*
* @author YouCai.Xiong
* @Date 2019年1月14日 - 下午6:15:14
* @Info 初始版本
* @Version 1.0
*/
@Service
public class SequenceServiceImpl implements SequenceService {
@Autowired
private RedisUtil redisUtil;
/**
* 生产分布式主键ID
*/
@Override
public String getUpdateQuerySeq(String prefix, String tableName) {
String journalCode = null;
try {
Long seq = redisUtil.getSeq(tableName, 1l);
journalCode = prefix + DateUtil.formatDate(Calendar.getInstance().getTime(), DateUtil.FORMAT_DATE_YYYYMMDD) + SequenceUtil.addLeftZero(seq + "", 9);
} catch (Exception e) {
throw e;
}
return journalCode;
}
}
四、测试
package com.glz.cloud.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.honghu.cloud.sequence.SequenceService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
/**
*
* @author YouCai.Xiong
* @Date 2018年12月20日 - 下午1:28:33
* @Info 初始版本 测试模块
* @Version 1.0
*/
@Api(description = "测试模块")
@RestController
@RequestMapping("test")
public class SysTestController{
@Autowired
private SequenceService sequenceService;
@ApiOperation(value = "获取唯一主键")
@PostMapping("getUpdateQuerySeq")
public String getUpdateQuerySeq(
@ApiParam(name = "prefix", value = "前綴") @RequestParam(name = "prefix") String prefix,
@ApiParam(name = "tableName", value = "表名") @RequestParam(name = "tableName") String tableName) {
return sequenceService.getUpdateQuerySeq(prefix,tableName);
}
}
关于理论数值和补0的位数问题,建议大家可以根据自己业务量的实际情况来设置。主键生成策略也建议大家抽成一个基础模块,所有tableName维护成一个常量类也放在基础模块里面,这样也方便其他模块,简单pom引用一下就可以了。方便快捷、即插即用。