Bypass GeneratedValue no Hibernate (dados de mesclagem não em db?)

Meu problema é o mesmo descrito em [1] ou [2] . Eu preciso definir manualmente um valor auto-gerado por padrão ( por que? Importar dados antigos ). Como descrito em [1] usando o entity = em.merge(entity) do Hibernate, ele fará o truque.

Infelizmente para mim isso não acontece. Eu nem recebo um erro nem qualquer outro aviso. A entidade simplesmente não aparecerá no database . Estou usando o Spring e o Hibernate EntityManager 3.5.3-Final.

Alguma ideia?

funciona no meu projeto com o seguinte código:

 @XmlAttribute @Id @Basic(optional = false) @GeneratedValue(strategy=GenerationType.IDENTITY, generator="IdOrGenerated") @GenericGenerator(name="IdOrGenerated", strategy="....UseIdOrGenerate" ) @Column(name = "ID", nullable = false) private Integer id; 

e

 import org.hibernate.id.IdentityGenerator; ... public class UseIdOrGenerate extends IdentityGenerator { private static final Logger log = Logger.getLogger(UseIdOrGenerate.class.getName()); @Override public Serializable generate(SessionImplementor session, Object obj) throws HibernateException { if (obj == null) throw new HibernateException(new NullPointerException()) ; if ((((EntityWithId) obj).getId()) == null) { Serializable id = super.generate(session, obj) ; return id; } else { return ((EntityWithId) obj).getId(); } } 

onde basicamente você define seu próprio gerador de ID (com base na estratégia Identity) e, se o ID não estiver definido, você delegará a geração ao gerador padrão.

A principal desvantagem é que ele limita você ao Hibernate como provedor JPA … mas funciona perfeitamente com meu projeto MySQL

Outra implementação, muito mais simples.

Este funciona com configuração baseada em anotação ou baseada em xml: ele depende de meta-dados de hibernação para obter o valor de id para o object. Substitua SequenceGenerator por IdentityGenerator (ou qualquer outro gerador) dependendo da sua configuração. (A criação de um decorador em vez de subsorting, passando o gerador de ID decorado como um parâmetro para este gerador, é deixada como um exercício para o leitor).

 public class UseExistingOrGenerateIdGenerator extends SequenceGenerator { @Override public Serializable generate(SessionImplementor session, Object object) throws HibernateException { Serializable id = session.getEntityPersister(null, object) .getClassMetadata().getIdentifier(object, session); return id != null ? id : super.generate(session, object); } } 

Resposta ao exercício (usando um padrão de decorador, conforme solicitado), não foi realmente testado:

 public class UseExistingOrGenerateIdGenerator implements IdentifierGenerator, Configurable { private IdentifierGenerator defaultGenerator; @Override public void configure(Type type, Properties params, Dialect d) throws MappingException; // For example: take a class name and create an instance this.defaultGenerator = buildGeneratorFromParams( params.getProperty("default")); } @Override public Serializable generate(SessionImplementor session, Object object) throws HibernateException { Serializable id = session.getEntityPersister(null, object) .getClassMetadata().getIdentifier(object, session); return id != null ? id : defaultGenerator.generate(session, object); } } 

Atualizando a resposta de Laurent Grégoire para o hibernate 5.2 porque parece ter mudado um pouco.

 public class UseExistingIdOtherwiseGenerateUsingIdentity extends IdentityGenerator { @Override public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException { Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session); return id != null ? id : super.generate(session, object); } } 

e usá-lo assim: (substitua o nome do pacote)

 @Id @GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "{package}.UseExistingIdOtherwiseGenerateUsingIdentity") @GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity") @Column(unique = true, nullable = false) protected Integer id; 

Eu estou dando uma solução aqui que funcionou para mim:
crie seu próprio identificador / sequenciador

 public class FilterIdentifierGenerator extends IdentityGenerator implements IdentifierGenerator{ @Override public Serializable generate(SessionImplementor session, Object object) throws HibernateException { // TODO Auto-generated method stub Serializable id = session.getEntityPersister(null, object) .getClassMetadata().getIdentifier(object, session); return id != null ? id : super.generate(session, object); } } 

modifique sua entidade como:

 @Id @GeneratedValue(generator="myGenerator") @GenericGenerator(name="myGenerator", strategy="package.FilterIdentifierGenerator") @Column(unique=true, nullable=false) private int id; ... 

e ao salvar em vez de usar persist() use merge() ou update()

De acordo com a opção Desativar seletivamente um novo encadeamento de ID nos fóruns do Hibernate, merge() pode não ser a solução (pelo menos não apenas) e talvez seja necessário usar um gerador personalizado (esse é o segundo link que você publicou).

Eu mesmo não testei isso, então não posso confirmar, mas eu recomendo ler o tópico dos fóruns do Hibernate.

Para qualquer outra pessoa que queira fazer isso, acima de tudo funciona bem. Apenas uma recomendação para obter o identificador do object em vez de ter inheritance para cada class Entity (Just for the Id), você poderia fazer algo como:

 import org.hibernate.id.IdentityGenerator; public class UseIdOrGenerate extends IdentityGenerator { private static final Logger log = Logger.getLogger(UseIdOrGenerate.class .getName()); @Override public Serializable generate(SessionImplementor session, Object object) throws HibernateException { if (object == null) throw new HibernateException(new NullPointerException()); for (Field field : object.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Id.class) && field.isAnnotationPresent(GeneratedValue.class)) { boolean isAccessible = field.isAccessible(); try { field.setAccessible(true); Object obj = field.get(object); field.setAccessible(isAccessible); if (obj != null) { if (Integer.class.isAssignableFrom(obj.getClass())) { if (((Integer) obj) > 0) { return (Serializable) obj; } } } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } return super.generate(session, object); } } 

Você precisa de uma transação em execução.

Caso sua transação seja gerenciada manualmente:

 entityManager.getTransaction().begin(); 

(claro que não se esqueça de cometer)

Se você estiver usando transactions declarativas, use a declaração apropriada (via annotations, provavelmente)

Além disso, defina o nível de log de hibernação para debug ( log4j.logger.org.hibernate=debug ) em seu log4j.properties para rastrear o que está acontecendo em mais detalhes.

Se você está usando o org.hibernate.id.UUIDGenerator do hibernate para gerar um id de String eu sugiro que você use:

 public class UseIdOrGenerate extends UUIDGenerator { @Override public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException { Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session); return id != null ? id : super.generate(session, object); } }