mysql日期加流水号为主键_日期流水号主键生成器

该博客介绍了一个实现MySQL中日期加流水号作为主键的生成器类`DateSeqGenerator`,该类继承自`IdentifierGenerator`并实现了`Configurable`接口。它通过配置参数获取表名、列名、长度等信息,并根据当前日期和最大流水号生成唯一的主键。类内部包含了从数据库获取当前日期、最大标识和生成新标识的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

public class DateSeqGenerator implements IdentifierGenerator,

Configurable {

// logger

private static final Log log = LogFactory.getLog(DateSeqGenerator.class);

// reference to the underlying generator to which we delegate the work

private String currentDate = "";

//上次生成标识的日期前缀

private long day;

//下一个Sequence值

private long next;

//末位序号的位数限制

private int length = 4;

//从数据库获取当前最大标识的Sql语句

private String sql;

//标识返回值的JAVA类别

private Class returnClass;

/**

* Construct a new DateSeqGenerator

*/

public DateSeqGenerator() {

super();

}

/* (non-Javadoc)

* @see org.hibernate.id.IdentifierGenerator#generate(org.hibernate.engine.SessionImplementor, java.lang.Object)

*/

public Serializable generate(SessionImplementor session, Object object)

throws SQLException, HibernateException

{

if (sql!=null) {

getNext( session );

}

long currentDay = getCurrentDay();

if(currentDay != day){

day = currentDay;

next = 1l;

}

return IdentifierGeneratorFactory.createNumber(day * Math.pow(10l,length) + (next++), returnClass);

}

/* (non-Javadoc)

* @see org.hibernate.id.Configurable#configure(org.hibernate.type.Type, java.util.Properties, org.hibernate.dialect.Dialect)

*/

public void configure(Type type, Properties params, Dialect dialect)

throws MappingException

{

String tableList = params.getProperty("tables");

if (tableList==null) tableList = params.getProperty(PersistentIdentifierGenerator.TABLES);

String[] tables = StringHelper.split(", ", tableList);

String column = params.getProperty("column");

if (column==null) column = params.getProperty(PersistentIdentifierGenerator.PK);

String numLength = params.getProperty("length");

if(numLength == null)

length = 4;

else

length = Integer.parseInt(numLength);

String schema = params.getProperty(PersistentIdentifierGenerator.SCHEMA);

String catalog = params.getProperty(PersistentIdentifierGenerator.CATALOG);

returnClass = type.getReturnedClass();

StringBuffer buf = new StringBuffer();

for ( int i=0; i

if (tables.length>1) {

buf.append("select ").append(column).append(" from ");

}

buf.append( Table.qualify(catalog, schema, tables[i], d.getSchemaSeparator() ) );

if ( i

}

if (tables.length>1) {

buf.insert(0, "( ").append(" ) ids_");

column = "ids_." + column;

}

sql = "select max(" + column + ") from " + buf.toString();

}

/**

* 从数据库检索已有数据标识的最大值

* @param session

*

* @return

*/

//从数据库获取已有数据的最大ID标识,确定下一标识的值

private void getNext( SessionImplementor session ) {

Connection conn = session.connection();

log.debug("fetching initial value: " + sql);

try {

PersistentIdentifierGenerator.SQL.debug(sql);

PreparedStatement st = conn.prepareStatement(sql);

ResultSet rs = null;

try {

rs = st.executeQuery();

if ( rs.next() ) {

next = rs.getLong(1);

if ( rs.wasNull())

next = 1l;

else{

day = Long.parseLong(next.substring(0,8)) + 1; }

next = Long.parseLong((next + "").substring(8));

}

else {

next = 1l;

}

sql=null;

log.debug("first free id: " + next);

}

finally {

if (rs!=null) rs.close();

st.close();

}

}

catch (SQLException sqle) {

throw JDBCExceptionHelper.convert(

session.getFactory().getSQLExceptionConverter(),

sqle,

"could not fetch initial value",

sql

);

}

}

/**

* 从数据库服务器获取当前日期

*

* @return long 数据库当前日期,日期格式为'yyyymmdd'

*/

private long getCurrentDay(){

String cDate = null;

/**此部分代码省略**/

/** 从数据库获取当前日期,返回格式为'yyyymmdd',“20060316”**/

return cDate;

}

}

分享到:

18e900b8666ce6f233d25ec02f95ee59.png

72dd548719f0ace4d5f9bca64e1d7715.png

2011-09-15 11:18

浏览 2229

评论

<think>好的,用户现在想了解如何根据数据库生成编号。首先,我需要明确用户的需求是什么。可能他们是在设计一个系统,需要为数据库中的记录生成唯一的标识符,比如订单号、用户ID或者产品编号之类的。常见的场景可能有电商平台的订单编号,会员系统的会员号,或者库存管理的产品编号等等。 接下来,我得考虑不同的生成策略。数据库自增ID是最简单的,但用户可能想要更复杂的编号规则,比如包含日期、前缀、后缀或者其他业务相关的信息。比如订单号可能是“ORD20231015-0001”,其中包含日期和自增部分。这时候就需要结合数据库的自增字段和应用程序的逻辑来生成。 然后,用户可能关心并发情况下的问题。如果多个客户端同时生成编号,如何保证唯一性?这时候可能需要数据库的事务机制,或者使用分布式ID生成方案,比如雪花算法(Snowflake),这样可以避免单点故障并提高性能。 另外,考虑到扩展性,如果系统是分布式的,传统的自增ID可能不够用,需要使用UUID或者基于Redis的自增ID。UUID虽然唯一,但长度较长,可读性差。而Redis的自增可以生成更紧凑的数值,适合需要短ID的场景。 还有可能用户需要编号有一定的业务含义,比如部门编号日期序号,这时候需要在应用层拼接字符串,同时确保序号部分在数据库中是原子操作,避免重复。例如,使用数据库的序列(Sequence)或者行级锁来保证同一时间段内的序号递增。 可能存在的误区是用户认为直接在应用层生成编号足够,但在高并发下可能导致重复。这时候需要强调数据库的事务和锁机制的重要性,或者采用分布式ID生成器。 最后,用户可能需要具体的实现步骤或代码示例,比如在MySQL中使用自增字段,或者在Java中结合日期和序列生成编号。需要根据不同的数据库和编程语言提供相应的例子,并提醒注意事项,比如并发处理、唯一性校验等。 总结下来,用户的需求可能包括生成唯一、有序、有业务含义的编号,并且在不同场景下(单机或分布式)有不同的解决方案。需要全面覆盖这些方面,给出具体的策略和实现方法,同时指出各种方法的优缺点,帮助用户根据实际需求选择最合适的方案。</think>--- ### **根据数据库生成编号的 6 大核心方法** #### **1. 自增主键法(最简单基础)** ```sql -- 建表时定义自增列 (MySQL) CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, product_no VARCHAR(20) NOT NULL ); -- 插入数据时自动生成编号 (Java示例) String sql = "INSERT INTO products (product_no) VALUES (CONCAT('PROD-', LPAD(LAST_INSERT_ID(), 5, '0')))"; ``` **特点**: - 数据库自动维护递增序列 - 需配合字符串拼接实现业务编号格式 - 适合简单流水号场景(如:PROD-00001) --- #### **2. 日期+序列组合法(含业务标识)** ```sql -- Oracle 实现动态编号生成 CREATE SEQUENCE order_seq START WITH 1 INCREMENT BY 1; INSERT INTO orders (order_no) VALUES ('ORD' || TO_CHAR(SYSDATE, 'YYYYMMDD') || LPAD(order_seq.NEXTVAL, 5, '0')); ``` **生成示例**: `ORD20231015-00001` **关键点**: - 使用数据库序列保证唯一性 - 日期部分实现按日/月归零 - 需处理跨日期的序列重置问题 --- #### **3. 分布式唯一ID法(应对高并发)** ```java // 雪花算法生成分布式ID (Java) public class SnowflakeIdGenerator { private final long datacenterId; // 数据中心ID private final long machineId; // 机器ID private long sequence = 0L; private long lastTimestamp = -1L; public synchronized long nextId() { long timestamp = System.currentTimeMillis(); if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨异常"); } if (timestamp == lastTimestamp) { sequence = (sequence + 1) & 0xFFF; if (sequence == 0) timestamp = tilNextMillis(lastTimestamp); } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - 1288834974657L) << 22) | (datacenterId << 17) | (machineId << 12) | sequence; } } ``` **ID结构示例**: `1626332400123456789` **优势**: - 无需数据库交互,性能极高 - 支持分布式部署 - 时间有序可排序 --- #### **4. 预分配区间法(超大规模系统优化)** ```sql -- 创建号段分配表 CREATE TABLE id_segments ( biz_type VARCHAR(50) PRIMARY KEY, current_max_id BIGINT NOT NULL, step INT NOT NULL DEFAULT 1000 ); -- 获取号段(事务中执行) BEGIN; SELECT current_max_id FROM id_segments WHERE biz_type = 'order' FOR UPDATE; UPDATE id_segments SET current_max_id = current_max_id + step WHERE biz_type = 'order'; COMMIT; ``` **应用层使用**: - 每次获取一个号段(如1000个ID) - 内存中分配完再重新获取 - 降低数据库压力 --- #### **5. Redis原子操作法(高性能场景)** ```java // 使用Redis生成每日流水号 public String generateDailyNo(String bizKey) { String dateStr = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); String redisKey = bizKey + ":" + dateStr; Long seq = redisTemplate.opsForValue().increment(redisKey); if (seq == 1) { // 首次生成设置过期时间 redisTemplate.expire(redisKey, 32, TimeUnit.HOURS); } return String.format("%s-%06d", dateStr, seq); } ``` **生成示例**: `20231015-000123` **优势**: - 天然支持分布式计数 - 自动过期清理 - 性能可达10万+/秒 --- #### **6. 复合规则生成法(复杂业务需求)** **示例:生成客户编号** `CUST[区域码][行业码][4位年份][5位序列]` → `CUST01A202300001` **实现步骤**: 1. 建立编码规则配置表 2. 通过数据库事务获取各部分参数 3. 组合生成最终编号 4. 使用唯一索引保证不重复 --- ### **选型决策树** ``` 是否需要分布式支持? ├─ 是 → 选择雪花算法/Redis/号段法 └─ 否 → ├ 需要业务含义 → 复合规则法 └ 纯数字 → 自增主键 ``` --- ### **关键注意事项** 1. **并发控制**: - 使用数据库事务 + SELECT FOR UPDATE - Redis原子操作 - 分布式锁(ZooKeeper/Redisson) 2. **容灾设计**: - 定期备份序列当前值 - 设计容错区间(如预留100个ID防宕机丢失) 3. **历史兼容**: - 避免改变已有编号规则 - 新规则通过版本号区分 4. **性能优化**: - 批量获取号段减少IO - 使用内存队列缓冲预生成ID --- ### **典型错误案例与解决方案** **问题**: 某电商系统在秒杀时出现订单号重复 **分析**: 使用数据库自增ID直接拼接字符串,未事务锁导致并发冲突 **解决方案**: ```sql START TRANSACTION; SELECT @next_id := MAX(order_no) + 1 FROM orders FOR UPDATE; INSERT INTO orders (order_no) VALUES (@next_id); COMMIT; ``` **改进效果**: - 事务保证原子性 - 行级锁防止并发覆盖 --- 根据具体业务场景(数据规模、并发量、编号复杂度)选择最适合的方案,建议优先考虑数据库原生方案,分布式场景推荐号段分配法或雪花算法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值