Programing

최대 절전 모드 JPA 시퀀스 (비 ID)

lottogame 2020. 7. 13. 08:12
반응형

최대 절전 모드 JPA 시퀀스 (비 ID)


식별자가 아니거나 복합 식별자의 일부가 아닌 일부 열에 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는 생성되지만 mySequenceValJPA 제공 업체에서도 속성을 생성합니다.

분명히하기 위해 : HibernatemySequencedValue속성 의 값을 생성하기를 원합니다 . Hibernate가 데이터베이스 생성 값을 처리 할 수 ​​있다는 것을 알고 있지만, 트리거나 Hibernate 이외의 다른 것을 사용하여 내 속성 값을 생성하고 싶지 않습니다. 최대 절전 모드에서 기본 키에 대한 값을 생성 할 수 있다면 간단한 속성에 대해 생성 할 수없는 이유는 무엇입니까?


이 문제에 대한 답을 찾고이 링크를 우연히 발견했습니다.

Hibernate / JPA가 비 ID 속성에 대한 값을 자동으로 생성 할 수없는 것 같습니다. @GeneratedValue과 주석 만 함께 사용됩니다 @Id자동으로 번호를 만들 수 있습니다.

@GeneratedValue주석은 데이터베이스가이 값 자체를 생성하는 것을 Hibernate에게 알려준다.

이 포럼에서 제안한 솔루션 (또는 해결 방법)은 다음과 같이 생성 된 ID로 별도의 엔터티를 만드는 것입니다.

@실재
공개 클래스 GeneralSequenceNumber {
  @신분증
  @GeneratedValue (...)
  개인용 긴 번호;
}

@실재 
공개 클래스 MyEntity {
  @ 아이디 ..
  개인 롱 ID;

  @1-1(...)
  개인 GeneralSequnceNumber myVal;
}

나는 그것이 @Column(columnDefinition="serial")PostgreSQL에 대해서만 완벽하게 작동 한다는 것을 알았습니다 . 나를 위해 이것은 완벽한 솔루션이었습니다. 왜냐하면 두 번째 엔터티는 "추악한"옵션이기 때문입니다.


나는 이것이 매우 오래된 질문이라는 것을 알고 있지만 첫 번째 결과에 표시되었으며 질문 이후 jpa가 많이 변경되었습니다.

지금 올바른 방법은 @Generated주석을 사용하는 것입니다. 시퀀스를 정의하고 열의 기본값을 해당 시퀀스로 설정 한 다음 열을 다음과 같이 매핑 할 수 있습니다.

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

최대 절전 모드가이를 지원합니다. 문서에서 :

"생성 된 속성은 데이터베이스에서 값을 생성 한 속성입니다. 일반적으로 Hibernate 응용 프로그램은 데이터베이스가 값을 생성 한 속성이 포함 된 개체를 새로 고쳐야했습니다. 그러나 속성을 생성 된 것으로 표시하면 응용 프로그램이이 책임을 Hibernate에 위임 할 수 있습니다. 기본적으로, Hibernate는 생성 된 속성을 정의한 엔티티에 대해 SQL INSERT 또는 UPDATE를 발행 할 때마다 생성 된 값을 검색하기 위해 즉시 선택을 발행합니다. "

삽입시에만 생성 된 속성의 경우 속성 매핑 (.hbm.xml)은 다음과 같습니다.

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

삽입 및 업데이트시 생성되는 속성의 경우 속성 매핑 (.hbm.xml)은 다음과 같습니다.

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

불행히도 JPA를 모르므 로이 기능이 JPA를 통해 노출되는지 알 수 없습니다 (아마 의심되지 않습니다)

또는 삽입 및 업데이트에서 속성을 제외하고 "수동으로"call session.refresh (obj); 삽입 / 업데이트 한 후 데이터베이스에서 생성 된 값을로드하십시오.

다음은 insert 및 update 문에서 속성이 사용되지 않도록하는 방법입니다.

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

다시 한 번, JPA가 이러한 최대 절전 기능을 제공하는지 여부는 알 수 없지만 최대 절전 기능은이를 지원합니다.


후속 조치로 다음과 같이 작동합니다.

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

이것은 오래된 스레드이지만 솔루션을 공유하고 이에 대한 의견을 얻고 싶습니다. 일부 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입니다. 스프링을 빈 컨테이너로 사용했습니다.

@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 엔터티를로드하고 다음 호출에 대해 incrementalValue 값을 예약합니다. 이렇게하면 시퀀스 값이 필요할 때마다 데이터베이스를 쿼리 할 필요가 없습니다. 다음 시퀀스 번호 세트를 예약하기 위해 열려있는 StatelessSession에 유의하십시오. EntityPersister에서 ConcurrentModificationException이 발생하므로 대상 엔티티가 현재 지속되는 동일한 세션을 사용할 수 없습니다.

이것이 누군가를 돕기를 바랍니다.


@PrePersist주석을 사용하여 Hibernate로 UUID (또는 시퀀스) 생성을 수정했습니다 .

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

I run in the same situation like you and I also didn't find any serious answers if it is basically possible to generate non-id propertys with JPA or not.

My solution is to call the sequence with a native JPA query to set the property by hand before persisiting it.

This is not satisfying but it works as a workaround for the moment.

Mario


If you are using postgresql
And i'm using in spring boot 1.5.6

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

I've found this specific note in session 9.1.9 GeneratedValue Annotation from JPA specification: "[43] Portable applications should not use the GeneratedValue annotation on other persistent fields or properties." So, I presume that it is not possible to auto generate value for non primary key values at least using simply JPA.


Looks like thread is old, I just wanted to add my solution here(Using AspectJ - AOP in spring).

Solution is to create a custom annotation @InjectSequenceValue as follows.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectSequenceValue {
    String sequencename();
}

Now you can annotate any field in entity, so that the underlying field (Long/Integer) value will be injected at runtime using the nextvalue of the sequence.

Annotate like this.

//serialNumber will be injected dynamically, with the next value of the serialnum_sequence.
 @InjectSequenceValue(sequencename = "serialnum_sequence") 
  Long serialNumber;

So far we have marked the field we need to inject the sequence value.So we will look how to inject the sequence value to the marked fields, this is done by creating the point cut in AspectJ.

We will trigger the injection just before the save/persist method is being executed.This is done in the below class.

@Aspect
@Configuration
public class AspectDefinition {

    @Autowired
    JdbcTemplate jdbcTemplate;


    //@Before("execution(* org.hibernate.session.save(..))") Use this for Hibernate.(also include session.save())
    @Before("execution(* org.springframework.data.repository.CrudRepository.save(..))") //This is for JPA.
    public void generateSequence(JoinPoint joinPoint){

        Object [] aragumentList=joinPoint.getArgs(); //Getting all arguments of the save
        for (Object arg :aragumentList ) {
            if (arg.getClass().isAnnotationPresent(Entity.class)){ // getting the Entity class

                Field[] fields = arg.getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(InjectSequenceValue.class)) { //getting annotated fields

                        field.setAccessible(true); 
                        try {
                            if (field.get(arg) == null){ // Setting the next value
                                String sequenceName=field.getAnnotation(InjectSequenceValue.class).sequencename();
                                long nextval=getNextValue(sequenceName);
                                System.out.println("Next value :"+nextval); //TODO remove sout.
                                field.set(arg, nextval);
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

        }
    }

    /**
     * This method fetches the next value from sequence
     * @param sequence
     * @return
     */

    public long getNextValue(String sequence){
        long sequenceNextVal=0L;

        SqlRowSet sqlRowSet= jdbcTemplate.queryForRowSet("SELECT "+sequence+".NEXTVAL as value FROM DUAL");
        while (sqlRowSet.next()){
            sequenceNextVal=sqlRowSet.getLong("value");

        }
        return  sequenceNextVal;
    }
}

Now you can annotate any Entity as below.

@Entity
@Table(name = "T_USER")
public class UserEntity {

    @Id
    @SequenceGenerator(sequenceName = "userid_sequence",name = "this_seq")
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "this_seq")
    Long id;
    String userName;
    String password;

    @InjectSequenceValue(sequencename = "serialnum_sequence") // this will be injected at the time of saving.
    Long serialNumber;

    String name;
}

"I don't want to use a trigger or any other thing other than Hibernate itself to generate the value for my property"

In that case, how about creating an implementation of UserType which generates the required value, and configuring the metadata to use that UserType for persistence of the mySequenceVal property?


This is not the same as using a sequence. When using a sequence, you are not inserting or updating anything. You are simply retrieving the next sequence value. It looks like hibernate does not support it.


I've been in a situation like you (JPA/Hibernate sequence for non @Id field) and I ended up creating a trigger in my db schema that add a unique sequence number on insert. I just never got it to work with JPA/Hibernate


After spending hours, this neatly helped me to solve my problem:

For Oracle 12c:

ID NUMBER GENERATED as IDENTITY

For H2:

ID BIGINT GENERATED as auto_increment

Also make:

@Column(insertable = false)

참고URL : https://stackoverflow.com/questions/277630/hibernate-jpa-sequence-non-id

반응형