简单值的集合可以用@ElementCollection
来映射,但这需要一个单独的表.
对于问题的情况,即将一组枚举映射到单个列,我喜欢使用位集-就像Java的EnumSet
在幕后所做的那样.我们将枚举的每个成员映射到只有1位集的布尔值,例如:
Enum member |
boolean |
MONDAY |
0000001 |
TUESDAY |
0000010 |
WEDNESDAY |
0000100 |
...and so on |
0001000 |
将枚举映射到位集只需1 << dayOfWeek.ordinal()
.因此,例如,值0000EnumSet.of(WEDNESDAY, MONDAY)
表示EnumSet.of(WEDNESDAY, MONDAY)
.现在可以很容易地使用数据库的位运算符来查询成员资格-例如,如果您想要包含TUESDAY
的所有Test
条记录,请执行以下操作:
-- This is a pseudocode example!!!
-- Various DBs have different ways to express binary constants and bitwise operators
SELECT * FROM Test WHERE activeDays & 0b0000010 <> 0
这需要JPA AttributeConverter
,如下所示:
import javax.persistence.AttributeConverter;
import java.time.DayOfWeek;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.EnumSet;
public class DayOfWeekConverter implements AttributeConverter<EnumSet<DayOfWeek>, Integer> {
@Override
public Integer convertToDatabaseColumn(EnumSet<DayOfWeek> value) {
return value == null ? null : value.stream().mapToInt(this::toBitmap).reduce(0, (aggr, cur) -> aggr | cur);
}
private int toBitmap(DayOfWeek d) {
return 1 << d.ordinal();
}
@Override
public EnumSet<DayOfWeek> convertToEntityAttribute(Integer dbData) {
return dbData == null ? null :
Arrays.stream(DayOfWeek.values())
.filter(d -> (toBitmap(d) & dbData) != 0)
.collect(
() -> EnumSet.noneOf(DayOfWeek.class),
AbstractCollection::add,
AbstractCollection::addAll
);
}
}
...和字段上的注释,如下所示-请注意,我使用了效率更高的EnumSet
作为数据类型:
@Convert(converter = DayOfWeekConverter.class)
private EnumSet<DayOfWeek> activeDays;
您可以调整转换器以存储JSON值或您能想到的EnumSet
的任何自定义格式(例如,像"|MONDAY|WEDNESDAY|"
这样的字符串是完全可能的,并且是可查询的,例如activeDays LIKE '%|MONDAY|%'
).我认为如果使用字符串表示,通过SQL进行搜索的效率会较低,但这取决于您.
实际上,如上所述的字符串转换器可以实现为:
public class DayOfWeekConverter implements AttributeConverter<EnumSet<DayOfWeek>, String> {
@Override
public String convertToDatabaseColumn(EnumSet<DayOfWeek> value) {
return value == null ? null : value.stream().map(Enum::name).collect(Collectors.joining("|","|","|"));
}
@Override
public EnumSet<DayOfWeek> convertToEntityAttribute(String dbData) {
return dbData == null ? null :
Arrays.stream(DayOfWeek.values())
.filter(d -> dbData.contains('|' + d.name() + '|'))
.collect(
() -> EnumSet.noneOf(DayOfWeek.class),
AbstractCollection::add,
AbstractCollection::addAll
);
}
}