问题 使用MySQL和Oracle生成Hibernate自动密钥


我正在努力 Java的 应用程序,应该做CRUD操作(使用 过冬 4.3.8)在具有相同数据库模式的两个不同数据库上。 有一个 MySQL的 (版本5.1.73)和 神谕 (11g Express Edition Release 11.2.0.2.0 - 64bit)数据库。

Java类用 JPA注释 是使用Hibernate代码生成从数据库表生成的。

问题是我们现在需要使用自动主键生成和MySQL使用 GenerationType.IDENTITY 和Oracle使用 GenerationType.SEQUENCE。此外,我们需要能够在极少数情况下自己手动设置主键。

注释类中的followig代码适用于两个数据库的自动密钥生成,但如果主键是自设置则失败。

@GeneratedValue(strategy=GenerationType.AUTO, generator="sequence_generator")
@SequenceGenerator(name="sequence_generator", sequenceName="SEQUENCE1")
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
    return this.id;
}

没有 @GeneratedValue 和 @SequenceGenerator 注释可以手动设置主键,但自动生成不起作用。


1190
2018-06-09 12:08


起源

试试这个[手动指定-a-primary-key-in-jpa-generatedvalue-column] [1]。那里有很多解决方案。 [1]: stackoverflow.com/questions/12002260/... - Volatile
此线程中以数据库为中心的解决方案对我不起作用,因为不应更改数据库架构。还有其他人依赖数据库模式不会改变。我不知道是否可能以及如何编写自己的Custom Id Generator,它支持MySQL和Oracle数据库(身份和序列)。在所有示例中,它们都使用IdentifierGenerator或Sequencegenerator。 - Simon Schürg
这个问题不明确。你是说你有一些类希望Hibernate自动生成标识符并手动为其他类分配标识符;或者你是说你希望Hibernate大多数时候自动为一个类的实例分配标识符,但在某些情况下你想手动为同一个类的某些实例分配标识符? - manish
您应该使用trigger来获得同构的跨数据库解决方案。 - falsarella
我的旧的Spring 4项目曾经在MySQL和Oracle 11g上运行良好 @SequenceGenerator(name = "id_generator", sequenceName = "sq_id_user")  注释,但现在我在Spring Boot 2和Hibernate 5上它抱怨10.1.24-MariaDB中存在一个不存在的序列。 - Stephane


答案:


即使你用过 GenerationType.AUTO 如果没有任何SEQUENCE特定参数,您将无法保存指定的标识符。

如果您愿意做出一些妥协,有一些解决方法:

  1. 一种方法是切换到指定的标识符。您可以使用 UUID 标识符,适用于MySQL和Oracle,您也可以手动分配值。

  2. 另一种方法是使用自定义表生成器。

    首先定义一个可识别的接口:

    public interface Identifiable<T extends Serializable> {
        T getId();
    }
    

    然后扩展表生成器:

    public class AssignedTableGenerator extends TableGenerator {
    
        @Override
        public Serializable generate(SessionImplementor session, Object obj) {
            if(obj instanceof Identifiable) {
                Identifiable identifiable = (Identifiable) obj;
                Serializable id = identifiable.getId();
                if(id != null) {
                    return id;
                }
            }
            return super.generate(session, obj);
        }
    }
    

    此生成器能够将分配的标识符与合成生成的标识符混合:

    doInTransaction(session -> {
        for (int i = 0; i < 5; i++) {
            session.persist(new AssignTableSequenceIdentifier());
        }
        AssignTableSequenceIdentifier tableSequenceIdentifier = new AssignTableSequenceIdentifier();
        tableSequenceIdentifier.id = -1L;
        session.merge(tableSequenceIdentifier);
        session.flush();
    });
    

    生成以下语句:

    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    insert into sequence_table (sequence_name, next_val)  values (default,1)
    update sequence_table set next_val=2  where next_val=1 and sequence_name=default
    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    update sequence_table set next_val=3  where next_val=2 and sequence_name=default
    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    update sequence_table set next_val=4  where next_val=3 and sequence_name=default
    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    update sequence_table set next_val=5  where next_val=4 and sequence_name=default
    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    update sequence_table set next_val=6  where next_val=5 and sequence_name=default
    select identityvs0_.id as id1_0_0_ from assigneTableIdentifier identityvs0_ where identityvs0_.id=-1
    insert into assigneTableIdentifier (id) values (1, 2)
    insert into assigneTableIdentifier (id) values (2, 4)
    insert into assigneTableIdentifier (id) values (5, -1)
    

对于Oracle,您可以组合SEQUENCE和指定的生成器,如中所述 本文

简而言之,考虑以下发电机:

public class AssignedSequenceStyleGenerator 
    extends SequenceStyleGenerator {

    @Override
    public Serializable generate(SessionImplementor session, 
        Object obj) {
        if(obj instanceof Identifiable) {
            Identifiable identifiable = (Identifiable) obj;
            Serializable id = identifiable.getId();
            if(id != null) {
                return id;
            }
        }
        return super.generate(session, obj);
    }
}

您可以将其映射到您的实体,如下所示:

@Id
@GenericGenerator(
    name = "assigned-sequence",
    strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.AssignedSequenceStyleGenerator",
    parameters = @org.hibernate.annotations.Parameter(
        name = "sequence_name", 
        value = "post_sequence"
    )
)
@GeneratedValue(
    generator = "assigned-sequence", 
    strategy = GenerationType.SEQUENCE
)
private Long id;

11
2018-06-25 07:47



这种方法适用于mysql db吗?似乎我得到的异常是由于:org.hibernate.MappingException:org.hibernate.dialect.MySQL5Dialect不支持序列 - simpleusr
您是否阅读了有关扩展TableGenerator的部分? - Vlad Mihalcea
嗨Vlad,你是说用TableGenerator而不是Sequence生成器?我已经有了一个实体类,并将它用于带有序列生成器的oracle db。但我的目的是也为mysql db使用相同的类。据我所知,在mysql数据库中没有相应的序列,所以我应该改变我的Id生成策略(即使用另一个表而不是序列)? - simpleusr
如果有问题, vladmihalcea.com上也有答案。 - Vlad Mihalcea
弗拉德。谢谢你的建议。我按照这个链接: vladmihalcea.com/...  并且可以使用与oracle db的序列相同的实体和mfsql db的自动增量。日Thnx - simpleusr


尝试这样的事情:

@Id
@Column( name = "ID" )
@TableGenerator( 
        name = "AppSeqStore", 
        table = "APP_SEQ_STORE", 
        pkColumnName = "APP_SEQ_NAME", 
        pkColumnValue = "LISTENER_PK", 
        valueColumnName = "APP_SEQ_VALUE", 
        initialValue = 1, 
        allocationSize = 1 )
@GeneratedValue( strategy = GenerationType.TABLE, generator = "AppSeqStore" )

而这个表在数据库中:

CREATE TABLE APP_SEQ_STORE (
    APP_SEQ_NAME VARCHAR(255) NOT NULL,
    APP_SEQ_VALUE NUMBER(10) NOT NULL,
    PRIMARY KEY(APP_SEQ_NAME)
)

INSERT INTO APP_SEQ_STORE VALUES ('LISTENER_PK', 0)

这一切都适用于Oracle,MS Sql Server和使用JBoss作为App Server的MySql。

更多信息: http://www.developerscrappad.com/408/java/java-ee/ejb3-jpa-3-ways-of-generating-primary-key-through-generatedvalue/ 


2
2018-06-24 13:48