Hibernate, @SequenceGenerator e alocaçãoSize

117

Todos nós conhecemos o comportamento padrão do Hibernate ao usar @SequenceGenerator- aumenta a sequência real do banco de dados em um , multiplica este valor por 50 ( allocationSizevalor padrão ) - e então usa este valor como ID de entidade.

Este é um comportamento incorreto e conflita com a especificação que diz:

alocaçãoSize - (opcional) a quantidade a ser incrementada ao alocar números de sequência da sequência.

Para ser claro: não me preocupo com as lacunas entre os IDs gerados.

Preocupo-me com IDs que não são consistentes com a sequência de banco de dados subjacente. Por exemplo: qualquer outro aplicativo (que usa JDBC simples) pode querer inserir novas linhas sob os IDs obtidos na sequência - mas todos esses valores já podem ser usados ​​pelo Hibernate! Loucura.

Alguém conhece alguma solução para este problema (sem configurar allocationSize=1e assim degradar o desempenho)?

EDIT:
Para tornar as coisas claras. Se o último registro inserido tinha ID = 1, então o HB usa valores 51, 52, 53...para suas novas entidades MAS ao mesmo tempo: o valor da sequência no banco de dados será definido como 2. O que pode facilmente levar a erros quando outros aplicativos estiverem usando essa sequência.

Por outro lado: a especificação diz (no meu entendimento) que a sequência do banco de dados deveria ter sido definida para 51e, entretanto, o HB deve usar valores do intervalo 2, 3 ... 50


ATUALIZAÇÃO:
Como Steve Ebersole mencionou abaixo: o comportamento descrito por mim (e também o mais intuitivo para muitos) pode ser habilitado por configuração hibernate.id.new_generator_mappings=true.

Obrigado a todos vocês.

ATUALIZAÇÃO 2:
Para futuros leitores, abaixo você pode encontrar um exemplo prático.

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USERS_SEQ")
    @SequenceGenerator(name = "USERS_SEQ", sequenceName = "SEQUENCE_USERS")
    private Long id;
}

persistence.xml

<persistence-unit name="testPU">
  <properties>
    <property name="hibernate.id.new_generator_mappings" value="true" />
  </properties>
</persistence-unit>
G. Demecki
fonte
2
"sem definir alocaçãoSize = 1 e, portanto, degradar o desempenho" por que prejudica o desempenho se você defini-lo como 1?
sheidaei
3
@sheidaei veja o comentário abaixo :-) Isso ocorre porque todo mundo saveprecisa consultar o banco de dados para o próximo valor da sequência.
G. Demecki
Obrigado estava enfrentando o mesmo problema. No início, eu estava adicionando alocaçãoSize = 1 em cada @SequenceGenerator. Usando hibernate.id.new_generator_mappings = true evite isso. Embora JPA ainda consulte o banco de dados para obter o id de cada inserção ...
TheBakker
1
Com o SequenceGeneratorHibernate irá consultar o banco de dados somente quando a quantidade de IDs especificados por allocationsizeacabar. Se você configurou allocationSize = 1, esse é o motivo pelo qual o Hibernate consulta o banco de dados para cada inserção. Altere esse valor e pronto.
G. Demecki
1
Obrigado! a hibernate.id.new_generator_mappingsconfiguração é muito importante. Eu espero que seja a configuração padrão que eu não tenha que gastar muito tempo pesquisando por que o número de id fica selvagem.
LeOn - Han Li

Respostas:

43

Para ser absolutamente claro ... o que você descreve não está em conflito com as especificações de forma alguma. A especificação fala sobre os valores que o Hibernate atribui às suas entidades, não os valores realmente armazenados na seqüência do banco de dados.

No entanto, existe a opção de obter o comportamento que procura. Veja primeiro minha resposta em Existe uma maneira de escolher dinamicamente uma estratégia @GeneratedValue usando anotações JPA e Hibernate? Isso lhe dará o básico. Contanto que você esteja configurado para usar aquele SequenceStyleGenerator, o Hibernate interpretará allocationSizeusando o "otimizador agrupado" no SequenceStyleGenerator. O "otimizador em pool" deve ser usado com bancos de dados que permitem uma opção de "incremento" na criação de sequências (nem todos os bancos de dados que suportam sequências suportam um incremento). De qualquer forma, leia sobre as várias estratégias de otimizador lá.

Steve Ebersole
fonte
Obrigado Steve! A melhor resposta. Além disso, sua outra postagem foi útil.
G. Demecki
4
Também percebi que você é coautor de org.hibernate.id.enhanced.SequenceStyleGenerator. Você me surpreendeu.
G. Demecki,
22
Surpreendeu você como? Sou o desenvolvedor líder do Hibernate. Eu escrevi / co-escrevi muitas classes do Hibernate;)
Steve Ebersole,
Apenas para registro. O incremento da sequência de banco de dados deve ser evitado para evitar grandes lacunas. A sequência do banco de dados é multiplicada por alocaçãoSize quando o cache de ID se esgota. Mais detalhes stackoverflow.com/questions/5346147/…
Olcay Tarazan
1
Uma maneira de alterar o "otimizador" usado globalmente é adicionar algo assim às opções de hibernação: serviceBuilder.applySetting ("hibernate.id.optimizer.pooled.preferred", LegacyHiLoAlgorithmOptimizer.class.getName ()); Em vez de LegacyHiLoAlgorithOptimizer, você pode escolher qualquer classe de otimizador e ela se tornará o padrão. Isso deve tornar mais fácil manter o comportamento desejado como padrão, sem alterar todas as anotações. Além disso, tome cuidado com os otimizadores "pool" e "hilo": eles fornecem resultados estranhos quando o valor de sua sequência começa em 0, causando IDs negativos.
fjalvingh
17

allocationSize=1É uma microotimização antes de obter a consulta. O Hibernate tenta atribuir um valor na faixa de allocationSize e, portanto, tenta evitar a consulta ao banco de dados por sequência. Mas esta consulta será executada todas as vezes se você defini-la como 1. Isso dificilmente fará qualquer diferença, pois se seu banco de dados for acessado por algum outro aplicativo, ele criará problemas se o mesmo id for usado por outro aplicativo nesse meio tempo.

A próxima geração de Sequence Id é baseada em alocação de tamanho.

Por padrão, é mantido como o 50que é demais. Também só ajudará se você tiver quase 50registros em uma sessão que não sejam persistentes e que serão persistidos usando esta sessão e transação em particular.

Portanto, você deve sempre usar allocationSize=1enquanto usa SequenceGenerator. Como para a maioria dos bancos de dados subjacentes, a sequência é sempre incrementada por 1.

Amit Deshpande
fonte
12
Nada a ver com desempenho? Tem certeza mesmo ? Fui ensinado que com o allocationSize=1Hibernate em cada saveoperação precisa fazer a viagem ao banco de dados para obter um novo valor de ID.
G. Demecki
2
É uma micro otimização antes de obter a consulta. O Hibernate tenta atribuir um valor na faixa de allocationSizee, portanto, tenta evitar a consulta ao banco de dados por sequência. Mas esta consulta será executada todas as vezes se você configurá-la para 1. Isso dificilmente fará qualquer diferença, pois se sua base de dados for acessada por algum outro aplicativo, ele criará problemas se o mesmo id for usado por outro aplicativo enquanto isso
Amit Deshpande
E sim, é completamente específico do aplicativo se um tamanho de alocação de 1 tem algum impacto real no desempenho. Em um micro benchmark, é claro, sempre aparecerá como um grande impacto; esse é o problema com a maioria dos benchmarks (micro ou outros), eles simplesmente não são realistas. E mesmo que eles sejam complexos o suficiente para serem realistas, você ainda precisa ver o quão próximo o benchmark está de seu aplicativo real para entender como os resultados do benchmark são aplicáveis ​​aos resultados que você veria em seu aplicativo. Resumindo a história ... teste você mesmo
Steve Ebersole,
2
ESTÁ BEM. Tudo é específico do aplicativo, não é? No caso de seu aplicativo ser somente leitura, o impacto de usar o tamanho de alocação 1000 ou 1 é absolutamente 0. Por outro lado, coisas como essas são as melhores práticas. Se você não respeitar as melhores práticas, elas acumulam e o impacto combinado será sua aplicação torna-se lenta. Outro exemplo seria iniciar uma transação quando você absolutamente não precisa de uma.
Hasan Ceylan
1

Steve Ebersole e outros membros,
Você poderia gentilmente explicar o motivo de uma id com um gap maior (por padrão 50)? Estou usando o Hibernate 4.2.15 e encontrei o seguinte código em org.hibernate.id.enhanced.OptimizerFactory cass.

if ( lo > maxLo ) {
   lastSourceValue = callback.getNextValue();
   lo = lastSourceValue.eq( 0 ) ? 1 : 0;
   hi = lastSourceValue.copy().multiplyBy( maxLo+1 ); 
}  
value = hi.copy().add( lo++ );

Sempre que atinge o interior da instrução if, o valor hi está ficando muito maior. Portanto, meu id durante o teste com a reinicialização frequente do servidor gera os seguintes ids de sequência:
1, 2, 3, 4, 19, 250, 251, 252, 400, 550, 750, 751, 752, 850, 1100, 1150.

Eu sei que você já disse que não estava em conflito com a especificação, mas acredito que essa situação será muito inesperada para a maioria dos desenvolvedores.

A opinião de qualquer pessoa será muito útil.

Jihwan

ATUALIZAÇÃO: ne1410s: Obrigado pela edição.
cfrick: OK. Eu vou fazer isso. Foi meu primeiro post aqui e não sabia como usá-lo.

Agora, eu entendi melhor porque maxLo foi usado para dois propósitos: uma vez que o hibernate chama a sequência do banco de dados uma vez, continua a aumentar o id no nível do Java e salva no banco de dados, o valor do id do nível do Java deve considerar quanto foi alterado sem chamar a sequência DB quando chamar a sequência na próxima vez.

Por exemplo, a id de sequência era 1 em um ponto e hibernar inseriu 5, 6, 7, 8, 9 (com alocaçãoSize = 5). Da próxima vez, quando obtermos o próximo número de sequência, DB retornará 2, mas o hibernate precisa usar 10, 11, 12 ... Então, é por isso que "hi = lastSourceValue.copy (). MultiplyBy (maxLo + 1)" é usado para obter um próximo id 10 dos 2 retornados da sequência do banco de dados. Parece que o único problema foi durante a reinicialização frequente do servidor e este foi o meu problema com a lacuna maior.

Portanto, quando usamos o SEQUENCE ID, o id inserido na tabela não vai corresponder ao número da SEQUENCE no DB.

Jihwan
fonte
1

Depois de cavar no código-fonte do hibernate e a configuração abaixo vai para o banco de dados Oracle para o próximo valor após 50 inserções. Portanto, faça seu INST_PK_SEQ incrementar 50 cada vez que for chamado.

Hibernate 5 é usado para a estratégia abaixo

Verifique também abaixo http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#identifiers-generators-sequence

@Id
@Column(name = "ID")
@GenericGenerator(name = "INST_PK_SEQ", 
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
        @org.hibernate.annotations.Parameter(
                name = "optimizer", value = "pooled-lo"),
        @org.hibernate.annotations.Parameter(
                name = "initial_value", value = "1"),
        @org.hibernate.annotations.Parameter(
                name = "increment_size", value = "50"),
        @org.hibernate.annotations.Parameter(
                name = SequenceStyleGenerator.SEQUENCE_PARAM, value = "INST_PK_SEQ"),
    }
)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INST_PK_SEQ")
private Long id;
Fatih Tekin
fonte
3
Desculpe, mas esta é uma maneira extremamente detalhada de configurar algo, que pode ser expressa facilmente com dois parâmetros para um Hibernate inteiro e, portanto, para todas as entidades.
G. Demecki
verdade, mas quando eu tento de outras maneiras nenhuma delas funcionou, se você a tiver funcionando, pode me enviar como você configurou
fatih tekin
Atualizei minha resposta - agora também inclui um exemplo prático. Embora meu comentário acima esteja parcialmente errado: infelizmente, você não pode definir nem allocationSizenem initialValueglobalmente para todas as entidades (a menos que use apenas um gerador, mas IMHO não é muito legível).
G. Demecki
1
Obrigado pela explicação, mas o que você escreveu acima eu tentei e não funcionou com o hibernate 5.0.7. Versão final então eu cavei no código-fonte para poder atingir este objetivo e essa é a implementação que consegui encontrar no código-fonte do hibernate. A configuração pode parecer ruim, mas infelizmente hiberna a api e estou usando a implementação EntityManager padrão do hibernate
fatih tekin
1

Eu também enfrentei esse problema no Hibernate 5:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
@SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE)
private Long titId;

Recebi um aviso como este abaixo:

Encontrado uso de gerador de id baseado em sequência obsoleto [org.hibernate.id.SequenceHiLoGenerator]; use org.hibernate.id.enhanced.SequenceStyleGenerator em vez disso. Consulte o Guia de mapeamento de modelo de domínio do Hibernate para obter detalhes.

Em seguida, alterei meu código para SequenceStyleGenerator:

@Id
@GenericGenerator(name="cmrSeq", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
                @Parameter(name = "sequence_name", value = "SEQUENCE")}
)
@GeneratedValue(generator = "sequence_name")
private Long titId;

Isso resolveu meus dois problemas:

  1. O aviso de uso suspenso foi corrigido
  2. Agora o id é gerado de acordo com a seqüência do oráculo.
Mohamed Afzal
fonte
0

Gostaria de verificar o DDL para a sequência no esquema. A implementação JPA é responsável apenas pela criação da sequência com o tamanho de alocação correto. Portanto, se o tamanho da alocação for 50, sua sequência deve ter o incremento de 50 em seu DDL.

Este caso pode ocorrer normalmente com a criação de uma sequência com tamanho de alocação 1 e posteriormente configurada para tamanho de alocação 50 (ou padrão), mas a sequência DDL não é atualizada.

Hasan Ceylan
fonte
Você está entendendo mal o meu ponto. ALTER SEQUENCE ... INCREMENTY BY 50;não vai resolver nada, porque o problema continua o mesmo. O valor da sequência ainda não reflete os IDs de entidades reais.
G. Demecki,
Compartilhe um caso de teste para que possamos entender melhor o problema aqui.
Hasan Ceylan,
1
Caso de teste? Por quê? A pergunta postada por mim não foi muito complicada e já foi respondida. Parece que você não sabe como funciona o gerador HiLo. De qualquer forma: obrigado por sacrificar seu tempo e esforço.
G. Demecki,
1
Gregory, Na verdade eu sei do que estou falando, escrevi Batoo JPA que é o% 100 JPA Implementation que está atualmente em sua incubação e bate o Hibernate em termos de velocidade - 15 vezes mais rápido. Por outro lado, eu posso ter entendido mal sua pergunta e não pensei que usar o Hibernate com sequências deveria criar qualquer problema, pois tenho usado o Hibernate desde 2003 em muitos projetos em muitos bancos de dados. O importante é que você encontrou a solução para a pergunta, desculpe, eu perdi a resposta marcada como correta ...
Hasan Ceylan
Desculpe, eu não queria te ofender. Obrigado novamente por sua ajuda, a pergunta foi respondida.
G. Demecki,