【问题标题】:How to use enums with JPA如何在 JPA 中使用枚举
【发布时间】:2010-09-26 00:56:36
【问题描述】:

我有一个电影租赁系统的现有数据库。每部电影都有一个评级属性。在 SQL 中,他们使用约束来限制该属性的允许值。

CONSTRAINT film_rating_check CHECK 
    ((((((((rating)::text = ''::text) OR 
          ((rating)::text = 'G'::text)) OR 
          ((rating)::text = 'PG'::text)) OR 
          ((rating)::text = 'PG-13'::text)) OR 
          ((rating)::text = 'R'::text)) OR 
          ((rating)::text = 'NC-17'::text)))

我认为使用 Java 枚举将约束映射到对象世界会很好。但是由于“PG-13”和“NC-17”中的特殊字符,不可能简单地采用允许的值。所以我实现了以下枚举:

public enum Rating {

    UNRATED ( "" ),
    G ( "G" ), 
    PG ( "PG" ),
    PG13 ( "PG-13" ),
    R ( "R" ),
    NC17 ( "NC-17" );

    private String rating;

    private Rating(String rating) {
        this.rating = rating;
    }

    @Override
    public String toString() {
        return rating;
    }
}

@Entity
public class Film {
    ..
    @Enumerated(EnumType.STRING)
    private Rating rating;
    ..

使用 toString() 方法,方向 enum -> String 工作正常,但 String -> enum 不起作用。我得到以下异常:

[TopLink 警告]: 2008.12.09 01:30:57.434--ServerSession(4729123)--异常 [TOPLINK-116] (Oracle TopLink Essentials - 2.0.1(构建 b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.DescriptorException 异常 说明:没有为中的值 [NC-17] 提供转换值 字段 [FILM.RATING]。映射: oracle.toplink.essentials.mappings.DirectToFieldMapping[rating-->FILM.RATING] 描述符:RelationalDescriptor(de.fhw.nsdb.entities.Film --> [数据库表(FILM)])

干杯

提莫

【问题讨论】:

  • 您是否有意省略了该字段的@Column 属性?没有它,就很难坚持下去……
  • 您需要一个枚举内部枚举的静态哈希图,以及一个静态方法“getByRating”

标签: java jpa enums toplink


【解决方案1】:

我不知道 toplink 的内部结构,但我有根据的猜测如下:它使用 Rating.valueOf(String s) 方法映射到另一个方向。无法覆盖 valueOf(),因此您必须遵守 java 的命名约定,以允许正确的 valueOf 方法。

public enum Rating {

    UNRATED,
    G, 
    PG,
    PG_13 ,
    R ,
    NC_17 ;

    public String getRating() {
        return name().replace("_","-");;
    }
}

getRating 产生“人类可读”的评级。请注意,枚举标识符中不允许使用“-”字符。

当然,您必须将数据库中的值存储为 NC_17。

【讨论】:

    【解决方案2】:

    这个呢

    public String getRating{  
       return rating.toString();
    }
    
    pubic void setRating(String rating){  
       //parse rating string to rating enum
       //JPA will use this getter to set the values when getting data from DB   
    }  
    
    @Transient  
    public Rating getRatingValue(){  
       return rating;
    }
    
    @Transient  
    public Rating setRatingValue(Rating rating){  
       this.rating = rating;
    }
    

    这样,您可以在数据库和实体上使用评级作为字符串,但将枚举用于其他所有内容。

    【讨论】:

      【解决方案3】:
      public enum Rating {
      
          UNRATED ( "" ),
          G ( "G" ), 
          PG ( "PG" ),
          PG13 ( "PG-13" ),
          R ( "R" ),
          NC17 ( "NC-17" );
      
          private String rating;
      
          private static Map<String, Rating> ratings = new HashMap<String, Rating>();
          static {
              for (Rating r : EnumSet.allOf(Rating.class)) {
                  ratings.put(r.toString(), r);
              }
          }
      
          private static Rating getRating(String rating) {
              return ratings.get(rating);
          }
      
          private Rating(String rating) {
              this.rating = rating;
          }
      
          @Override
          public String toString() {
              return rating;
          }
      }
      

      但是,我不知道如何在带注释的 TopLink 方面进行映射。

      【讨论】:

        【解决方案4】:

        听起来您需要添加对自定义类型的支持:

        Extending OracleAS TopLink to Support Custom Type Conversions

        【讨论】:

          【解决方案5】:

          这里有一个问题,那就是 JPA 在处理枚举方面的能力有限。使用枚举,您有两种选择:

          1. 将它们存储为等于Enum.ordinal() 的数字,这是一个糟糕的主意(恕我直言);或
          2. 将它们存储为等于Enum.name() 的字符串。 注意: 不是您所期望的toString(),尤其是因为Enum.toString() 的默认行为是返回name()

          我个人认为最好的选择是(2)。

          现在您遇到了一个问题,即您定义的值不代表 Java 中的有效实例名称(即使用连字符)。所以你的选择是:

          • 更改您的数据;
          • 保留字符串字段并隐式将它们转换为对象中的枚举或从枚举转换;或
          • 使用非标准扩展,例如 TypeConverters。

          我会按照优先顺序(从第一个到最后一个)进行操作。

          有人建议使用 Oracle TopLink 的转换器,但您可能正在使用 Toplink Essentials,它是参考 JPA 1.0 实现,它是商业 Oracle Toplink 产品的子集。

          作为另一个建议,我强烈建议切换到EclipseLink。它是一个比 Toplink Essentials 更完整的实现,Eclipselink 将成为 JPA 2.0 发布时的参考实现(预计明年年中 JavaOne 发布)。

          【讨论】:

            【解决方案6】:

            您是否尝试过存储序数值。如果您没有与值关联的字符串,则存储字符串值可以正常工作:

            @Enumerated(EnumType.ORDINAL)
            

            【讨论】:

            • 考虑到@Enumerated 是part of JPA1,我不敢相信这没有更多的赞成票。不过,就我个人而言,我认为 EnumType.STRING 会更好,原因是 cletus 说的。
            • @electrotype 是的,从技术上讲它有效,但远非最佳实践。通过数字存储枚举是一个可怕的想法。不会改变代码功能的非常基本的更改很容易使所有持久数据无效。例如,重新排列枚举值的顺序或在列表的开头添加一个新值。
            • @spaarky21 在大多数情况下我同意这一点,但是,存储永远不会改变的值,比如工作日,你也可以存储序数(并将其作为 TINYINT(1) 保存,以防万一工作日)。
            • @JelleBlaauw 我会在任何地方都这样做,就像约定一样。即使是星期几也可能有问题。一周只有 7 天,但不同的图书馆对待它们的方式不同。主要区别在于星期是从星期日开始(例如,Java 日历)还是星期一(例如,Java 8 DayOfWeek),以及值是从 0 开始(例如,枚举序数)还是从 1 开始(例如,Joda 常量。 ) 想象一下,您将项目从一个库更改为另一个库,一位善意的开发人员重新排序您的 DayOfWeek 枚举值以匹配。
            【解决方案7】:

            使用这个注释

            @Column(columnDefinition="ENUM('User', 'Admin')")
            

            【讨论】:

            • 我很确定这行不通。告诉 JPA 如何定义列不会指示它以不同的方式查找数据库的值。在 Hibernate 中,提供列定义有助于解决类型不匹配问题 - 例如,如果 Hibernate 认为它应该对列使用一种类型的 blob,但数据库使用另一种类型。
            【解决方案8】:

            解决了!!! 我在哪里找到答案:http://programming.itags.org/development-tools/65254/

            简而言之,转换查找枚举的名称,而不是属性“rating”的值。 在你的情况下:如果你有 db 值“NC-17”,你需要在你的枚举中:

            枚举评级 {
            (...)
            NC-17 ( "NC-17" );
            (...)

            【讨论】:

            • 投反对票,因为 - 不允许在枚举名称中使用,如 OP 所述。
            【解决方案9】:

            我认为,问题在于,JPA 从来没有考虑到我们可以拥有一个复杂的预先存在的模式。

            我认为这导致了 Enum 特有的两个主要缺点:

            1. 使用 name() 和 ordinal() 的限制。为什么不像我们使用@Entity 那样用@Id 标记getter?
            2. 枚举通常在数据库中有表示,以允许与各种元数据关联,包括专有名称、描述性名称,可能还有本地化等。我们需要枚举的易用性和实体的灵活性.

            帮助我的事业并为JPA_SPEC-47投票

            【讨论】:

              【解决方案10】:

              枚举 公共枚举 ParentalControlLevelsEnum { U("U"), PG("PG"), _12("12"), _15("15"), _18("18");

              private final String value;
              
              ParentalControlLevelsEnum(final String value) {
                  this.value = value;
              }
              
              public String getValue() {
                  return value;
              }
              
              public static ParentalControlLevelsEnum fromString(final String value) {
                  for (ParentalControlLevelsEnum level : ParentalControlLevelsEnum.values()) {
                      if (level.getValue().equalsIgnoreCase(value)) {
                          return level;
                      }
                  }
                  return null;
              }
              

              }

              比较 -> 枚举

              公共类 RatingComparator 实现 Comparator {

              public int compare(final ParentalControlLevelsEnum o1, final ParentalControlLevelsEnum o2) {
                  if (o1.ordinal() < o2.ordinal()) {
                      return -1;
                  } else {
                      return 1;
                  }
              }
              

              }

              【讨论】:

                【解决方案11】:

                使用您现有的enum Rating。你可以使用AttributeCoverters。

                @Converter(autoApply = true)
                public class RatingConverter implements AttributeConverter<Rating, String> {
                
                    @Override
                    public String convertToDatabaseColumn(Rating rating) {
                        if (rating == null) {
                            return null;
                        }
                        return rating.toString();
                    }
                
                    @Override
                    public Rating convertToEntityAttribute(String code) {
                        if (code == null) {
                            return null;
                        }
                
                        return Stream.of(Rating.values())
                          .filter(c -> c.toString().equals(code))
                          .findFirst()
                          .orElseThrow(IllegalArgumentException::new);
                    }
                }
                

                【讨论】:

                  猜你喜欢
                  • 2013-07-19
                  • 2018-04-11
                  • 2019-10-09
                  • 1970-01-01
                  • 2019-05-29
                  • 2012-08-22
                  • 2023-03-20
                  • 2016-11-10
                  • 2014-07-31
                  相关资源
                  最近更新 更多