Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate sequence generator confused by sharing it for multiple entities

My classes use an ID like

@Id @Generated(GenerationTime.INSERT) @GeneratedValue private Integer id;

This works perfectly for H2 (supporting sequences) and gets interpreted for MySql by creating a helper table hibernate_sequence. Using this answer, everything looks the way I want, especially using a single sequence for all tables.

One thing seems to be wrong: There are multiple rows in the helper table. My id is declared in a @MappedSuperclass and during initialization, for each concrete class this line gets executed:

insert into hibernate_sequence values ( 1 )

This is obviously wrong: there is a line per table there and each contains the same value (initially one; when changed, they all change in the same way, as the SQL is update hibernate_sequence set next_val=? where next_val=?, so it effects all the rows in the same way).

It's harmless, but I wonder: Is it a bug or am I doing something wrong?

like image 347
maaartinus Avatar asked Mar 13 '23 17:03

maaartinus


2 Answers

If you want to make it works you need to use other strategy for now:

@GenericGenerator(
        name = "table_generator",
        strategy = "org.hibernate.id.enhanced.TableGenerator"
)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "table_generator")

What about org.hibernate.id.enhanced.SequenceStyleGenerator:

I believe there is an issue in how hibernate initializes shared single row sequence table.

For hibernare 5.0.6.Final Source of the issue lies in org.hibernate.boot.internal.InFlightMetadataCollectorImpl class in

private void processExportableProducers(MetadataBuildingContext buildingContext) {
    // for now we only handle id generators as ExportableProducers

    final Dialect dialect = getDatabase().getJdbcEnvironment().getDialect();
    final String defaultCatalog = extractName( getDatabase().getDefaultNamespace().getName().getCatalog(), dialect );
    final String defaultSchema = extractName( getDatabase().getDefaultNamespace().getName().getSchema(), dialect );

    for ( PersistentClass entityBinding : entityBindingMap.values() ) {
        if ( entityBinding.isInherited() ) {
            continue;
        }

        // ***************************************************************************
        // For Instance, it does not filter out the same entityBinding.getIdentifier()
        // and make initialization multiple time
        // ***************************************************************************
        handleIdentifierValueBinding(
                entityBinding.getIdentifier(),
                dialect,
                defaultCatalog,
                defaultSchema,
                (RootClass) entityBinding
        );
    }

    for ( Collection collection : collectionBindingMap.values() ) {
        if ( !IdentifierCollection.class.isInstance( collection ) ) {
            continue;
        }

        handleIdentifierValueBinding(
                ( (IdentifierCollection) collection ).getIdentifier(),
                dialect,
                defaultCatalog,
                defaultSchema,
                null
        );
    }
}
like image 121
valery.barysok Avatar answered Apr 26 '23 20:04

valery.barysok


Same Sequence Generator

Presume there is a Base class annotated with @MappedSuperclass. There are also A and B classes extending Base class; If you annotate Id field in Base class with @SequenceGenerator, all subclasses of Base share the same sequence generator and will increament/use the same sequence in the database for their ids. This is ofcourse harmless but results in ugly numbers for ids :

@MappedSuperclass
public class Person {    
  @Id
  @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="id_gen")
  @SequenceGenerator(name="id_gen", sequenceName="a_seq", allocationSize=1)
  private Long id;
}

@Entity
public class A extends Base {
}

@Entity
public class B extends Base {
}

This is what their ids look like after adding A, then B, then A :

A{id=1, name='...'}
B{id=2, name='...'}
A{id=3, name='...'}

Distinct Sequence Generator

A nicer way would be having a fresh sequence for each table which can be implemented through assigning different sequence generators for different subclasses :

@MappedSuperclass
public class Person {    
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="id_gen")
    private Long id;
}

@Entity
@SequenceGenerator(name="id_gen", sequenceName="a_seq", allocationSize=1)
public class A extends Base {
}

@Entity
@SequenceGenerator(name="id_gen", sequenceName="b_seq", allocationSize=1)
public class B extends Base {
}

Their ids will look like this after adding A, then B, then A :

A{id=1, name='...'}
B{id=1, name='...'}
A{id=2, name='...'}
like image 35
NiMa Thr Avatar answered Apr 26 '23 19:04

NiMa Thr