【问题标题】:Map some boolean properties as enum Set in Hibernate将一些布尔属性映射为 Hibernate 中的枚举集
【发布时间】:2013-09-07 21:11:36
【问题描述】:

我有一个实体,它在数据库中有一些 BIT 字段:

  • 可编辑
  • needs_review
  • 有效

这些字段使用 Hibernate 3.6.9 版本映射到其 Java 类中的 boolean 字段。这迫使我为我想要获取的每个实体列表编写一个接口方法:

List<Entity> listEditables();
List<Entity> listReviewNeeded();
List<Entity> listActives();

或者写一个通用的接口方法来实现它们的组合:

List<Entity> listEntities(boolean editables, boolean reviewNeeded, boolean actives);

第二个选择看起来更好,但如果我将来添加另一个字段,则需要修改接口本身(以及与之耦合的每一行代码)。

所以我决定可以将其表示为枚举Set

public enum EntityType{
    EDITABLE, REVIEW_NEEDED, ACTIVE
}

//That way there's no need to change interface method's signature
List<Entity> listEntities(Set<EntityType> requiredTypes);

作为一个枚举匹配我想要实现的目标是有道理的,Entity 类型本身应该有自己的Set&lt;EntityType&gt;

public class Entity{
    Set<EntityType> entityTypes;
}

但是,我有映射的布尔值,它在逻辑上匹配 Set。然后我的问题是,有什么方法可以根据 BIT 字段在休眠中映射Set&lt;EntityType&gt; entityTypes,还是我必须自己管理该逻辑,将它们作为boolean

更新

将它们映射为Set 意味着可以使用in 子句查询列表,如果不是,则意味着在我的控制器和模型代码之间进行转换需要额外的步骤。

Set<EntityType> typesSet = Sets.newHashSet(EntityType.EDITABLE, EntityType.REVIEW_NEEDED);
//Obtains a list of every single entity which is EDITABLE or REVIEW_NEEDED
session.createCriteria(Entity.class).addRestriction(Restrictions.in("entityTypes",typeSet)).list();

【问题讨论】:

    标签: java hibernate


    【解决方案1】:

    我想我有一个适合你的解决方案。您感兴趣的是 CompositeUserType。

    作为示例,让我们使用我最近编写的 InetAddress 复合用户类型将 128 位 IPv6 地址/IPv4Address 对象映射到用户帐户实体内的两个 64 位长属性。

    signupIp:InetAddress 使用以下方法映射到两列(没有列数限制等):

        @Columns(columns = {@Column(name = "ip_low", nullable = true), @Column(name = "ip_high", nullable = true)})
        private InetAddress signupIp;
    

    实现的有趣部分如下所示:

    public class InetAddressUserType implements CompositeUserType {
    @Override
    public String[] getPropertyNames() {
        return new String [] {"ipLow", "ipHigh"};
    }
    
    @Override
    public Type[] getPropertyTypes() {
        return new Type [] { LongType.INSTANCE, LongType.INSTANCE};
    }
    
    @Override
    public Object getPropertyValue(Object component, int property) throws HibernateException {
        if(component != null)
            return toLong((InetAddress)component)[property];
        else
            return null;
    }
    
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index,
            SessionImplementor session) throws HibernateException, SQLException {
    
        if(value != null) {
            long [] longs = toLong((InetAddress)value);
            st.setLong(index, longs[0]);
            st.setLong(index + 1, longs[1]);
        }
        else {
            st.setNull(index, LongType.INSTANCE.sqlType());
            st.setNull(index + 1, LongType.INSTANCE.sqlType());
        }
    }
    
    @Override
    public void setPropertyValue(Object component, int property, Object value)
            throws HibernateException {
        throw new RuntimeException("This object is immutable");
    }
    
    @Override
    public Class<?> returnedClass() {
        return InetAddress.class;
    }
    
    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return x != null ? x.equals(y) : null == y;
    }
    
    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }
    
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names,
            SessionImplementor session, Object owner)
            throws HibernateException, SQLException {
        Long ipLow = rs.getLong(names[0]);
        if(!rs.wasNull()) {
            Long ipHigh = rs.getLong(names[1]);
    
            try {
                return fromLong(new long [] {ipLow, ipHigh});
            } catch (UnknownHostException e) {
                throw new HibernateException("Failed to get InetAddress: ip = " + ipHigh + " + " + ipLow, e);
            }
        }
        else
            return null;
    }
    
    @Override
    public Object deepCopy(Object value) throws HibernateException {
        if(value != null)
            try {
                return InetAddress.getByAddress(((InetAddress)value).getAddress());
            } catch (UnknownHostException e) {
                throw new RuntimeException("Impossible Exception: " + e.getMessage(), e);
            }
        else
            return null;
    }
    
    @Override
    public boolean isMutable() {
        return false;
    }
        ...
     }
    

    请注意,我根据 ipLow 和 ipHigh 的值在 Inet4Address 和 Inet6Address 实例之间灵活切换。复合被标记为不可变,您需要检查文档和 Hibernate 源代码中的示例(内置复合用户类型)。

    以类似的方式,您可以映射有意义的位属性。您可以使用引用您的 EnumType 的单个 Restriction.eq 来查询这些位。您可以使用 equals 方法检查属性对象。如果你需要引用一个特殊的映射位,你可以使用像 signupIp.ipLow 中的点符号来引用 ipLow 属性/列。

    我猜这就是你要找的。​​p>

    更新:

    最后归结为定义属性的正确顺序。 Hibernate 将始终使用整数索引值来访问每个属性:

    //immutable for simplicity
    class Status {
         private final boolean editable;
         private final boolean needsReview;
         private final boolean active;
         //... constructor + isEditable etc..
    }
    

    在您的 StatusCompositeType 类中:

    public String[] getPropertyNames() {
      return new String [] {"editable", "needsReview", "active"};
    }
    
    public Type[] getPropertyTypes() {
      return new Type [] { BooleanType.INSTANCE, LongType.INSTANCE};
    }
    
    public Object getPropertyValue(Object component, int property) throws HibernateException {
    if(component != null) {
       Status status = (Status)component;
       switch(property) {
       case 1: return status.isEditable();
       case 2: return status.isReviewNeeded();
       case 3: return status.isActive();
       default: throw new IllegalArgumentException();
       }
    }
    else
        return null; //all columns can be set to null if you allow a entity to have a null status.
    }
    
    
    public void nullSafeSet(PreparedStatement st, Object value, int index,
        SessionImplementor session) throws HibernateException, SQLException {
    
      if(value != null) {
        Status status = (Status)value;
        st.setBoolean(index, status.isEditable());
        st.setBoolean(index + 1, status.isReviewNeeded());
        st.setBoolean(index + 2, status.isActive());
      }
      else {
        st.setNull(index, BooleanType.INSTANCE.sqlType());
        st.setNull(index + 1, BooleanType.INSTANCE.sqlType());
        st.setNull(index + 2, BooleanType.INSTANCE.sqlType());
      }
    }
    
    public Object nullSafeGet(ResultSet rs, String[] names,
        SessionImplementor session, Object owner)
        throws HibernateException, SQLException {
      Boolean isEditable = rs.getBoolean(names[0]);
      if(!rs.wasNull()) {
        Boolean isReviewNeeded = rs.getBoolean(names[1]);
        Boolean isActive = rs.getBoolean(names[2]);
    
        return new Status(isEditable, isReviewNeeded, isActive);
      }
      else
        return null;
    }
    

    其余的都是直截了当的。请记住为用户类型实现 equals 和 hashcode,并在创建 sessionFactory 之前将类型添加到配置中。

    一切就绪后,您可以创建条件搜索并使用:

    //search for any elements that have a status of editable, no reviewNeeded and is not active (true false false).
    criteria.add(Restrictions.eq("status", new Status(true, false, false));
    

    现在您的 listEntities 方法可能变为:listEntities(Status status)listEntities(boolean editable, boolean reviewNeeded, boolean isActive)

    如果您需要更多信息,只需检查 Hibernate 在其自己的源代码中提供的 CompositeType 和 BasicType 实现(查找 CompositeType 和 BasicType 的实现者)。了解这些有助于使用和学习 Hibernate 的中级知识。

    【讨论】:

    • 很有趣,但您能否提供一个示例代码来参考我的案例?其他解决方法是将枚举类型存储在其他表中并引用Entity 表。 Like that 我可以为每个 Entity 收集一组枚举。
    • 我将更新帖子以包含更多针对您的特殊情况的信息
    • 感谢您的努力。当您在附近时,有时很难看到参与其中的人。仍然需要更好地查看 Hibernate 的 CompositeUserType 工具,正如我在这里看到的,需要为每个 Set&lt;EntityType&gt; 可能的组合构建一个特定的 Status。总而言之,Status 本身包含了这些可能性的单个组合,这可以认为与我自己的解决方案一样好,因为它本身包含了标准语句,但还必须实现 StatusCompositeType。但是,没有机会将它们与我在更新中提到的in 子句匹配。
    • 您不需要坚持每次创建的状态对象。您可以将 Status 替换为 Enum 类对象,它的工作方式相同。代表三个(或更多)列的实际对象实例取决于您。您可以使用点符号来引用任何列/属性。 Restrictions.eq(status.active, "true")。这就是使用复合类型/或嵌入的美妙之处。您可以像以前一样访问映射的属性。
    【解决方案2】:

    经过一番头脑风暴,我找到了一种解决方法,我认为第二好的方法是不可能为 Hibernate 中的布尔值映射枚举。这就是我的Entity 班级现在的样子:

    public class Entity{
    
        private boolean editable;
    
        private boolean needsReview;
    
        private boolean active;
    
        //getters and setters
    
    }
    

    我的列表方法是这样实现的:

    public List<Entity> listEntities(Set<EntityType> requiredTypes){
        Criteria cri = session.createCriteria(Entity.class);
        if (requiredTypes.contains(EntityType.EDITABLE)){
            cri.addRestriction(Restrictions.eq("editable",true));
        }
        if (requiredTypes.contains(EntityType.NEEDS_REVIEW)){
            cri.addRestriction(Restrictions.eq("needsReview",true));
        }
        if (requiredTypes.contains(EntityType.ACTIVE)){
            cri.addRestriction(Restrictions.eq("active",true));
        }
        return cri.list();
    }
    

    不错,但不知道这是不是唯一的方法!

    【讨论】:

    • 上一个答案中建议的一对多映射是多余的。您可以使用 EntityType 枚举的 @ElementCollection 来实现相同的目的。通过使用此映射,您的休眠代码可以通过使用Restrictions.in("flags", requiredTypes) 来简化,但由于需要连接表,sql 查询会更慢。
    【解决方案3】:

    我不认为 hibernate 提供了一种按照您描述的方式管理映射的方法。您可以创建自己的 UserType (https://community.jboss.org/wiki/Java5EnumUserType),但每次添加新枚举值时,您都必须更改 UserType 中的逻辑以映射新字段。

    另一种方法是将其转换为一对多关系。您的观点基本上是,如果您想添加更多字段,则必须更改 listEntities 的签名,但您还必须修改表格。

    因此,您可以创建一个包含您的实体类型并与您的实体具有@OneToMany` 关系的表。例如:

    根据需要定义您的标志:

    public enum Flags {
        EDITABLE, REVIEW_NEEDED, ACTIVE
    }
    

    创建与EntityType 的一对多关系:

    @Entity
    @Table( name="entity" )
    public class Entity implements Serializable {
    
        @OneToMany(mappedBy = "entity")
    public Set<EntityType> getEntityTypes() {
        return entityTypes;
    }
    

    Entity 的多对一:

    @Entity
    @Table( name="entityType" )
    public class EntityType implements Serializable {
    
        @Id
        private Integer id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ENTITY_ID")
        private Entity entity;
    
        @Enumerated(EnumType.STRING)
        private Flag entityType;
    
        ...
    }
    

    PD:请注意,该代码只是一个示例,并未完整或经过测试。

    【讨论】:

    • 我不认为需要为此创建关系。我将检查是否可以使用EnumUserType 实现某些目标。谢谢!
    • 可能您可以使用EnumUserType 实现它,但每次添加新标志时,您都必须更改数据库以添加新布尔值、用户类型中的映射并添加新值您的枚举,而另一种方法您根本不必更改数据库,只需添加新的枚举
    • 这里真正重要的是保持界面不变。一旦实现,我只想告诉休眠枚举匹配布尔值。我只是希望能够在Set 中保存一些新的枚举值并将相应的布尔值设置为 1。更改映射本身以添加新值不是问题,这不会是常见的事情。跨度>
    • 您知道如何使用EnumUserType 实现这一目标吗? The clearest tutorial 我发现似乎只对为枚举设置不同于其名称或序数值的不同值有效。
    • Mmmmh...EnumUserType 也不可能。正如您所指出的,最好用于更改您要保留的列的值,而不是用于将多个字段映射到一个集合中,反之亦然。我没有看到一个明确的解决方案来做到这一点。也许,您可以在表中使用它们的位域表示,而不是拥有多个 BIT 字段(即EDITABLE | REVIEW_NEEDED)。 See this link for fitfield using java enums
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-05
    • 2012-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多