在加载时实例化存储保存为null的@Embedded字段的情况?

时间:2015-03-26 15:26:03

标签: java hibernate jpa jpa-2.1

如果Person没有Address的问题被保留并稍后加载,则Person包含Address,所有字段都设置为null

这个(修改过的)示例来自coderanch,其中报告了类似的问题。

@Embeddable
public class Address {
  private String street;
  private String postalCode;
}

@Entity
public class Person {
  private String name;
  @Embedded
  private Address home;
}

我该如何规避这个问题?如果所有字段都是@Embedded,是否可以指示Hibernate不实例化null对象?

更改每个@Embedded字段的getter似乎很麻烦且容易出错。另一个繁琐的替代方法是@PostLoad的使用,但仅针对@Entity调用,而不是@Embeddable s。

1 个答案:

答案 0 :(得分:0)

好吧,我写了一些对我有帮助的东西。我将@PostLoad添加到@Entity s @Embeddable s:

public class NullableEmbeddedCleanerImpl implements NullableEmbeddedCleaner {
    private final static Logger LOGGER = LoggerFactory.getLogger(NullableEmbeddedCleaner.class);

    Map<Class<?>, Predicate<Object>> classFilterForNullables = new HashMap<>();
    Map<Class<?>, Predicate<Object>> collectionMap = new HashMap<>();
    Set<Class<?>> primitiveArrayClasses = new HashSet<Class<?>>();

    public NullableEmbeddedCleanerImpl() {
        fillPredicates();
        fillCollectionMap();
        fillPrimitiveArrayClasses();
    }

    /**
     * B C D F I J S Z
     */
    private void fillPrimitiveArrayClasses() {
        try {
            primitiveArrayClasses.addAll(Arrays.asList(Class.forName("[B"), Class.forName("[B"), Class.forName("[C"),
                    Class.forName("[D"), Class.forName("[F"), Class.forName("[I"), Class.forName("[J"), Class.forName("[S"),
                    Class.forName("[Z")));
        } catch (ClassNotFoundException e) {
            LOGGER.error("Class not found", e);
        }
    }

    @SuppressWarnings("unchecked")
    private void fillCollectionMap() { // misses Lists, Maps, ...
        collection(Set.class, s -> {
            final Set<Object> toRemove = new HashSet<>();
            for (Object i : s) {
                try {
                    if (clean(i)) {
                        toRemove.add(i);
                    }
                } catch (Exception e) {
                    LOGGER.warn("Error cleaning embeddable {} : {}", e, i);
                }
            }
            s.removeAll(toRemove);
            return s.isEmpty();
        });
    }

    @Override
    public final void embeddables(Object... arg) {
        for (Object i : arg) {
            try {
                clean(i);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                LOGGER.warn("Error cleaning embeddable {} : {}", e, i);
            }
        }
    }

    @Override
    public final boolean clean(Object arg) throws IllegalArgumentException, IllegalAccessException {
        if (arg == null) {
            return true;
        }
        boolean cleanThis = true;
        Vector<Field> fields = new Vector<>();
        for (Class<?> clazz = arg.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        }
        for (Field field : fields) {
            if (!fieldLoop(field, arg)) {
                cleanThis = false;
            }
        }
        return cleanThis;
    }

    private boolean fieldLoop(Field field, Object arg) throws IllegalArgumentException, IllegalAccessException {
        if (Modifier.isStatic(field.getModifiers())) { // skip static fields
            return true;
        }
        field.setAccessible(true);
        Object fieldValue = field.get(arg);
        if (fieldValue == null) {
            return true;
        }
        Class<?> fieldsClass = field.getType();
        if (fieldsClass.isPrimitive() || fieldsClass.isEnum()) {
            return false; // can not clean primitives nor enums
        }
        if (fieldsClass.isArray()) {
            if (primitiveArrayClasses.contains(fieldsClass)) {
                return false; // ignore primitive arrays
            } else {
                throw new UnsupportedOperationException("Do something useful here"); // object
                                                                                                            // arrays
            }
        }
        for (Class<?> clazz : collectionMap.keySet()) {
            if (clazz.isAssignableFrom(fieldsClass)) {
                boolean emptyCollection = collectionMap.get(clazz).test(fieldValue);
                if (!emptyCollection) {
                    return false;
                } else {
                    field.set(arg, null);
                }
                return true;
            }
        }
        // test primitives. just classes, no interfaces >>
        for (Class<?> fieldClass = fieldsClass; fieldClass != null; fieldClass = fieldClass.getSuperclass()) {
            if (classFilterForNullables.containsKey(fieldClass)) {
                Predicate<Object> handle = classFilterForNullables.get(fieldClass);
                if (handle.test(fieldValue)) {
                    return true;
                } else {
                    return false;
                }
            }
        }
        if (clean(field.get(arg))) { // decent to contained objects
            field.set(arg, null);
        } else {// non clean-able child exists
            return false;
        }
        return true;
    }

    private void fillPredicates() {
        nullableFilters(String.class, Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class,
                Float.class, Double.class, Void.class, LocalDateTime.class, LocalDate.class, LocalTime.class,
                OffsetDateTime.class, OffsetTime.class);
        classFilterForNullables.put(NullableEmbeddedCleanerImpl.class, n -> true); // always
                                                                                                            // filter
    }

    private void nullableFilters(Class<?>... cs) {
        for (Class<?> c : cs) {
            classFilterForNullables.put(c, o -> false);
        }
    }

    @SuppressWarnings("unchecked")
    final private <T> void collection(Class<T> c, Predicate<T> fun) {
        collectionMap.put((Class<?>) c, ((Predicate<Object>) fun));
    }
}