`
walle1027
  • 浏览: 22815 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

自定义 hibernate 主键生成机制

    博客分类:
  • java
阅读更多

      hibernate 有多种自定义的主键生成机制,比较常见的是使用UUID作为主键,或者使用数据库表来生成主键,抑或是使用数据库自带的主键生成机制来获取主键。这几种方法都有各自专门的详细文章详细介绍,这里就不多讲了。但是在使用的过程中都有或多或少的局限性。下面列举一些常见的问题。

  1. 如果采用数据库自带的主键生成机制,那么在做数据库迁移的时候,会带来额外的工作量。(如果不考虑数据库迁移的可能新,那么可以忽略)
  2. 如果采用数据库表的方式来生成主键,那么再每次新增前要先访问一次数据库,增加了开销。
  3. 如果采用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>&lt;param&gt;</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") 的参数中。

 

2
3
分享到:
评论

相关推荐

    Hibernate各种主键生成策略与配置详解

    关于Hibernate的各种主键生成策略与配置详解

    hibernate主键生成策略

    - **应用场景**:主要用于早期版本的数据库主键生成机制。 - **特点**: - 实现灵活,可以通过自定义 SQL 控制主键的生成。 - 使用较少,随着技术的发展,其他策略更为常见。 #### 三、总结 通过上述介绍,我们...

    总结hibernate常用主键生成策略。

    1主键常用的生成策略 2对数据库的依赖性总结 3关于主键生成策略的选择 详细解释

    Hibernate主键生成策略

    ### Hibernate主键生成策略 #### 一、概述 在Hibernate框架中,主键生成策略是对象持久化过程中不可或缺的一部分。合理的主键生成机制不仅能够确保数据的唯一性,还能够提高系统的性能和可扩展性。本文将详细介绍...

    hibernate中自动生成主键的办法

    在IT领域的数据库设计与开发中,主键的生成机制是一个至关重要的环节,它不仅关系到数据的唯一性,还直接影响到数据库的性能和安全性。Hibernate作为一款流行的Java持久层框架,提供了多种自动生成主键的方法,以...

    持久化类主键生成策略+例子

    ### Hibernate主键生成策略 1. **`native`**:类似于JPA的`GenerationType.AUTO`,根据底层数据库选择合适的生成策略。 2. **`identity`**:相当于JPA的`GenerationType.IDENTITY`,适合自动增长主键的数据库。 3...

    JPA环境搭建及JPA实例与JPA主键生成策略

    这里的`@Id`注解标记`id`字段为实体的主键,而`@GeneratedValue`注解表示主键生成策略。`GenerationType.IDENTITY`意味着主键值由数据库自动生成,比如在MySQL中,这通常对应于`AUTO_INCREMENT`。 **JPA主键生成...

    真实项目中关于主键生成方式的剖析(JPA)

    而Hibernate提供了跨数据库的主键生成策略,例如`native`策略,它会根据底层数据库自动选择合适的主键生成方式(如MySQL的自动增长ID或Oracle的序列)。 在JPA中,通过`@GenericGenerator`注解可以定义主键的生成...

    Hibernate主键类型说明和配置手册.doc

    以下是对Hibernate主键类型及其配置的详细说明: 1. **uuid.hex**: 这种生成器使用128位算法生成一个32位的字符串。由于其通用性,它适用于所有类型的数据库。在`.hbm.xml`映射文件中,你可以这样配置: ```xml ...

    JPA学习笔记-EJB-03JPA主键生成策略总结

    持久化对象的主键生成机制在JPA(Java Persistence API)中占据着重要的位置。它不仅关乎数据的唯一标识符如何确定,还与数据的存储效率及应用逻辑紧密相关。JPA通过`@GeneratedValue`注解来指定主键生成策略,提供...

    hibernate 主键一对一映射(单向) 实例(java类)

    - 为了确保一对一关系的唯一性,通常其中一个实体的主键会作为另一个实体的外键,且两者的主键生成策略应保持一致。 - 考虑到性能,避免在查询时加载不必要的关联对象,可以使用懒加载(`fetch = FetchType.LAZY`)...

    hibernate自动生成

    自动生成的Entity类通常包含属性(对应表的字段)和getter/setter方法,有时还会包含一些特定的Hibernate注解,如@Id(主键)和@GeneratedValue(主键生成策略)。 2. **映射文件(Mapping Files)**:在传统的...

    hibernate实体生成工具

    - **可定制化**:多数实体生成工具允许用户自定义生成模板,比如字段命名规则、注释等。 - **兼容性**:工具通常支持多种数据库,如MySQL、Oracle、PostgreSQL等。 - **版本控制**:随着数据库表结构的修改,工具也...

    Hibernate 映射文件自动生成

    【压缩包子文件的文件名称列表】:“hibernate映射文件自动生成.doc”很可能是这篇博文的文档形式,其中详细介绍了具体的操作步骤、使用工具或者自定义脚本,以帮助开发者理解并应用自动化的映射文件生成过程。...

    HIbernate Jpa 生成entities

    标题“Hibernate JPA 生成entities”涉及到的是Java开发中的一个重要话题,主要关于如何利用Hibernate的Java Persistence API(JPA)来自动创建数据访问对象(DAOs),也称为实体(Entities)。这个过程通常被称为...

    Hibernate-tools解决hbm.xml中文注释乱码和生成实体类注释

    - 对于复杂的业务场景,可能需要编写自定义的代码生成逻辑,这时可以利用`Hibernate-tools`提供的API进行扩展。 总结,`Hibernate-tools`是Hibernate生态中不可或缺的一部分,它极大地提高了开发效率,同时也为...

    Hibernate逆向生成entity和hbm.xml文件

    3. 自定义属性:对于一些特殊需求,如日期格式化、自定义转换等,可能需要在生成的实体类或映射文件中添加额外的注解或配置。 总的来说,Hibernate的逆向工程功能是Java开发中的利器,它可以节省大量时间并降低出错...

    Swing编写的Hibernate生成器

    3. **注解**:如`@Entity`表示这是一个Hibernate实体,`@Table`指定对应数据库中的表名,`@Id`标识主键,`@GeneratedValue`处理主键生成策略。 除了实体类,生成器还会自动生成对应的Hibernate映射文件(.hbm.xml)...

Global site tag (gtag.js) - Google Analytics