سؤال

هل من الممكن استخدام تسلسل 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());

سيتم إنشاء المعرف ، ولكن mySequenceVal سيتم إنشاء العقار أيضًا بواسطة مزود JPA الخاص بي.

فقط لتوضيح الأمور: أريد بيات شتوى لتوليد القيمة ل mySequencedValue منشأه. أعلم أنه يمكن لـ Hibernate التعامل مع القيم التي أنشأتها قاعدة البيانات ، لكنني لا أرغب في استخدام مشغل أو أي شيء آخر غير السبات نفسه لإنشاء القيمة لممتلكاتي. إذا كان بإمكان السبات أن يولد قيمًا للمفاتيح الأساسية ، فلماذا لا يمكن توليد خاصية بسيطة؟

هل كانت مفيدة؟

المحلول

أبحث عن إجابات لهذه المشكلة ، تعثرت عليها هذا الرابط

يبدو أن Hibernate/JPA غير قادر على إنشاء قيمة للاشتعالات غير المعتادة تلقائيًا. ال @GeneratedValue يستخدم التعليق التوضيحي فقط بالاقتران مع @Id لإنشاء الأرقام التلقائية.

ال @GeneratedValue يخبر التعليقات التوضيحية فقط Hibernate أن قاعدة البيانات تولد هذه القيمة نفسها.

الحل (أو العمل) المقترح في هذا المنتدى هو إنشاء كيان منفصل مع معرف تم إنشاؤه ، شيء من هذا القبيل:

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

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

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

نصائح أخرى

لقد وجدت ذلك @Column(columnDefinition="serial") يعمل بشكل مثالي ولكن فقط ل postgresql. بالنسبة لي كان هذا الحل الأمثل ، لأن الكيان الثاني هو الخيار "القبيح".

أعلم أن هذا سؤال قديم للغاية ، لكنه تم عرضه أولاً على النتائج وتغير JPA كثيرًا منذ السؤال.

الطريقة الصحيحة للقيام بذلك الآن هي مع @Generated حاشية. ملاحظة. يمكنك تحديد التسلسل ، وتعيين الافتراضي في العمود على هذا التسلسل ، ثم قم بتخطيط العمود على النحو التالي:

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

السباتات بالتأكيد تدعم هذا. من المستندات:

"الخصائص التي تم إنشاؤها هي الخصائص التي يتم إنشاؤها قيمها بواسطة قاعدة البيانات. عادةً ما تكون تطبيقات السبات اللازمة لتحديث الكائنات التي تحتوي على أي خصائص كانت قاعدة البيانات توليد قيم. في الأساس ، عندما يصدر السبات ، يقوم SQL بإدراج أو تحديث لكيان حدد خصائص تم إنشاؤها ، فهي تصدر على الفور تحديدًا بعد ذلك لاسترداد القيم التي تم إنشاؤها. "

بالنسبة للخصائص التي تم إنشاؤها على إدراج فقط ، سيبدو تعيين الممتلكات الخاص بك (.hbm.xml):

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

بالنسبة للخصائص التي تم إنشاؤها على إدراج وتحديث رسم خرائط الخاص بك (.hbm.xml) سيبدو:

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

لسوء الحظ ، لا أعرف JPA ، لذلك لا أعرف ما إذا كانت هذه الميزة مكشوفة عبر JPA (أظن أنه ربما لا)

بدلاً من ذلك ، يجب أن تكون قادرًا على استبعاد العقار من الإدراج والتحديثات ، ثم "يدوي" جلسة الاتصال. RERFRESH (OBJ) ؛ بعد إدراجها/تحديثها لتحميل القيمة التي تم إنشاؤها من قاعدة البيانات.

هذه هي الطريقة التي تستبعد بها العقار من استخدامها في عبارات إدراج وتحديث:

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

على الرغم من أن هذا موضوع قديم ، إلا أنني أريد مشاركة الحل الخاص بي ونأمل الحصول على بعض التعليقات حول هذا الموضوع. كن حذرًا من أنني اختبرت هذا الحل فقط مع قاعدة البيانات المحلية الخاصة بي في بعض testcase. لذلك هذه ليست ميزة مثمرة حتى الآن.

لقد حللت هذه المشكلة عن طريق تقديم تعليق توضيحي مخصص يسمى التسلسل بدون خاصية. إنها مجرد علامة للحقول التي يجب تعيين قيمة من تسلسل متزايد.

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

كما ترون من الكود أعلاه ، استخدم المستمع مثيل تسلسل واحد لكل فئة كيان ويحتفظ ببعض أرقام التسلسل المحددة بواسطة زيادة القيمة لكيان التسلسل. إذا كان ينفد من أرقام التسلسل ، فإنه يقوم بتحميل كيان التسلسل للفئة المستهدفة ويحتفظ بقيم زيادة القيمة للمكالمات التالية. وبهذه الطريقة لا أحتاج إلى الاستعلام عن قاعدة البيانات في كل مرة يلزم وجود قيمة تسلسل. لاحظ أن Sathelsessession الذي يتم فتحه لحفظ المجموعة التالية من أرقام التسلسل. لا يمكنك استخدام نفس الجلسة ، يتم استمرار الكيان المستهدف في الوقت الحالي لأن هذا سيؤدي إلى وجود توافق في التحميل في entitypersister.

أمل أن هذا يساعد شخصاما.

لقد أصلحت توليد UUID (أو التسلسلات) باستخدام السبات باستخدام @PrePersist حاشية. ملاحظة:

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

أركض في نفس الموقف مثلك ولم أجد أي إجابات جادة إذا كان من الممكن أساسًا إنشاء خاصية غير ID باستخدام JPA أم لا.

الحل الخاص بي هو استدعاء التسلسل باستخدام استعلام JPA الأصلي لتعيين العقار يدويًا قبل أن يبرزه.

هذا ليس مرضيًا ولكنه يعمل بمثابة حل بديل في الوقت الحالي.

ماريو

لقد وجدت هذه الملاحظة المحددة في الجلسة 9.1.9 التعليق التوضيحي المولد من مواصفات JPA: [43] يجب ألا تستخدم التطبيقات المحمولة التعليق التوضيحي المولد في الحقول أو الخصائص المستمرة الأخرى." لذلك ، أفترض أنه لا يمكن إنشاء قيمة تلقائيًا لقيم المفتاح غير الأساسي على الأقل باستخدام JPA ببساطة.

إذا كنت تستخدم postgresql
وأنا أستخدم في Spring Boot 1.5.6

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

"لا أريد استخدام الزناد أو أي شيء آخر غير السبات نفسه لتوليد القيمة لممتلكاتي"

في هذه الحالة ، ماذا عن إنشاء تنفيذ من نوع المستخدم الذي يولد القيمة المطلوبة ، وتكوين البيانات الوصفية لاستخدام نوع المستخدم هذا لاستمرار خاصية MySequenceVal؟

هذا ليس هو نفسه استخدام تسلسل. عند استخدام تسلسل ، لا تقوم بإدخال أو تحديث أي شيء. أنت ببساطة تسترجع قيمة التسلسل التالية. يبدو أن السبات لا يدعمه.

يبدو أن الخيط قديم ، أردت فقط إضافة الحل الخاص بي هنا (باستخدام SideJ - AOP في الربيع).

الحل هو إنشاء شرح مخصص @InjectSequenceValue كالآتي.

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

الآن يمكنك التعليق على أي مجال في الكيان ، بحيث يتم حقن قيمة الحقل الأساسي (الطويل/الصحيح) في وقت التشغيل باستخدام القيمة التالية للتسلسل.

تعليقات مثل هذا.

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

حتى الآن قمنا بتمييز الحقل الذي نحتاجه إلى ضخ قيمة التسلسل. لذلك سننظر في كيفية حقن قيمة التسلسل للحقول المحددة ، ويتم ذلك عن طريق إنشاء النقطة المقطوعة في الجانب.

سنقوم بإعداد الحقن قبل save/persist يتم تنفيذ الطريقة. يتم ذلك في الفصل أدناه.

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

الآن يمكنك التعليق على أي كيان على النحو التالي.

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

لقد كنت في وضع مثلك (تسلسل JPA/Hibernate للحقل غير المعياري) وانتهى بي الأمر بإنشاء مشغل في مخطط DB الخاص بي يضيف رقم تسلسل فريد على الإدراج. أنا فقط لم أحصل على العمل مع JPA/Hibernate

بعد قضاء ساعات ، ساعدني هذا بدقة على حل مشكلتي:

لـ Oracle 12C:

ID NUMBER GENERATED as IDENTITY

لـ H2:

ID BIGINT GENERATED as auto_increment

أيضا جعل:

@Column(insertable = false)
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top