5

I have an entity which has some BIT fields into the database:

  • editable
  • needs_review
  • active

These fields are mapped against boolean fields in its Java class using Hibernate 3.6.9 version. That forces me to write an interface method for each List of entities I want to get:

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

Or write a general interface method to achieve a combination of them:

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

That second choice looks greater, but if I add another field in the future there will be a need to modify the interface itself (and every line of code coupled to it).

So I decided I can express it as an enumeration 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);

It makes sense that being an enumeration match what I want to achieve, the Entity type itself should have its own Set<EntityType>:

public class Entity{
    Set<EntityType> entityTypes;
}

However instead of that I have the mapped booleans which logically match that Set. Then my question, is there any way to map Set<EntityType> entityTypes in hibernate based in that BIT fields or do I have to manage that logic myself having them as boolean?

UPDATE

Having them mapped as a Set implies the possibility of querying for a List using an in clause, if not it would imply an extra step for conversion between my controller and model codes.

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();
Aritz
  • 30,971
  • 16
  • 136
  • 217

3 Answers3

3

I think I have a solution for you. What you are interested in is a CompositeUserType.

As an example lets use a InetAddress composite user type I wrote lately to map a 128bit IPv6 Address / IPv4Address object to two 64bit long properties inside a user account entity.

The signupIp:InetAddress is mapped towards two columns (there is no column count limit or alike) using:

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

And the interesting part of the implementation looks like this:

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

Note that I flexibly switch between Inet4Address and Inet6Address instances depending on the values of ipLow and ipHigh. The composite is marked as immutable and you need to check the documentation and the examples in the Hibernate source code (build in composite user types).

In a similar way you can map your meaningful bit properties. You can query those bits by using a single Restriction.eq refering to your EnumType. You can use the equals method to check the properties object. And if you need to refer to a special mapped bit you can use the dot notation like in signupIp.ipLow to refer to the ipLow property/column.

I guess this is what you are looking for.

Update:

In the end it boils down to define the right order of your properties. Hibernate will always use integer index values to access each property:

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

In your StatusCompositeType class:

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

The rest is straight forward. Remember to implement equals and hashcode for the user type and add the type to the configuration before you create your sessionFactory.

Once you have everything in place you can create a criteria search and use:

//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));

Now your listEntities method may become either: listEntities(Status status) or listEntities(boolean editable, boolean reviewNeeded, boolean isActive).

If you need additional information just check the CompositeType and BasicType implementations Hibernate provides within its own sourcecode (look for implementors of CompositeType and BasicType). Understanding those helps alot to use and learn this intermediate level knowledge of Hibernate.

Aritz
  • 30,971
  • 16
  • 136
  • 217
Martin Kersten
  • 5,127
  • 8
  • 46
  • 77
  • Interesting, but could you provide an example code refering to my case? Other workaround would be to have the enum type stored in other table with a reference to the `Entity` table. [Like that](http://stackoverflow.com/questions/416208/jpa-map-collection-of-enums) I could have a collection of enums for each `Entity`. – Aritz Sep 30 '13 at 06:30
  • I will update the post to contain some more information for your special case – Martin Kersten Oct 01 '13 at 11:41
  • Thanks for your effort. It's sometimes difficult to see people involved as you're around here. Having still to look better at Hibernate's `CompositeUserType` tool, as I see here it's required to build an specific `Status` for each `Set` possible combination. Summarizing, `Status` itself packs a single combination of that possibilities, which can be considered as good as my own solution, because it packs the criteria statement itself, but also `StatusCompositeType` has to be implemented. However there's not the chance to match them with an `in` clause as I refer to in my update. – Aritz Oct 02 '13 at 07:10
  • You do not need to stick to a Status object created every time. You can replace Status with your Enum class objects and it works the same way. The actual object instance representing the three(or more) columns is up to you. You can refer to any of the columns/ properties using the dot notation. Restrictions.eq(status.active, "true"). Thats the beauty of using composite types / or embeddables. You can access the mapped properties the same way you are used to. – Martin Kersten Oct 02 '13 at 20:27
1

I don't think hibernate provides a way to manage the mappings the way you're describing. You can create your own UserType (https://community.jboss.org/wiki/Java5EnumUserType) but every time you add a new enum value you will have to change the logic in the UserType to map the new field as well.

The alternative will be to convert this into a one to many relationship. Your point is basically that if you want to add more fields you will have to change the signature of listEntities but also you will have to modify your table.

So, instead you can create a table that will contain your entity types and have a @OneToMany` relationship to it from your entity. For example:

Define your flags as required:

public enum Flags {
    EDITABLE, REVIEW_NEEDED, ACTIVE
}

Create a one-to-many relationship to EntityType:

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

    @OneToMany(mappedBy = "entity")
public Set<EntityType> getEntityTypes() {
    return entityTypes;
}

And a many-to-one to 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: Please note the code is just an example and is not complete or tested.

Eduardo Sanchez-Ros
  • 1,777
  • 2
  • 18
  • 30
  • I don't consider the need of creating a relation for that. I'll just check if I can achieve something with the `EnumUserType`. Thanks! – Aritz Sep 04 '13 at 13:40
  • Potentially you could achieve it with `EnumUserType` but every time you add a new flag you will have to change the database to add a new boolean, the mapping in the user type and add a new value to your enumeration whereas the other approach you won't have to change the database at all, just add new enumerations – Eduardo Sanchez-Ros Sep 04 '13 at 13:58
  • What it really matters here is maintaining the interface as it is. Once that achieved I only want to tell hibernate that enum matches the boolean values. I just want to be able to save some new enum value in the `Set` and have the corresponding boolean value set to 1. Changing the mapping itself to add new values is not a problem, it's not going to be something usual. – Aritz Sep 04 '13 at 14:02
  • Do you know how this could be achieved with `EnumUserType`? [The clearest tutorial](http://code.google.com/p/hibernate-enum-usertype/) I found seems to be valid only for setting a different value to the enumeration different than its name or ordinal value. – Aritz Sep 04 '13 at 14:29
  • Mmmmh... it's not going to be possible with `EnumUserType` either. As you pointed out it's best used to change the value of the column you're persisting rather than used to map a number of fields into a set and viceversa. I don't see a clear solution to do this. Maybe, instead of having multiple BIT fields you can have their bitfield representation in your table (i.e. `EDITABLE | REVIEW_NEEDED`). [See this link for fitfield using java enums](http://stackoverflow.com/questions/5346477/implementing-a-bitfield-using-java-enums) – Eduardo Sanchez-Ros Sep 04 '13 at 15:00
  • That link is not Hibernate-related. However it follows kind of solution I consider if there's no way to map this directly. Basically, doing enum-boolean translation into the app logic. – Aritz Sep 04 '13 at 15:20
  • A one to many mapping doesn't make sense if you basically want to map a collection of enums; use @ElementCollection instead. (http://stackoverflow.com/questions/416208/jpa-map-collection-of-enums) – Pieter Sep 27 '13 at 19:09
1

After some brainstorming, I've gone to a workaround which I consider the second best one being imposible to map an enum for the booleans in Hibernate. This is how I have my Entity class looks now:

public class Entity{

    private boolean editable;

    private boolean needsReview;

    private boolean active;

    //getters and setters

}

My listing method is implemented as this:

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

Not bad, but don't know if it's the only way to go with that!

Aritz
  • 30,971
  • 16
  • 136
  • 217
  • 1
    The One-to-many mapping suggested in the previous answer is overkill. You could achieve the same thing with an [@ElementCollection](http://stackoverflow.com/questions/416208/jpa-map-collection-of-enums) of the EntityType enum. By using this mapping your hibernate code can be simplified by using `Restrictions.in("flags", requiredTypes)`, but the sql query will be slower as it needs to join tables. – Pieter Sep 27 '13 at 19:35