java - التعداد خريطة في JPA مع قيم ثابتة؟





spring orm enums (7)


أفضل طريقة هي تعيين معرِّف فريد لكل نوع التعداد ، وبالتالي تجنب عثرات ORDINAL و STRING. شاهد هذه post التي تحدد 5 طرق يمكنك من خلالها رسم التعداد.

مأخوذ من الرابط أعلاه:

1 و 2. باستخدامEnumerated

توجد حاليًا طريقتان يمكنك من خلالهما تعيين التعدادات ضمن كيانات JPA باستخدام التعليق التوضيحيEnumerated. للأسف ، يكون لكل من EnumType.STRING و EnumType.ORDINAL قيودهما.

إذا كنت تستخدم EnumType.String ، فإن إعادة تسمية أحد أنواع التعداد سوف يجعل قيمة التعداد غير متزامنة مع القيم المحفوظة في قاعدة البيانات. إذا كنت تستخدم EnumType.ORDINAL ، فسوف يؤدي حذف أو إعادة ترتيب الأنواع داخل التعداد إلى تعيين القيم المحفوظة في قاعدة البيانات إلى أنواع التعدادات الخاطئة.

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

3. رد فعل دورة حياة

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

4. تعيين معرف فريد لكل نوع التعداد

الحل المفضل هو تعيين التعداد إلى قيمة ثابتة ، أو معرّف ، معرّف ضمن التعداد. يجعل التعيين إلى قيمة ثابتة محددة مسبقًا شفرتك أكثر قوة. أي تعديل في ترتيب أنواع التعدادات ، أو إعادة بيع الأسماء ، لن يسبب أي آثار سلبية.

5. باستخدام جافا EE7Convert

إذا كنت تستخدم JPA 2.1 ، فلديك خيار استخدام التعليق الجديدConvert. يتطلب ذلك إنشاء فئة محولة ، مشروحة بـConverter ، والتي من خلالها يمكنك تحديد القيم التي يتم حفظها في قاعدة البيانات لكل نوع التعداد. داخل الكيان الخاص بك ، يمكنك إضافة تعليق توضيحي معConvert.

تفضلي: (رقم 4)

السبب في تفضيل تعريف المعرّف داخل التعداد كمعارضة استخدام المحول ، هو تغليف جيد. يجب أن يعرف نوع التعداد فقط معرفه ، ويجب أن يعرف الكيان فقط كيفية تعيين التعداد لقاعدة البيانات.

راجع post الأصلية لمثال الكود.

أنا أبحث عن طرق مختلفة لتخطيط التعداد باستخدام JPA. أرغب بشكل خاص في تعيين القيمة الصحيحة لكل إدخال تعداد ولحفظ قيمة الأعداد الصحيحة فقط.

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

  public enum Right {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      Right(int value) { this.value = value; }

      public int getValue() { return value; }
  };

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;

  // the enum to map : 
  private Right right;
}

الحل البسيط هو استخدام التعليق التوضيحي مع EnumType.ORDINAL:

@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;

ولكن في هذه الحالة ، تقوم JPA بتعيين فهرس التعداد (0،1،2) وليس القيمة التي أريدها (100،200،300).

لا يبدو أن اثنين من الحلول التي وجدتها بسيطة ...

الحل الأول

يستخدم الحل المقترح هنا ،PrePersist وPostLoad لتحويل التعداد إلى حقل آخر ووضع علامة على حقل التعداد كنقل مؤقت:

@Basic
private int intValueForAnEnum;

@PrePersist
void populateDBFields() {
  intValueForAnEnum = right.getValue();
}

@PostLoad
void populateTransientFields() {
  right = Right.valueOf(intValueForAnEnum);
}

الحل الثاني

اقترح الحل الثاني المقترح هنا كائن تحويل عام ، ولكن لا يزال يبدو ثقيلًا وموجهاً إلى السبات (يبدو أنType غير موجود في Java EE):

@Type(
    type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
    parameters = {
            @Parameter(
                name  = "enumClass",                      
                value = "Authority$Right"),
            @Parameter(
                name  = "identifierMethod",
                value = "toInt"),
            @Parameter(
                name  = "valueOfMethod",
                value = "fromInt")
            }
)

هل هناك حلول أخرى؟

لدي العديد من الأفكار في أذهاننا ولكن لا أعرف ما إذا كانت موجودة في JPA:

  • استخدام أساليب setter و getter للعضو الصحيح في فئة Authority عند تحميل كائن Authority وحفظه
  • فكرة مماثلة هي أن نقول JPA ما هي طرق التعداد الصحيح لتحويل التعداد إلى int و int enum
  • لأنني أستخدم Spring ، هل هناك أي طريقة لإخبار JPA باستخدام محول محدد (RightEditor)؟






بالنسبة للإصدارات الأقدم من JPA 2.1 ، توفر JPA طريقتين للتعامل مع التعدادات ، من خلال أسمائهم أو من خلال ordinal . ولا تدعم JPA القياسية الأنواع المخصصة. وبالتالي:

  • إذا كنت تريد إجراء تحويلات من النوع المخصص ، فسيتعين عليك استخدام ملحق موفر (مع Hibernate UserType ، EclipseLink Converter ، إلخ). (الحل الثاني). ~ أو ~
  • سيكون عليك استخدامPrePersist و @ PostLoad خدعة (الحل الأول). ~ أو ~
  • علق غاتر واضعة أخذ وإرجاع قيمة int ~ أو ~
  • استخدم خاصية عدد صحيح على مستوى الكيان وقم بإجراء ترجمة في getters and setters.

سوف أوضح الخيار الأخير (هذا هو التنفيذ الأساسي ، قم بتعديله كما هو مطلوب):

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

    public enum Right {
        READ(100), WRITE(200), EDITOR (300);

        private int value;

        Right(int value) { this.value = value; }    

        public int getValue() { return value; }

        public static Right parse(int id) {
            Right right = null; // Default
            for (Right item : Right.values()) {
                if (item.getValue()==id) {
                    right = item;
                    break;
                }
            }
            return right;
        }

    };

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "AUTHORITY_ID")
    private Long id;

    @Column(name = "RIGHT_ID")
    private int rightId;

    public Right getRight () {
        return Right.parse(this.rightId);
    }

    public void setRight(Right right) {
        this.rightId = right.getValue();
    }

}



من JPA 2.1 يمكنك استخدام AttributeConverter .

إنشاء فئة تعداد مثل هكذا:

public enum NodeType {

    ROOT("root-node"),
    BRANCH("branch-node"),
    LEAF("leaf-node");

    private final String code;

    private NodeType(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

وقم بإنشاء محول مثل هذا:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class NodeTypeConverter implements AttributeConverter<NodeType, String> {

    @Override
    public String convertToDatabaseColumn(NodeType nodeType) {
        return nodeType.getCode();
    }

    @Override
    public NodeType convertToEntityAttribute(String dbData) {
        for (NodeType nodeType : values()) {
            if (nodeType.getCode().equals(dbData)) {
                return nodeType;
            }
        }

        throw new IllegalArgumentException("Unknown database value:" + dbData);
    }
}

على الكيان الذي تحتاجه فقط:

@Column(name = "node_type_code")

أنت محظوظ مع @Converter(autoApply = true) قد يختلف حسب الحاوية ولكن تم اختباره للعمل على Wildfly 8.1.0. إذا لم تنجح ، يمكنك إضافة @Convert(converter = NodeTypeConverter.class) على عمود فئة الكيان.




سأفعل ما يلي:

أعلن عن التعداد ، في الملف الخاص به:

public enum RightEnum {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      private RightEnum (int value) { this.value = value; }


      @Override
      public static Etapa valueOf(Integer value){
           for( RightEnum r : RightEnum .values() ){
              if ( r.getValue().equals(value))
                 return r;
           }
           return null;//or throw exception
     }

      public int getValue() { return value; }


}

قم بتعريف كيان JPA جديد يدعى Right

@Entity
public class Right{
    @Id
    private Integer id;
    //FIElDS

    // constructor
    public Right(RightEnum rightEnum){
          this.id = rightEnum.getValue();
    }

    public Right getInstance(RightEnum rightEnum){
          return new Right(rightEnum);
    }


}

سوف تحتاج أيضا إلى محول لاستقبال هذه القيم (JPA 2.1 فقط ، وهناك مشكلة لا أقوم بمناقشتها هنا مع هذه الأعداد بحيث يمكن الاستمرار فيها مباشرة باستخدام المحول ، لذلك سيكون الطريق ذو اتجاه واحد فقط)

import mypackage.RightEnum;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

/**
 * 
 * 
 */
@Converter(autoApply = true)
public class RightEnumConverter implements AttributeConverter<RightEnum, Integer>{

    @Override //this method shoudn´t be used, but I implemented anyway, just in case
    public Integer convertToDatabaseColumn(RightEnum attribute) {
        return attribute.getValue();
    }

    @Override
    public RightEnum convertToEntityAttribute(Integer dbData) {
        return RightEnum.valueOf(dbData);
    }

}

هيئة الهيئة:

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {


  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;

  // the **Entity** to map : 
  private Right right;

  // the **Enum** to map (not to be persisted or updated) : 
  @Column(name="COLUMN1", insertable = false, updatable = false)
  @Convert(converter = RightEnumConverter.class)
  private RightEnum rightEnum;

}

بهذه الطريقة ، لا يمكنك التعيين مباشرة على حقل التعداد. ومع ذلك ، يمكنك تعيين الحقل "حق" في "السلطة" باستخدام

autorithy.setRight( Right.getInstance( RightEnum.READ ) );//for example

وإذا كنت بحاجة إلى المقارنة ، فيمكنك استخدام:

authority.getRight().equals( RightEnum.READ ); //for example

وهو أمر رائع ، على ما أعتقد. انها ليست صحيحة تماما ، لأن المحول لا يقصد استخدامه مع enum´s. في الواقع ، تنص الوثائق على عدم استخدامها أبدًا لهذا الغرض ، يجب عليك استخدام التعليق التوضيحيEnumerated بدلاً من ذلك. المشكلة هي أن هناك نوعين فقط من التعداد: ORDINAL أو STRING ، ولكن ORDINAL خادع وغير آمن.

ومع ذلك ، إذا لم يرضيك ، يمكنك القيام بشيء أكثر ذكاءًا وبساطة (أو لا).

دعنا نرى.

RightEnum:

public enum RightEnum {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      private RightEnum (int value) { 
            try {
                  this.value= value;
                  final Field field = this.getClass().getSuperclass().getDeclaredField("ordinal");
                  field.setAccessible(true);
                  field.set(this, value);
             } catch (Exception e) {//or use more multicatch if you use JDK 1.7+
                  throw new RuntimeException(e);
            }
      }


      @Override
      public static Etapa valueOf(Integer value){
           for( RightEnum r : RightEnum .values() ){
              if ( r.getValue().equals(value))
                 return r;
           }
           return null;//or throw exception
     }

      public int getValue() { return value; }


}

والهيئة السلطة

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {


  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;


  // the **Enum** to map (to be persisted or updated) : 
  @Column(name="COLUMN1")
  @Enumerated(EnumType.ORDINAL)
  private RightEnum rightEnum;

}

في هذه الفكرة الثانية ، ليس الوضع مثاليًا منذ أن اخترقنا السمة الترتيبية ، ولكنه أصغر بكثير من الترميز.

أعتقد أن مواصفات JPA يجب أن تتضمن EnumType.ID حيث يجب أن يكون حقل قيمة التعداد مشبوتًا بنوع من التعليقات التوضيحية لـEnumId.




المشكلة هي ، كما أعتقد ، أن JPA لم يتم إدراكها أبداً مع الفكرة القائلة بأنه يمكن أن يكون لدينا مخطط موجود مسبقاً موجود بالفعل.

أعتقد أن هناك نوعين من أوجه القصور الرئيسية الناتجة عن ذلك ، خاصة في Enum:

  1. قيود استخدام name () و ordinal (). لماذا لا تضع علامة فقط على getter بـId ، كما نفعل معEntity؟
  2. يوجد تمثيل Enum عادة في قاعدة البيانات للسماح بالارتباط مع جميع أنواع البيانات الوصفية ، بما في ذلك الاسم الصحيح ، الاسم الوصفي ، ربما شيء مع التعريب وما إلى ذلك. نحتاج إلى سهولة استخدام التعداد جنبًا إلى جنب مع مرونة الكيان.

مساعدة قضيتي والتصويت على JPA_SPEC-47

هل هذا لن يكون أكثر أناقة من استخدامConverter لحل المشكلة؟

// Note: this code won't work!!
// it is just a sample of how I *would* want it to work!
@Enumerated
public enum Language {
  ENGLISH_US("en-US"),
  ENGLISH_BRITISH("en-BR"),
  FRENCH("fr"),
  FRENCH_CANADIAN("fr-CA");
  @ID
  private String code;
  @Column(name="DESCRIPTION")
  private String description;

  Language(String code) {
    this.code = code;
  }

  public String getCode() {
    return code;
  }

  public String getDescription() {
    return description;
  }
}



هذا يوزع الأعداد الصحيحة أو الأوتار على التعداد المستهدف مع المطابقة الجزئية في dot.NET 4.0 باستخدام مواد بديلة مثل في فئة الأداة المساعدة Tawani أعلاه. أنا أستخدمه لتحويل متغيرات تبديل سطر الأوامر التي قد تكون غير مكتملة. نظرًا لأن التعداد لا يمكن أن يكون خاليًا ، فيجب تقديم قيمة افتراضية منطقية. يمكن أن يطلق عليه هذا:

var result = EnumParser<MyEnum>.Parse(valueToParse, MyEnum.FirstValue);

هنا الرمز:

using System;

public class EnumParser<T> where T : struct
{
    public static T Parse(int toParse, T defaultVal)
    {
        return Parse(toParse + "", defaultVal);
    }
    public static T Parse(string toParse, T defaultVal) 
    {
        T enumVal = defaultVal;
        if (defaultVal is Enum && !String.IsNullOrEmpty(toParse))
        {
            int index;
            if (int.TryParse(toParse, out index))
            {
                Enum.TryParse(index + "", out enumVal);
            }
            else
            {
                if (!Enum.TryParse<T>(toParse + "", true, out enumVal))
                {
                    MatchPartialName(toParse, ref enumVal);
                }
            }
        }
        return enumVal;
    }

    public static void MatchPartialName(string toParse, ref T enumVal)
    {
        foreach (string member in enumVal.GetType().GetEnumNames())
        {
            if (member.ToLower().Contains(toParse.ToLower()))
            {
                if (Enum.TryParse<T>(member + "", out enumVal))
                {
                    break;
                }
            }
        }
    }
}

لمعلوماتك: كان السؤال عن الأعداد الصحيحة ، والتي لم يذكرها أحد سيتم أيضا تحويل صريح في Enum.TryParse ()







java spring orm jpa enums