Я нахожусь на Spring Boot 2.0.6, где у сущности pet
действительно есть отношение Ленивый много-к-одному с другой сущностью owner
Домашнее животное
@Entity
@Table(name = "pets")
public class Pet extends AbstractPersistable<Long> {
@NonNull
private String name;
private String birthday;
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
@JsonIdentityReference(alwaysAsId=true)
@JsonProperty("ownerId")
@ManyToOne(fetch=FetchType.LAZY)
private Owner owner;
Но при отправке запроса, подобного /pets
через клиента (например, PostMan), метод controller.get () сталкивается с исключением, как показано ниже: -
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.lang.Long and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ArrayList[0]->com.petowner.entity.Pet["ownerId"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191) ~[jackson-databind-2.9.7.jar:2.9.7]
Реализация Controller.get
@GetMapping("/pets")
public @ResponseBody List<Pet> get() {
List<Pet> pets = petRepository.findAll();
return pets;
}
Мои наблюдения
Попытка явно вызвать геттеры в пределах от owner
до pet
, чтобы принудительно запустить отложенную загрузку из прокси-объекта javaassist owner
в пределах pet
. Но не сработало.
@GetMapping("/pets")
public @ResponseBody List<Pet> get() {
List<Pet> pets = petRepository.findAll();
pets.forEach( pet -> pet.getOwner().getId());
return pets;
}
Попытка, как предложено в этом ответе stackoverflow в https://stackoverflow.com/a/51129212/5107365, чтобы иметь вызов контроллера для делегирования компоненту службы в рамках транзакции для принудительной отложенной загрузки. Но это тоже не сработало.
@Service
@Transactional(readOnly = true)
public class PetServiceImpl implements PetService {
@Autowired
private PetRepository petRepository;
@Override
public List<Pet> loadPets() {
List<Pet> pets = petRepository.findAll();
pets.forEach(pet -> pet.getOwner().getId());
return pets;
}
}
Работает, когда Service / Controller возвращает DTO, созданный из объекта. Очевидно, причина в том, что сериализатор JSON начинает работать с POJO вместо сущности ORM без каких-либо фиктивных объектов.
Изменение режима выборки сущностей на FetchType.EAGER решит проблему, но я не хотел его менять.
Мне любопытно узнать, почему выбрасывается исключение в случае (1) и (2). Те должны были принудительно загружать ленивые объекты.
Вероятно, ответ может быть связан с жизнью и областью действия созданных javassist объектов для поддержки ленивых объектов. Тем не менее, интересно, как сериализатор Джексон не может найти сериализатор для типа оболочки Java, как java.lang.Long
. Пожалуйста, помните здесь, что сгенерированное исключение указывает, что сериализатор Джексона получил доступ к owner.getId
, поскольку он распознал тип свойства ownerId
как java.lang.Long
.
Любые подсказки будут высоко оценены.
Редактировать
Отредактированная часть из принятого ответа объясняет причины. Предложение использовать собственный сериализатор очень полезно, если мне не нужно идти по пути DTO.
Я немного просмотрел источники в Джексоне, чтобы выяснить причины. Мысль поделиться этим тоже.
Джексон кэширует большинство метаданных сериализации при первом использовании. Логика, связанная с обсуждаемым сценарием использования, начинается с этого метода com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(Collection<?> value, JsonGenerator g, SerializerProvider provider)
. И соответствующий фрагмент кода: -
Оператор serializer = _findAndAddDynamic(serializers, cc, provider)
в строке # 140 запускает поток для назначения сериализаторов для свойств pet
-уровня, пропуская ownerId
для последующей обработки через serializer.serializeWithType
в строке # 147.
Назначение сериализаторов производится методом com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.resolve(SerializerProvider provider)
. Соответствующий фрагмент показан ниже: -
Сериализаторы назначаются в строке # 340 только для тех свойств, которые подтверждены как final
посредством проверки в строке # 333.
Когда сюда приходит owner
, его прокси-свойства имеют тип com.fasterxml.jackson.databind.type.SimpleType
. Если бы эта связанная сущность была загружена eagerly
, прокси-свойства явно не были бы там. Вместо этого исходные свойства будут найдены со значениями, которые набираются конечными классами, такими как Long, String и т. Д. (Как и свойства pet
).
Интересно, почему Джексон не может решить эту проблему с их конца, используя тип получателя вместо использования свойства прокси. Во всяком случае, это может быть другая тема для обсуждения :-)