hibernate 有多种自定义的主键生成机制,比较常见的是使用UUID作为主键,或者使用数据库表来生成主键,抑或是使用数据库自带的主键生成机制来获取主键。这几种方法都有各自专门的详细文章详细介绍,这里就不多讲了。但是在使用的过程中都有或多或少的局限性。下面列举一些常见的问题。
- 如果采用数据库自带的主键生成机制,那么在做数据库迁移的时候,会带来额外的工作量。(如果不考虑数据库迁移的可能新,那么可以忽略)
- 如果采用数据库表的方式来生成主键,那么再每次新增前要先访问一次数据库,增加了开销。
- 如果采用UUID的方式获取主键,有这样一个问题,UUID是无序的,而大多数数据库查询或默认按照主键进行排序。所以在使用UUID作为主键的时候,容易出现查询列表的记录是乱序的。
由于以上原因,所以想这样一个思路。仍然采用数据库表的方式来保存主键,这样保证主键 的有序性,同时在内存中使用池来保存主键,不用每次访问数据库。比如一次性生成100个主键,将其放到内存中,这样每100次才访问一次数据库,保证了在大量新增时的效率。
下面是代码实现。
需要这样一张表
table_name 记录每张表的表名
entity_name 记录每张表对应的实体名称
last_number 记录每张表最后的增长号码
CREATE TABLE `sys_table_identity` (
`table_name` varchar(200) DEFAULT NULL,
`entity_name` varchar(200) NOT NULL DEFAULT '',
`last_number` bigint(20) DEFAULT NULL,
PRIMARY KEY (`entity_name`)
)
这是主键池。
package com.yangtao.framework.orm.id;
import com.yangtao.framework.service.ServiceLocator;
import com.yangtao.framework.util.StringHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @auther: XXX
* Date: 11-5-9
* Time: 下午11:35
* @version: 1.0
*/
public class IdPool {
Log log = LogFactory.getLog(getClass());
private IdDao idDao;
/**
* 实体名称
*/
private String entityName;
/**
* 表名称
*/
private String tableName;
/**
* 号码池的长度
*/
private int poolSize = 50;
/**
* 主键的数组
*/
private String[] numbers;
/**
* 当前指针位置
*/
private int pointer;
public IdPool() {
}
public IdPool(String entityName) {
this.entityName = entityName;
initalize();
}
public IdPool(String entityName, int poolSize) {
this.entityName = entityName;
this.poolSize = poolSize;
initalize();
}
public void initalize() {
//从数据库取得记录
if (idDao == null) {
this.idDao = ServiceLocator.getInstance().getService(IdDao.class);
}
long lastNumber = idDao.getLastNumber(this.entityName, this.poolSize);
//构造新的id序列
clear();
numbers = new String[poolSize];
for (int i = 0; i < poolSize; i++) {
numbers[i] = buildId(lastNumber + i);
}
}
/**
* 根据流水号生成主键
* @param number
* @return
*/
private String buildId(long number) {
return StringHelper.leftPad(number + "", 20, '0');
}
public synchronized String getNextId() {
if (pointer >= poolSize) {
clear();
initalize();
}
return getIdentity();
}
/**
* 返回下一个主键
*
* @return
*/
private String getIdentity() {
String nextNumber = numbers[pointer];
pointer++;
return nextNumber;
}
/**
* 清空号码池
*/
private void clear() {
numbers = null;
pointer = 0;
}
public String getEntityName() {
return entityName;
}
public void setEntityName(String entityName) {
this.entityName = entityName;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public int getPoolSize() {
return poolSize;
}
public void setPoolSize(int poolSize) {
this.poolSize = poolSize;
}
public String[] getNumbers() {
return numbers;
}
public void setNumbers(String[] numbers) {
this.numbers = numbers;
}
public int getPointer() {
return pointer;
}
public void setPointer(int pointer) {
this.pointer = pointer;
}
public IdDao getIdDao() {
return idDao;
}
public void setIdDao(IdDao idDao) {
this.idDao = idDao;
}
}
这个是数据操作类
package com.yangtao.framework.orm.id;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* @auther: XXX
* Date: 11-5-10
* Time: 上午9:44
* @version: 1.0
*/
public class IdDao {
private Log log = LogFactory.getLog(getClass());
private DataSource dataSource;
private String identityTable = "sys_table_identity";
/**
* 根据实体名称获取最新的滚动号,并且自动按照递增长度设置最后的滚动号
*
* @param entityName
* @param size
* @return
*/
public long getLastNumber(String entityName, int size) {
//从数据库取得记录
Connection connection = null;
long _latestNumber = 0;
Map<String, Object> params = new HashMap<String, Object>();
params.put("entityName", entityName);
try {
connection = dataSource.getConnection();
connection.setAutoCommit(false);
Long lastestNumber = getLastestNumber(connection, params);
if (lastestNumber != null) {
_latestNumber = lastestNumber;
}
//如果查询不到任何结果就插入一条新的记录
else {
insertNewRecord(connection, params);
}
params.put("newNumber", _latestNumber + size);
//更新最后的记录
updateRecord(connection, params);
//提交事务
connection.commit();
} catch (SQLException e) {
if (connection != null) {
try {
connection.rollback();
} catch (SQLException e1) {
log.error(e.getMessage(), e1);
}
}
log.error(e.getMessage(), e);
} finally {
clearConnection(connection);
}
return _latestNumber;
}
/**
* 获取当前表的最后主键序列
*
* @param connection
* @return
* @throws java.sql.SQLException
*/
private Long getLastestNumber(Connection connection, Map<String, Object> params) throws SQLException {
String selectSql = "select last_number from " + identityTable + " where entity_name = ?";
PreparedStatement preparedStatement = connection.prepareStatement(selectSql);
preparedStatement.setString(1, params.get("entityName").toString());
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.first()) {
return resultSet.getLong("last_number");
}
return null;
}
/**
* 插入一条表的主键记录
*
* @param connection
* @throws java.sql.SQLException
*/
private void insertNewRecord(Connection connection, Map<String, Object> params) throws SQLException {
String insertSql = "insert into "
+ identityTable + "(table_name, entity_name, last_number)" +
"values(?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(insertSql);
preparedStatement.setString(1, params.get("tableName") == null ? null : params.get("tableName").toString());
preparedStatement.setString(2, params.get("entityName").toString());
preparedStatement.setLong(3, 0);
preparedStatement.execute();
}
/**
* 更新一条记录
*
* @param connection
* @param params
* @throws java.sql.SQLException
*/
private void updateRecord(Connection connection, Map<String, Object> params) throws SQLException {
//更新最后的记录
String updateSql = "update " + identityTable + " set last_number = ? where entity_name=?";
PreparedStatement preparedStatement = connection.prepareStatement(updateSql);
preparedStatement.setLong(1, Long.valueOf(params.get("newNumber").toString()));
preparedStatement.setString(2, params.get("entityName").toString());
preparedStatement.execute();
}
/**
* 释放连接资源
*
* @param connection
*/
public void clearConnection(Connection connection) {
//关闭resultset
// if (resultSet != null) {
// try {
// resultSet.close();
// } catch (SQLException e) {
// resultSet = null;
// e.printStackTrace();
// }
// }
// //关闭preparedStatement
// if (preparedStatement != null) {
// try {
// preparedStatement.close();
// } catch (SQLException e) {
// preparedStatement = null;
// log.error(e.getMessage(), e);
// }
// }
//关闭连接
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
connection = null;
log.error(e.getMessage(), e);
}
}
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public String getIdentityTable() {
return identityTable;
}
public void setIdentityTable(String identityTable) {
this.identityTable = identityTable;
}
}
定义一个自定义的主键生成器
package com.yangtao.framework.hibernate;
import com.yangtao.framework.orm.id.IdPool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.type.Type;
import java.io.Serializable;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* @auther: XXX
* Date: 11-5-9
* Time: 下午1:50
* @version: 1.0
*/
public class CustomIdGenerator implements IdentifierGenerator, Configurable {
private Log log = LogFactory.getLog(getClass());
private static final Map<String, IdPool> ID_MAP = new ConcurrentHashMap<String, IdPool>();
private int poosize;
/**
* Generate a new identifier.
*
* @param session
* @param object the entity or toplevel collection for which the id is being generated
* @return a new identifier
* @throws org.hibernate.HibernateException
*
*/
@Override
public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
String entityName = object.getClass().getName();
if (log.isDebugEnabled()) {
log.debug("需要获取主键的实体名:[" + entityName + "]");
}
if (ID_MAP.get(entityName) == null) {
initalize(entityName);
}
String id = ID_MAP.get(entityName).getNextId();
if (log.isDebugEnabled()) {
log.debug("线程:[" + Thread.currentThread().getName() + "] " +
"实体:[" + entityName + "] 生成的主键为:[" + id + "]");
}
return id;
}
/**
* 初始化对象的主键
*
* @param entityName 对象名称
*/
private void initalize(String entityName) {
IdPool pool = new IdPool(entityName, poosize);
ID_MAP.put(entityName, pool);
}
/**
* Configure this instance, given the value of parameters
* specified by the user as <tt><param></tt> elements.
* This method is called just once, following instantiation.
*
* @param params param values, keyed by parameter name
*/
@Override
public void configure(Type type, Properties params, Dialect d) throws MappingException {
Object poosizeObj = params.get("poolsize");
if (poosizeObj != null) {
poosize = Integer.parseInt(poosizeObj.toString());
} else {
poosize = 50;
}
}
public int getPoosize() {
return poosize;
}
public void setPoosize(int poosize) {
this.poosize = poosize;
}
}
在sessionFactory中使用这个自定义的主键生成器
package com.yangtao.framework.hibernate;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
/**
* @auther: XXX
* Date: 11-5-9
* Time: 下午11:28
* @version: 1.0
*/
public class CustomSessionFactoryBean extends AnnotationSessionFactoryBean {
protected Configuration newConfiguration() throws HibernateException {
Configuration config = super.newConfiguration();
//增加自己定义的主键生成器
config.getIdentifierGeneratorFactory().register("pool", CustomIdGenerator.class);
return config;
}
}
spring的配置文件
<bean id="sessionFactory" class="com.yangtao.framework.hibernate.CustomSessionFactoryBean"> .... </bean>
在实体配置中使用
/*
* 文件名:BaseEntity.java
* 创建时间:2010-03-10
* 版本:1.0
* 版权所有:XXX
*/
package com.yangtao.framework.hibernate;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
/**
* 所有的实体类的父类,集成了一些公用属性
*
* @author XXX
*/
@SuppressWarnings("serial")
@MappedSuperclass
public abstract class BaseEntity implements Serializable {
/**
* 主键
*/
@Id
@GeneratedValue(generator = "custgenerator")
@GenericGenerator(strategy = "pool", name = "custgenerator")
@Column(length = 20)
private String id;
/**
* 数据版本号
*/
@Version
private Long version;
/**
* @return the id
*/
public String getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* @return the version
*/
public Long getVersion() {
return version;
}
/**
* @param version the version to set
*/
public void setVersion(Long version) {
this.version = version;
}
}
以下是它生成的主键
00000000000000000352
00000000000000000353
00000000000000000354
00000000000000000355
00000000000000000356
00000000000000000357
00000000000000000358
00000000000000000359
00000000000000000360
00000000000000000361
00000000000000000362
00000000000000000363
00000000000000000364
00000000000000000365
00000000000000000366
00000000000000000367
00000000000000000368
00000000000000000369
00000000000000000370
00000000000000000371
00000000000000000372
00000000000000000373
00000000000000000374
00000000000000000375
00000000000000000376
00000000000000000377
00000000000000000378
00000000000000000379
00000000000000000380
00000000000000000381
补充: 在产生 主键的方法buildId中,可以扩展,为每张表生成不同的前缀或者后缀,方法很简单,这里就累述了。另外可以自由调节号码池的默认长度,或者可以调节其他参数,这些参数都可以写在@GenericGenerator(strategy = "pool", name = "custgenerator") 的参数中。