Saving java BitSet to DB

More efficient way (that uses int instead of byte[]) requires a pretty simple custom class:

@Entity
@Access(AccessType.FIELD)
public class SampleEntity {

    @Transient
    private IntBitSet isolationLevel = new IntBitSet(0);

    public static final int USER_BIT = 0;
    public static final int DEVICE_BIT = 1;
    // 2, 3, 4, ...

    public boolean isUserIsolated() {
        return isolationLevel.bitGet(USER_BIT);
    }

    public boolean isDeviceIsolated() {
        return isolationLevel.bitGet(DEVICE_BIT);
    }

    public void setUserIsolated(boolean b) {
        isolationLevel.bitSet(USER_BIT, b);
    }

    public void setDeviceIsolated(boolean b) {
        isolationLevel.bitSet(DEVICE_BIT, b);
    }

    @Access(AccessType.PROPERTY)
    @Column
    public int getIsolationLevel() {
        return isolationLevel.getValue();
    }

    public void setIsolationLevel(int isolationLevel) {
        this.isolationLevel = new IntBitSet(isolationLevel);
    }

    private static class IntBitSet {
        private int value;

        public IntBitSet(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }

        public boolean bitGet(int i) {
            return ((value >> i) & 1) == 1;
        }

        public void bitSet(int i, boolean b) {
            if (b) {
                bitSet(i);
            } else {
                bitUnset(i);
            }
        }
        private void bitSet(int i) {
            value = value | (1 << i);
        }
        private void bitUnset(int i) {
            value = value & ~(1 << i);
        }
    }
}

By default JPA uses Java serialization to persist properties of unknown Serializable types (so that you have a serialized representation stored as a byte[]).

Usually it's not what you want, because there can be more efficient ways to represent your data. For example, BitSet can be efficiently represented as a number (if its size is limited), or byte[], or something else (unfortunately, BitSet doesn't provide methods to do these conversions, therefore you need to implement them manually).

When you've decided what kind of data representation you want to have in the database you need to tell JPA to apply the necessary conversion. There are two options:

  • Implement conversion in getters and setters. For example, as follows:

    @Entity
    @Table(name = "myTable")
    @Access(AccessType.FIELD)
    public class MyClass {
        ...
        @Transient // Do not store this field
        protected BitSet tags;
    
        @Access(AccessType.PROPERTY) // Store the property instead
        @Column(name = "Tags")
        byte[] getTagsInDbRepresentation() {
            ... // Do conversion
        }
    
        void setTagsInDbRepresentation(byte[] data) {
            ... // Do conversion
        }
        ...
    }
    
  • Use provider-specific extension to perform the conversion implicitly (for example, custom types in Hibernate). This approach allows you to reuse your type conversion logic in different entities.