Hibernate JPA序列(非Id)

java hibernate jpa sequence

99850 观看

15回复

13144 作者的声誉

对于某些不是标识符/不是复合标识符一部分的列,是否可以使用DB序列?

我正在使用hibernate作为jpa提供程序,并且我有一个表有一些生成值的列(使用序列),尽管它们不是标识符的一部分。

我想要的是使用序列为实体创建一个新值,其中序列的列不是主键的一部分:

@Entity
@Table(name = "MyTable")
public class MyEntity {

    //...
    @Id //... etc
    public Long getId() {
        return id;
    }

   //note NO @Id here! but this doesn't work...
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "myGen")
    @SequenceGenerator(name = "myGen", sequenceName = "MY_SEQUENCE")
    @Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true)
    public Long getMySequencedValue(){
      return myVal;
    }

}

然后,当我这样做:

em.persist(new MyEntity());

将生成id,但该mySequenceVal属性也将由我的JPA提供程序生成。

只是为了说清楚:我希望HibernatemySequencedValue属性生成值。我知道Hibernate可以处理数据库生成的值,但是我不想使用触发器或除Hibernate之外的任何其他东西来为我的属性生成值。如果Hibernate可以为主键生成值,为什么它不能为简单属性生成?

作者: Miguel Ping 的来源 发布者: 2008 年 11 月 10 日

回应 (15)


-1

4288 作者的声誉

我一直处在像你这样的情况下(非@Id字段的JPA / Hibernate序列)我最终在我的数据库模式中创建了一个触发器,它在插入时添加了一个唯一的序列号。我从来没有使用过JPA / Hibernate

作者: Frederic Morin 发布者: 12.11.2008 01:54

14

1988 作者的声誉

Hibernate绝对支持这一点。来自文档:

“生成的属性是由数据库生成其值的属性。通常,Hibernate应用程序需要刷新包含数据库生成值的任何属性的对象。但是,将属性标记为生成,可以让应用程序将此职责委托给Hibernate。实质上,每当Hibernate为已定义生成属性的实体发出SQL INSERT或UPDATE时,它会立即发出一个select来检索生成的值。“

对于仅在insert上生成的属性,属性映射(.hbm.xml)将如下所示:

<property name="foo" generated="insert"/>

对于在插入和更新时生成的属性,属性映射(.hbm.xml)将如下所示:

<property name="foo" generated="always"/>

不幸的是,我不知道JPA,所以我不知道这个功能是否通过JPA公开(我怀疑可能没有)

或者,您应该能够从插入和更新中排除该属性,然后“手动”调用session.refresh(obj); 插入/更新后,从数据库加载生成的值。

这是在排除和更新语句中使用的排除属性的方法:

<property name="foo" update="false" insert="false"/>

同样,我不知道JPA是否公开了这些Hibernate功能,但Hibernate确实支持它们。

作者: alasdairg 发布者: 12.11.2008 10:57

0

1988 作者的声誉

“我不想使用触发器或除Hibernate之外的任何其他东西来为我的属性生成值”

在这种情况下,如何创建生成所需值的UserType实现,并配置元数据以使用该UserType持久化mySequenceVal属性?

作者: alasdairg 发布者: 17.11.2008 04:29

3

0 作者的声誉

我和你一样在同样的情况下运行,如果基本上可以用JPA生成非id属性,我也没有找到任何严肃的答案。

我的解决方案是使用本机JPA查询调用序列,以便在持久化之前手动设置属性。

这并不令人满意,但目前它可以作为一种解决方法。

马里奥

作者: Mario 发布者: 21.11.2008 02:24

58

965 作者的声誉

决定

寻找这个问题的答案,我偶然发现了这个链接

似乎Hibernate / JPA无法为您的非id属性自动创建值。该@GeneratedValue注释只有配合使用@Id,以创建自动编号。

@GeneratedValue注释只是告诉Hibernate数据库已生成该值本身。

该论坛中建议的解决方案(或解决方法)是使用生成的Id创建一个单独的实体,如下所示:

@实体
公共类GeneralSequenceNumber {
  @ID
  @GeneratedValue(...)
  私人长号;
}

@实体 
公共课MyEntity {
  @ID ..
  私人长身份;

  @OneToOne(...)
  私人GeneralSequnceNumber myVal;
}
作者: Morten Berg 发布者: 11.02.2009 09:35

0

1 作者的声誉

这与使用序列不同。使用序列时,您不会插入或更新任何内容。您只是检索下一个序列值。看起来hibernate不支持它。

作者: kammy 发布者: 11.11.2009 08:36

7

106 作者的声誉

作为后续跟进,我是如何让它工作的:

@Override public Long getNextExternalId() {
    BigDecimal seq =
        (BigDecimal)((List)em.createNativeQuery("select col_msd_external_id_seq.nextval from dual").getResultList()).get(0);
    return seq.longValue();
}
作者: Paul 发布者: 19.04.2010 04:42

1

11 作者的声誉

我在JPA规范的会话9.1.9 GeneratedValue Annotation中找到了这个特定的注释:“[43]便携式应用程序不应该在其他持久字段或属性上使用GeneratedValue注释。” 因此,我认为至少使用JPA不可能为非主键值自动生成值。

作者: Gustavo Orair 发布者: 24.11.2010 07:31

32

5529 作者的声誉

我发现这种方法很@Column(columnDefinition="serial")完美,但仅适用于PostgreSQL。对我来说这是完美的解决方案,因为第二个实体是“丑陋”的选择。

作者: Sergey Vedernikov 发布者: 18.05.2012 06:44

4

182 作者的声誉

虽然这是一个旧线程,但我想分享我的解决方案并希望得到一些反馈。请注意,我只在一些JUnit测试用例中使用我的本地数据库测试了此解决方案。所以到目前为止这不是一个有效的功能。

我通过引入一个名为Sequence且没有属性的自定义注释来解决这个问题。它只是字段的标记,应该从递增的序列中分配值。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}

使用此注释我标记了我的实体。

public class Area extends BaseEntity implements ClientAware, IssuerAware
{
    @Column(name = "areaNumber", updatable = false)
    @Sequence
    private Integer areaNumber;
....
}

为了保持数据库独立,我引入了一个名为SequenceNumber的实体,它保存了序列当前值和增量大小。我选择className作为唯一键,因此每个实体类都会得到自己的序列。

@Entity
@Table(name = "SequenceNumber", uniqueConstraints = { @UniqueConstraint(columnNames = { "className" }) })
public class SequenceNumber
{
    @Id
    @Column(name = "className", updatable = false)
    private String className;

    @Column(name = "nextValue")
    private Integer nextValue = 1;

    @Column(name = "incrementValue")
    private Integer incrementValue = 10;

    ... some getters and setters ....
}

最后一步也是最困难的是处理序列号赋值的PreInsertListener。请注意,我使用spring作为bean容器。

@Component
public class SequenceListener implements PreInsertEventListener
{
    private static final long serialVersionUID = 7946581162328559098L;
    private final static Logger log = Logger.getLogger(SequenceListener.class);

    @Autowired
    private SessionFactoryImplementor sessionFactoryImpl;

    private final Map<String, CacheEntry> cache = new HashMap<>();

    @PostConstruct
    public void selfRegister()
    {
        // As you might expect, an EventListenerRegistry is the place with which event listeners are registered
        // It is a service so we look it up using the service registry
        final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);

        // add the listener to the end of the listener chain
        eventListenerRegistry.appendListeners(EventType.PRE_INSERT, this);
    }

    @Override
    public boolean onPreInsert(PreInsertEvent p_event)
    {
        updateSequenceValue(p_event.getEntity(), p_event.getState(), p_event.getPersister().getPropertyNames());

        return false;
    }

    private void updateSequenceValue(Object p_entity, Object[] p_state, String[] p_propertyNames)
    {
        try
        {
            List<Field> fields = ReflectUtil.getFields(p_entity.getClass(), null, Sequence.class);

            if (!fields.isEmpty())
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Intercepted custom sequence entity.");
                }

                for (Field field : fields)
                {
                    Integer value = getSequenceNumber(p_entity.getClass().getName());

                    field.setAccessible(true);
                    field.set(p_entity, value);
                    setPropertyState(p_state, p_propertyNames, field.getName(), value);

                    if (log.isDebugEnabled())
                    {
                        LogMF.debug(log, "Set {0} property to {1}.", new Object[] { field, value });
                    }
                }
            }
        }
        catch (Exception e)
        {
            log.error("Failed to set sequence property.", e);
        }
    }

    private Integer getSequenceNumber(String p_className)
    {
        synchronized (cache)
        {
            CacheEntry current = cache.get(p_className);

            // not in cache yet => load from database
            if ((current == null) || current.isEmpty())
            {
                boolean insert = false;
                StatelessSession session = sessionFactoryImpl.openStatelessSession();
                session.beginTransaction();

                SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className);

                // not in database yet => create new sequence
                if (sequenceNumber == null)
                {
                    sequenceNumber = new SequenceNumber();
                    sequenceNumber.setClassName(p_className);
                    insert = true;
                }

                current = new CacheEntry(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue(), sequenceNumber.getNextValue());
                cache.put(p_className, current);
                sequenceNumber.setNextValue(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue());

                if (insert)
                {
                    session.insert(sequenceNumber);
                }
                else
                {
                    session.update(sequenceNumber);
                }
                session.getTransaction().commit();
                session.close();
            }

            return current.next();
        }
    }

    private void setPropertyState(Object[] propertyStates, String[] propertyNames, String propertyName, Object propertyState)
    {
        for (int i = 0; i < propertyNames.length; i++)
        {
            if (propertyName.equals(propertyNames[i]))
            {
                propertyStates[i] = propertyState;
                return;
            }
        }
    }

    private static class CacheEntry
    {
        private int current;
        private final int limit;

        public CacheEntry(final int p_limit, final int p_current)
        {
            current = p_current;
            limit = p_limit;
        }

        public Integer next()
        {
            return current++;
        }

        public boolean isEmpty()
        {
            return current >= limit;
        }
    }
}

从上面的代码中可以看出,侦听器为每个实体类使用了一个SequenceNumber实例,并保留了一些由SequenceNumber实体的incrementValue定义的序列号。如果序列号用完,则为目标类加载SequenceNumber实体,并为下次调用保留incrementValue值。这样,每次需要序列值时,我都不需要查询数据库。请注意正在打开的StatelessSession,用于保留下一组序列号。您不能使用目标实体当前持久化的相同会话,因为这会导致EntityPersister中出现ConcurrentModificationException。

希望这有助于某人。

作者: Sebastian Götz 发布者: 07.08.2012 09:16

15

911 作者的声誉

我知道这是一个非常古老的问题,但它首先显示了结果,jpa自问题以来发生了很大的变化。

现在正确的方法是使用@Generated注释。您可以定义序列,将列中的默认值设置为该序列,然后将列映射为:

@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)
作者: Rumal 发布者: 23.05.2014 02:12

2

3428 作者的声誉

我用Hibernate使用@PrePersist注释修复了UUID(或序列)的生成:

@PrePersist
public void initializeUUID() {
    if (uuid == null) {
        uuid = UUID.randomUUID().toString();
    }
}
作者: Matroska 发布者: 09.03.2016 09:59

-1

3194 作者的声誉

花了好几个小时后,这整齐地帮助我解决了我的问题:

对于Oracle 12c:

ID NUMBER GENERATED as IDENTITY

对于H2:

ID BIGINT GENERATED as auto_increment

还可以:

@Column(insertable = false)
作者: Spring 发布者: 19.03.2018 05:03

0

36 作者的声誉

如果您正在使用postgresql
而我正在使用spring boot 1.5.6

@Column(columnDefinition = "serial")
@Generated(GenerationTime.INSERT)
private Integer orderID;
作者: Sulaymon Hursanov 发布者: 23.11.2018 06:52

0

1 作者的声誉

一个更简单的解决方案是简单地将列设置为insertable = false,updatable = false。

@Column(nullable = false, unique=true, insertable=false, updatable=false)
private long itemCount;

这是我的解决方案。

作者: Kwaku Twumasi 发布者: 18.03.2019 01:45
32x32