HIbernate中的主键生成策略可分为由JPA提供和Hibernate扩展。所有的主键都需要标注@ID和使用@GeneratedValue注解来指定它
我们使用@GeneratedValue的strategry字段声明主键生成策略,generator声明主键生成器的名称,对应于同名的主键生成器@SequenceGenerator或者@TableGenerator。

  • 本文使用的实体类
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Entity(name = "user")
@Table(name = "t_user")
public class Subscriber implements Serializable {

    private static final long serialVersionUID = 9181139977020744196L;

    @Id
    private int userID;

    private String username;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastUpdate;

    public Subscriber() {}

    public Subscriber(String username) {
        this.username = username;
        this.lastUpdate = new Date();
    }

    @Override
    public String toString() {
        return "userID: " + this.userID + ", username: " + this.username + ", lastUpdate: " + this.lastUpdate;
    }

    /*get and set*/
}
  • 测试用例
public class HibernateTest extends BaseTestCase {

    public void testInsert() throws Exception {
        Session session = this.sessionFactory.getCurrentSession();

        session.getTransaction().begin();
        Subscriber subscriber = new Subscriber("username");
        session.save(subscriber);
        session.getTransaction().commit();
        session.close();
    }


}

JPA中的主键生成策略

JPA中的主键生成策略一共分为4种

GenerationType.IDENTITY

有些数据库支持一个主键标识符,只要插入一行到表中,标识符列就将为其分配一个唯一标识符(比如MySQL中可以在创建表时声明“AUTO_INCREMENT”),但是只有在数据库支持时才能使用。
和其他策略之间的差异在于,只有当插入发生后,才可以访问标识符。当使用IDENTITY时,是插入操作导致标识符的生成,因此标识符在实体插入到数据库之前不可能可用。同时,因为实体插入往往延迟到提交时间,所以标识符只有在事物提交后才可用

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int userID;
2017-01-01 21:48:05 DEBUG create table t_user (userID integer not null auto_increment, lastUpdate datetime, username varchar(255), primary key (userID))
#插入数据时发出的SQL
2017-01-01 21:48:05 DEBUG insert into t_user (lastUpdate, username) values (?, ?)
GenerationType.SEQUENCE

许多数据库支持一个用于id生成的内部机制,称为序列(SEQUENCE)

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private int userID;
2017-01-01 21:49:19 DEBUG create table hibernate_sequence (next_val bigint)
2017-01-01 21:49:19 DEBUG create table t_user (userID integer not null, lastUpdate datetime, username varchar(255), primary key (userID))
2017-01-01 21:49:19 DEBUG select next_val as id_val from hibernate_sequence for update
2017-01-01 21:49:19 DEBUG update hibernate_sequence set next_val= ? where next_val=?
2017-01-01 21:49:19 DEBUG insert into t_user (lastUpdate, username, userID) values (?, ?, ?)
2017-01-01 21:49:19 TRACE binding parameter [1] as [TIMESTAMP] - [Sun Jan 01 21:49:19 CST 2017]
2017-01-01 21:49:19 TRACE binding parameter [2] as [VARCHAR] - [username]
2017-01-01 21:49:19 TRACE binding parameter [3] as [INTEGER] - [1]

使用该策略会维护一张保存主键的表,插入数据时从该表中取得主键。如果不指定@SequenceGenerator,则使用厂商提供的默认序列生成器,比如Hibernate默认提供的序列名称为hibernate_sequence。

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "t_user_sequence")
@SequenceGenerator(name = "t_user_sequence", sequenceName = "t_user_sequence_pk", initialValue = 10)
private int userID;
2017-01-01 21:55:01 DEBUG create table t_user (userID integer not null, lastUpdate datetime, username varchar(255), primary key (userID))
2017-01-01 21:55:01 DEBUG create table t_user_sequence_pk (next_val bigint)
2017-01-01 21:55:01 DEBUG insert into t_user_sequence_pk values ( 10 )
2017-01-01 21:57:12 DEBUG select next_val as id_val from t_user_sequence_pk for update
2017-01-01 21:57:12 DEBUG update t_user_sequence_pk set next_val= ? where next_val=?
2017-01-01 21:57:12 DEBUG insert into t_user (lastUpdate, username, userID) values (?, ?, ?)
2017-01-01 21:57:12 TRACE binding parameter [1] as [TIMESTAMP] - [Sun Jan 01 21:57:12 CST 2017]
2017-01-01 21:57:12 TRACE binding parameter [2] as [VARCHAR] - [username]
2017-01-01 21:57:12 TRACE binding parameter [3] as [INTEGER] - [10]

@GeneratedValue中的generator 由用户自定义,需要和@SequenceGenerator等注解的name一致即可。
SequenceGenerator中name 属性指定的是所使用的生成器;sequenceName 指定的是数据库中的序列;initialValue 指定的是序列的初始值;allocationSize 指定的是持久化引擎 从序列 (sequence) 中读取值时的缓存大小。

GenerationType.TABLE

这种策略中,持久化引擎 使用关系型数据库中的一个表 (Table) 来生成主键。

@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private int userID;
2017-01-01 22:03:51 DEBUG create table hibernate_sequences (sequence_name varchar(255) not null, next_val bigint, primary key (sequence_name))
2017-01-01 22:03:51 DEBUG create table t_user (userID integer not null, lastUpdate datetime, username varchar(255), primary key (userID))
2017-01-01 22:03:51 DEBUG select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update
2017-01-01 22:03:51 DEBUG insert into hibernate_sequences (sequence_name, next_val)  values (?,?)
2017-01-01 22:03:51 DEBUG update hibernate_sequences set next_val=?  where next_val=? and sequence_name=?
2017-01-01 22:03:51 DEBUG insert into t_user (lastUpdate, username, userID) values (?, ?, ?)
2017-01-01 22:03:51 TRACE binding parameter [1] as [TIMESTAMP] - [Sun Jan 01 22:03:51 CST 2017]
2017-01-01 22:03:51 TRACE binding parameter [2] as [VARCHAR] - [username]
2017-01-01 22:03:51 TRACE binding parameter [3] as [INTEGER] - [1]

可以使用@TableGenerator来指定序列表属性

@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "t_user_pk_seq")
@TableGenerator(name = "t_user_pk_seq", table = "t_user_table", pkColumnName = "sequence_name", valueColumnName = "sequence_count")
private int userID;
2017-01-01 22:10:07 DEBUG create table t_user (userID integer not null, lastUpdate datetime, username varchar(255), primary key (userID))
2017-01-01 22:10:07 DEBUG create table t_user_table (sequence_name varchar(255) not null, sequence_count bigint, primary key (sequence_name))
2017-01-01 22:10:07 DEBUG select tbl.sequence_count from t_user_table tbl where tbl.sequence_name=? for update
2017-01-01 22:10:07 DEBUG insert into t_user_table (sequence_name, sequence_count)  values (?,?)
2017-01-01 22:10:07 DEBUG update t_user_table set sequence_count=?  where sequence_count=? and sequence_name=?
2017-01-01 22:10:07 DEBUG select tbl.sequence_count from t_user_table tbl where tbl.sequence_name=? for update
2017-01-01 22:10:07 DEBUG update t_user_table set sequence_count=?  where sequence_count=? and sequence_name=?
2017-01-01 22:10:07 DEBUG insert into t_user (lastUpdate, username, userID) values (?, ?, ?)
2017-01-01 22:10:07 TRACE binding parameter [1] as [TIMESTAMP] - [Sun Jan 01 22:10:07 CST 2017]
2017-01-01 22:10:07 TRACE binding parameter [2] as [VARCHAR] - [username]
2017-01-01 22:10:07 TRACE binding parameter [3] as [INTEGER] - [1]
GenerationType.Auto

把主键生成策略交给JPA厂商(Persistence Provider),由它根据具体的数据库选择合适的策略,可以是Table/Sequence/Identity中的一种。

@Id
@GeneratedValue
private int userID;

Hibernate补充

使用Hibernate补充的主键都需要指定org.hibernate.annotations.GenericGenerator注解
Hibernate的主键生成策略全部在定义org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory中

public DefaultIdentifierGeneratorFactory() {
        register( "uuid2", UUIDGenerator.class );
        register( "guid", GUIDGenerator.class ); // can be done with UUIDGenerator + strategy
        register( "uuid", UUIDHexGenerator.class ); // "deprecated" for new use
        register( "uuid.hex", UUIDHexGenerator.class ); // uuid.hex is deprecated
        register( "assigned", Assigned.class );
        register( "identity", IdentityGenerator.class );
        register( "select", SelectGenerator.class );
        register( "sequence", SequenceStyleGenerator.class );
        register( "seqhilo", SequenceHiLoGenerator.class );
        register( "increment", IncrementGenerator.class );
        register( "foreign", ForeignGenerator.class );
        register( "sequence-identity", SequenceIdentityGenerator.class );
        register( "enhanced-sequence", SequenceStyleGenerator.class );
        register( "enhanced-table", TableGenerator.class );
    }
uuid2

实现类是UUIDGenerator,具体由实现UUIDGenerationStrategy接口的StandardRandomStrategy负责生成,StandardRandomStrategy最终由UUID.randomUUID();生成,生成主键是包含”-“长度为36的UUID

// org.hibernate.id.uuid.StandardRandomStrategy
@Override
public UUID generateUUID(SharedSessionContractImplementor session) {
    return UUID.randomUUID();
}
@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "uuid2")
private String userID;
2017-01-02 19:49:22 DEBUG create table t_user (userID varchar(255) not null, lastUpdate datetime, username varchar(255), primary key (userID))
2017-01-02 19:49:22 DEBUG insert into t_user (lastUpdate, username, userID) values (?, ?, ?)
2017-01-02 19:49:22 TRACE binding parameter [1] as [TIMESTAMP] - [Mon Jan 02 19:49:22 CST 2017]
2017-01-02 19:49:22 TRACE binding parameter [2] as [VARCHAR] - [username]
2017-01-02 19:49:22 TRACE binding parameter [3] as [VARCHAR] - [2c466b96-563c-406f-9ff1-83a7e617586a]
guid

实现类是GUIDGenerator,通过session.getFactory().getDialect().getSelectGUIDString();获得各个数据库中的标示字符串,mySql用”select uuid()”;

@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "guid")
private String userID;
2017-01-02 19:54:37 DEBUG create table t_user (userID varchar(255) not null, lastUpdate datetime, username varchar(255), primary key (userID))
2017-01-02 19:54:37 DEBUG select uuid()
2017-01-02 19:54:37 WARN  HHH000113: GUID identifier generated: 6abffcda-2232-1035-8f62-400a721c38c5
2017-01-02 19:54:37 DEBUG insert into t_user (lastUpdate, username, userID) values (?, ?, ?)
uuid & uuid.hex

实现类是UUIDHexGenerator,通过如下代码生成主键,通过IP,jvm的启动时间等特征获取主键,保证全网唯一

@Override
public Serializable generate(SharedSessionContractImplementor session, Object obj) {
    return format( getIP() ) + sep
            + format( getJVM() ) + sep
            + format( getHiTime() ) + sep
            + format( getLoTime() ) + sep
            + format( getCount() );
}
assigned

实现类为Assigned,主键由外部程序负责生成,Hibernate不负责维护主键生成。在保存前使用setter给主键赋值,至于这个值如何生成,完全由自己决定

@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "assigned")
private int userID;
2017-01-01 22:21:28 DEBUG create table t_user (userID integer not null, lastUpdate datetime, username varchar(255), primary key (userID))
2017-01-01 22:21:28 DEBUG insert into t_user (lastUpdate, username, userID) values (?, ?, ?)
2017-01-01 22:21:28 TRACE binding parameter [1] as [TIMESTAMP] - [Sun Jan 01 22:21:28 CST 2017]
2017-01-01 22:21:28 TRACE binding parameter [2] as [VARCHAR] - [username]
2017-01-01 22:21:28 TRACE binding parameter [3] as [INTEGER] - [123]
identity

实现类为IdentityGenerator,扩展了AbstractPostInsertGenerator,并实现PostInsertIdentifierGenerator。同JPA中的identity类似

@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "identity")
private int userID;
select

实现类为SelectGenerator,扩展了AbstractPostInsertGenerator实现了Configurable接口,而AbstractPostInsertGenerator实现了PostInsertIdentifierGenerator,由数据库触发器生成。

sequence

实现类为SequenceStyleGenerator,实现了PersistentIdentifierGenerator接口,和Configurable接口,PersistentIdentifierGenerator接口扩展IdentifierGenerator接口,通过数据库不同获取不同的取值语句(同JPA中的sequence)

@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "sequence")
private int userID;
seqhilo

实现类为seqhilo,逻辑较为复杂,通过高低位算符生成。

@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "seqhilo", parameters = {
        @Parameter(name = "sequence", value = "hibernate_seq"),
        @Parameter(name = "max_lo", value = "100")
})
private int userID;
increment

实现类为IncrementGenerator,由Hibernate从数据库中取出主键的最大值(每个session只取1次),以该值为基础,每次增量为1,在内存中生成主键,不依赖于底层的数据库。使用select max(idColumnName) from tableName语句获取主键最大值。该方法被声明成了synchronized,所以在一个独立的Java虚拟机内部是没有问题的,然而,在多个JVM同时并发访问数据库select max时就可能取出相同的值,再insert就会发生Dumplicate entry的错误。所以只能有一个Hibernate应用进程访问数据库,否则就可能产生主键冲突,所以不适合多进程并发更新数据库,适合单一进程访问数据库,不能用于群集环境。

@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "increment")
private int userID;
2017-01-01 22:23:57 DEBUG create table t_user (userID integer not null, lastUpdate datetime, username varchar(255), primary key (userID))
2017-01-01 22:23:57 DEBUG select max(userID) from t_user
2017-01-01 22:23:57 DEBUG insert into t_user (lastUpdate, username, userID) values (?, ?, ?)
foreign

实现类ForeignGenerator,通过给定的entityName和propertyName查询获得值,大多用在一对一关系中。

@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "foreign", parameters = { 
        @Parameter(name = "property", value = "user") 
})
@OneToOne
@PrimaryKeyJoinColumn
native

native由hibernate根据使用的数据库自行判断采用哪一种策略作为主键生成方式

@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "native")
自定义主键生成

只要实现IdentifierGenerator,并且在使用时指定为该类就可以使用

  • 主键生成类
import com.annwyn.persistence.util.LoggerUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;

import java.io.Serializable;
import java.util.UUID;

public class PrimaryGenerator implements IdentifierGenerator {

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
        String uuid = UUID.randomUUID().toString().replace("-", "");
        LoggerUtils.getInstance().info("PrimaryGenerator.class generator: " + uuid);
        return uuid;
    }
}
@Id
@GeneratedValue(generator = "user_id")
@GenericGenerator(name = "user_id", strategy = "com.annwyn.persistence.database.entity.PrimaryGenerator")
private String userID;
2017-01-02 20:26:01 DEBUG create table t_user (userID varchar(255) not null, lastUpdate datetime, username varchar(255), primary key (userID))
2017-01-02 20:26:01 INFO  PrimaryGenerator.class generator: 40a388e8f5f6468eaada167c1c77386c
2017-01-02 20:26:01 DEBUG insert into t_user (lastUpdate, username, userID) values (?, ?, ?)
最后的最后

最后的最后,到底使用何种策略,可以根据不同的系统,不同的需求选择不同的方式。