冬眠JPA序列(非Id)的冬眠JPA序列(非Id)的(Hibernate JPA Sequence

2019-05-10 10:42发布

是否有可能使用DB序列对某些列,它是不是标识符/不是组合标识符的一部分

我使用休眠如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提供商也产生了。

只是为了把事情说清楚:我想休眠产生的价值mySequencedValue属性。 我知道Hibernate可以处理数据库生成的值,但我不希望使用触发器或大于休眠本身产生我的财产价值等任何其他东西。 如果Hibernate可以用于主键生成值,为什么就不能生成一个简单的财产?

Answer 1:

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

看来,休眠/ JPA是无法自动为您创造无标识的属性的值。 该@GeneratedValue注释只有配合使用@Id创建自动编号。

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

该解决方案(或变通)建议在该论坛是创建一个单独的实体与生成的ID,这样的事情:

@Entity
public class GeneralSequenceNumber {
  @Id
  @GeneratedValue(...)
  private Long number;
}

@Entity 
public class MyEntity {
  @Id ..
  private Long id;

  @OneToOne(...)
  private GeneralSequnceNumber myVal;
}


Answer 2:

我发现, @Column(columnDefinition="serial")的作品完美,但只限于PostgreSQL。 对我来说,这是完美的解决方案,因为第二个实体是“丑”的选项。



Answer 3:

我知道这是一个很古老的问题,但它首先表现在结果和自提JPA有了很大的变化。

现在要做的是正确的做法是用@Generated注解。 您可以定义序列,设置默认列于序列,然后列映射为:

@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)


Answer 4:

Hibernate的绝对支持这一点。 从文档:

“生成的特性是具有由数据库生成它们的值的特性。通常,休眠刷新其含有的量,数据库生成值的任何属性的对象所需要的应用程序。属性标明为generated,但是,让应用程序负责这个动作休眠。从本质上讲,每当Hibernate执行对定义了generated properties的实体SQL INSERT或UPDATE,会立刻执行一条select来获得生成的值“。

有关插入生成的属性而已,你的属性映射(的.hbm.xml)将如下所示:

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

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

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

不幸的是,我不知道JPA,因此,如果此功能是通过JPA暴露我不知道(我怀疑可能不是)

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

这是你将如何在INSERT和UPDATE语句中使用排除属性:

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

同样,我不知道JPA公开这些休眠功能,但是Hibernate并不支持他们。



Answer 5:

作为后续这里是我如何开始工作:

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


Answer 6:

虽然这是一个古老的线程我想分享我的解决方案,并希望得到一些这方面的反馈。 但是要注意,我只测试了一些JUnit的测试用例我的本地数据库,这个解决方案。 所以,这不是一个富有成效的功能至今。

我解决了这个问题,我通过引入所谓的序列,没有产权的自定义注释。 它只是应该从一个递增的序列被分配一个值字段的标志。

@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。 请注意,我用的弹簧豆容器。

@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定义的序列号。 如果它运行的序列号,它加载目标类和储备下一个电话incrementValue重视的SequenceNumber实体。 这样,我不需要每次需要序列值的时间来查询数据库。 请注意,正在打开预留下一组序列号的StatelessSession。 你不能使用目标实体当前坚持同一个会话,因为这将导致EntityPersister一个ConcurrentModificationException的。

希望这可以帮助别人。



Answer 7:

我像你一样的情况运行,我还没有发现任何严重的答案,如果它基本上可以产生非ID propertys与JPA与否。

我的解决方法是调用序列与天然JPA查询persisiting前设置手动财产。

这不是令人满意的,但它可以作为暂时解决方法。

马里奥



Answer 8:

我使用Hibernate固定UUID(或序列)的产生@PrePersist注释:

@PrePersist
public void initializeUUID() {
    if (uuid == null) {
        uuid = UUID.randomUUID().toString();
    }
}


Answer 9:

我发现从JPA规范在会议9.1.9 GeneratedValue注释这个特定的纸条:“[43]便携式应用程序不应该使用其他持久字段或属性的GeneratedValue注解。” 所以,我相信,这是不可能自动生成至少使用简单JPA非主键值值。



Answer 10:

“我不想使用触发器或大于休眠本身以外的任何其他东西来为我的属性值”

在这种情况下,如何如何创建用户类型的实现产生所需的值,并配置使用该用户类型为mySequenceVal财产的持久性元数据?



Answer 11:

这是不一样的使用顺序。 当使用序列,你是不是插入或更新任何东西。 你只是在检索下一个序列值。 它看起来像Hibernate不支持它。



Answer 12:

如果您在使用PostgreSQL
而我用了春天开机1.5.6

@Column(columnDefinition = "serial")
@Generated(GenerationTime.INSERT)
private Integer orderID;


Answer 13:

我一直在一种情况就像你(JPA /休眠序列非@Id场),我结束了创建我的数据库架构一个触发器,在插入时添加唯一的序列号。 我从来没有得到它与J​​PA /休眠工作



Answer 14:

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

对于Oracle 12C:

ID NUMBER GENERATED as IDENTITY

对于H2:

ID BIGINT GENERATED as auto_increment

另外,还要:

@Column(insertable = false)


文章来源: Hibernate JPA Sequence (non-Id)