@Id @GeneratedValue but set own ID value
Asked Answered
B

5

35

I have a table with a generated id, but in some cases I would like to set it on my own. Can I, somehow, force Hibernate to ignore the @GeneratedValue?

Broadminded answered 21/1, 2010 at 9:42 Comment(6)
Have you tried it, see what happens?Apocrypha
Yes, Hibernate does not care if the id is set or not - a value is generated anyway.Broadminded
I knew I'd seen this before somewhere: #89939 - use merge() rather than persist()Apocrypha
Don't forget to say entity=merge(entity). The returned entity is the managed one; the original one remains unmanaged.Stronski
This does not seem to work for me, my entity still gets a generated id, although i use mergeBroadminded
@Broadminded Show your mapping and how do you save a new Entity, please. ThanksVibratory
P
8

It may be an overkill but have you thought about writing your own CustomIDGenerator which probably subclasses say the AutoGenerator of hibernate and exposes a couple of methods where you can set the id of the next class object to be generated so for example

class MyGenerator extends .... {

public void setIdForObject(Class clazz, Long id) {
    //once you use this API, the next time an object of 
    //type clazz is saved the id is used
}

public void setIdForObject(Class clazz, Long id, Matcher matcher) {
    //once you use this API, the next time an object of 
    //type clazz is saved and the matcher matches yes the id will be 
    //assigned. Your matcher can match properties like name, age etc
    //to say the matched object
}
}

This could get complicated but at the least is possible as per hibernate doco

Predominate answered 15/2, 2010 at 10:59 Comment(3)
On the hibernate.org forums, there is a user that wrote his Generator class for this exact scenario: forum.hibernate.org/viewtopic.php?p=2404032Plank
I stumpled upon this request looking for a solution to the exact same question (though I am using EclipseLink instead of hibernate). I started using a custom ID generator but (at least in my EclipseLink scenario) there is one problem: How do I know if the object that needs to be persisted already has an ID or not? I don't have the entity object in my custom ID generator at hand. So I can't determine if I need to generate a new ID - in case the object does not have one. Or if I just return the id the object already has (or maybe null or whatever is necessary).Grackle
Add-on to my comment before: In EclipseLink, when creating an own Sequence generator there is a method called shouldAlwaysOverrideExistingValue which can be overridden. This seems to be a possible solution. Although it feels a little uncomfortable to override things I do not completely oversee and also didn't find any documentation to other than this bug entry.Grackle
O
8

create your own identifiergenerator/sequencegenerator

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);
}

}

modify your entity as:

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

and while saving instead of using persist() use merge() or update()

Orthogonal answered 20/4, 2017 at 12:38 Comment(0)
M
7

Although this question was asked quite a while ago, I found the perfect answer for it in this post by @lOranger, and wanted to share it.

This proposal checks if the object's current id is set to something other than null, and if so, it uses it, otherwise, it generates it using the default (or configured) generation strategy.

It's simple, straight forward, and addresses the issue brought up by @Jens, of one not being able to retrieve the object's current id.

I just implemented it (by extending the UUIDGenerator), and it works like a charm :-D

Mauser answered 13/11, 2012 at 5:46 Comment(0)
S
2

For you use case, you can manually add this no user. One way to do it is to put the insert operation on a file named "./import.sql" (in your classpath). Hibernate will go execute these statements when the SessionFactory is started.

Shelves answered 24/1, 2010 at 18:21 Comment(2)
@Emmanuel Bernard He said: but, in some cases, i would like to set it on my own. If you define how Hibernate should save an Entity (through "/.import.sql" file), so its automatic generation of identifier will not work, do not ?Vibratory
No it's unrelated, import.sql is plain SQL so you can do what you want, including updating the sequence or table id if neededShelves
T
0

@rakesh's solution doesn't work since hibernate 6.2 https://discourse.hibernate.org/t/hybrid-id-generation-in-hibernate-6-2-is-difficult/7666

I found a solution for hibernate 6.4.4.Final

public class AssignedOrIdentityGenerator extends IdentityGenerator implements BeforeExecutionGenerator {

    @Override
    public Object generate(SharedSessionContractImplementor session, Object entity, Object currentValue, EventType eventType) {
        return getId(entity, session);
    }

    private static Object getId(Object entity, SharedSessionContractImplementor session) {
        return session.getEntityPersister(null, entity)
                .getIdentifier(entity, session);
    }

    /**
     * @implNote Method {@link #generate(SharedSessionContractImplementor, Object, Object, EventType)}
     * is called if this method returns false
     */
    @Override
    public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
        Object id = getId(entity, session);
        return isNull(id);
    }

    @Override
    public boolean generatedOnExecution() {
        // This method is called to configure a context (using this Generator) without knowledge of a specific Entity.
        // The choice for the real Entity must be made in the this.generatedOnExecution(entity, session) method.
        // For example, find out comment "support mixed-timing generators" in IdentifierGeneratorUtil.class (hibernate-core):
        // true is required, if ID sometimes should be generated by RDBMS (for example by AUTO_INCREMENT)
        return true;
    }
}

This class completly replaces @rakesh's FilterIdentifierGenerator, the rest of the code can remain the same. This class works for AUTO_INCREMENT / IDENTITY db columns.

If you want use TABLE, SEQUENCE, UUID id generation types you can use class I've described here. Then you can wrap required "before" and "on" sql statement execution id generator implementations

class AssignedOrIdentityGenerator extends BeforeOrOnExecutionGenerator {
    public AssignedOrIdentityGenerator() {
        super(new Assigned(), new IdentityGenerator());
    }
}

With hibernate 6.4.4 you can also create simple annotation

@Retention(RUNTIME)
@Target({METHOD, FIELD})
@IdGeneratorType(AssignedOrIdentityGenerator.class)
public @interface AssignedOrGeneratedValue {
}

So you can replace this code snippet

@Id
@GeneratedValue(generator = "assigned-or-generated")
@GenericGenerator(name = "assigned-or-generated", type = AssignedOrIdentityGenerator.class)
@Column(name = "id")
private Integer id;

with a simpler one

@Id
@AssignedOrGeneratedValue
@Column(name = "id")
private Integer id;
Therefrom answered 13/4 at 12:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.