简体   繁体   English

在Hibernate中绕过GeneratedValue(合并数据不在db?中)

[英]Bypass GeneratedValue in Hibernate (merge data not in db?)

My problem is the same as described in [1] or [2] . 我的问题与[1][2]中描述的相同。 I need to manually set a by default auto-generated value ( why? importing old data ). 我需要手动设置默认的自动生成值( 为什么?导入旧数据 )。 As described in [1] using Hibernate's entity = em.merge(entity) will do the trick. [1]中所述,使用Hibernate的entity = em.merge(entity)就可以了。

Unfortunately for me it does not. 不幸的是,它没有。 I neither get an error nor any other warning. 我既没有得到错误也没有任何其他警告。 The entity is just not going to appear in the database . 该实体不会出现在数据库中 I'm using Spring and Hibernate EntityManager 3.5.3-Final. 我正在使用Spring和Hibernate EntityManager 3.5.3-Final。

Any ideas? 有任何想法吗?

Another implementation, way simpler. 另一种实现方式更简单。

This one works with both annotation-based or xml-based configuration: it rely on hibernate meta-data to get the id value for the object. 这一次工作基于XML注释或基于配置:它依靠休眠元数据中获取价值为对象的ID。 Replace SequenceGenerator by IdentityGenerator (or any other generator) depending on your configuration. IdentityGenerator (或任何其他生成器)替换SequenceGenerator ,具体取决于您的配置。 (The creation of a decorator instead of subclassing, passing the decorated ID generator as a parameter to this generator, is left as an exercise to the reader). (创建装饰器而不是子类化,将装饰的ID生成器作为参数传递给此生成器,留给读者练习)。

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

Answer to the exercise (using a decorator pattern, as requested), not really tested: 回答练习(使用装饰器模式,按要求),未经过实际测试:

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

it works on my project with the following code: 它适用于我的项目,代码如下:

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

and

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

    }
}

where you basically define your own ID generator (based on the Identity strategy), and if the ID is not set, you delegate the generation to the default generator. 您基本上定义了自己的ID生成器(基于Identity策略),如果未设置ID,则将生成委派给默认生成器。

The main drawback is that it bounds you to Hibernate as JPA provider ... but it works perfectly with my MySQL project 主要的缺点是它将你作为JPA提供程序绑定到Hibernate ...但它与我的MySQL项目完美配合

Updating Laurent Grégoire's answer for hibernate 5.2 because it seems to have changed a bit. 更新LaurentGrégoire对hibernate 5.2的回答,因为它似乎有所改变。

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

and use it like this: (replace the package name) 并像这样使用它:(替换包名)

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

I`m giving a solution here that worked for me: 我在这里给出了一个对我有用的解决方案:
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() 保存而不是使用persist()使用merge()update()

If you are using hibernate's org.hibernate.id.UUIDGenerator to generate a String id I suggest you use: 如果你使用hibernate的org.hibernate.id.UUIDGenerator来生成一个String id,我建议你使用:

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

According to the Selectively disable generation of a new ID thread on the Hibernate forums, merge() might not be the solution (at least not alone) and you might have to use a custom generator (that's the second link you posted). 根据在Hibernate论坛上选择性地禁用新ID线程的生成merge()可能不是解决方案(至少不是单独的),您可能必须使用自定义生成器 (这是您发布的第二个链接)。

I didn't test this myself so I can't confirm but I recommend reading the thread of the Hibernate's forums. 我没有自己测试,所以我无法确认,但我建议阅读Hibernate论坛的主题。

For anyone else looking to do this, above does work nicely. 对于任何想要这样做的人来说,上面确实很有效。 Just a recommendation to getting the identifier from the object rather than having inheritance for each Entity class (Just for the Id), you could do something like: 只是建议从对象获取标识符而不是为每个Entity类继承(Just for the Id),您可以执行以下操作:

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

You need a running transaction. 您需要一个正在运行的事务

In case your transaction are manually-managed: 如果您的交易是手动管理的:

entityManager.getTransaction().begin();

(of course don't forget to commit) (当然不要忘记提交)

If you are using declarative transactions, use the appropriate declaration (via annotations, most likely) 如果您正在使用声明性事务,请使用适当的声明(通过注释,最有可能)

Also, set the hibernate logging level to debug ( log4j.logger.org.hibernate=debug ) in your log4j.properties in order to trace what is happening in more details. 另外,在log4j.properties中将hibernate日志记录级别设置为debuglog4j.logger.org.hibernate=debug ),以便更详细地跟踪发生的情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM