Не удалось записать JSON: невозможно получить доступ к потоку лобов - PullRequest
0 голосов
/ 03 января 2019

Я пишу сервер с Spring-Boot, используя PostgreSQL Я пытаюсь получить информацию об изображениях, которые связаны с определенной сущностью. Я пытаюсь получить информацию о пользователе с сервера в мое внешнее приложение Angular. В моей системе у пользователя есть изображения, связанные с его учетной записью, поэтому я сделал класс ImageEntity

@Entity @Table(name = "image") @Data
public class ImageEntity {
   @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
   private Long id;
   private String name;
   private String type;
   @Lob
   private byte[] image;

   @JsonIgnore
   public byte[] getImage() {
       return image;
   }
}

Затем я связал список изображений с классом учетной записи пользователя

@Entity @Data
public class UserAccount{
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String firstName;
private String lastName

@OneToMany(cascade = CascadeType.ALL)
@JoinTable(
        name = "user_images",
        joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
        inverseJoinColumns = {@JoinColumn(name = "image_id", referencedColumnName = "id")}
)
private List<ImageEntity> images;

public void addImage(ImageEntity image) {
    images.add(image);
}
}

Затем я создаю конечную точку, чтобы получить пользователя по идентификатору

@GetMapping("users/{id}")
public Optional<User> getUserById(@PathVariable Long id) {
    return service.getUserById(id);
}

метод обслуживания очень прост

@Transactional
public Optional<User> getUserById(Long id) {
    return repository.findById(id);
}

Я добавил несколько изображений через другую конечную точку, все работает нормально, потому что я могу получить изображение в своем интерфейсе.

Проблема в том, что когда я хочу получить информацию о пользователе в виде JSON с сервера (и я пишу @JsonIgnore в поле @Lob, потому что я хочу, чтобы информация об изображении не являлась действительным изображением), я получаю эту ошибку

Resolved exception caused by handler execution: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Unable to access lob stream; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unable to access lob stream (through reference chain: com.app.model.user.User["images"])

Я прочитал несколько похожих статей и пытаюсь дать @JsonIgnore при получении изображения Image @Lob. Я добавил @Transactional в сервисный метод, извлекающий элементы, но он не работает.

Я просто хочу получить такое сообщение от сервера:

{
 id: "1"
 firstName: "test",
 lstName: "test_ln",
 images: {
    {
        "id": 10,
        "name": "IMG12.jpg",
        "type": "image/jpeg"
    },
    {
        "id": 20,
        "name": "IMG456.jpg",
        "type": "image/jpeg"
    }
 }
}

1 Ответ

0 голосов
/ 03 января 2019

Самое быстрое решение (не самое лучшее) заключается в добавлении fecth = EAGER к отношению OneToMany изображений ... проблема с этим решением заключается в том, что всегда будет загружаться объект изображений (включая изображение байта []), когдаработа с объектами пользователей (что может быть проблемой производительности) ...

Следующее "лучшее" решение - это опустить конфигурацию EAGER, описанную ранее, и создать новый метод в вашем хранилище ... такойметод должен выполнить запрос JPA следующим образом:

SELECT ua
FROM
    UserAccount ua
    LEFT JOIN FECTH ua.images img
WHERE
    ua.id = :id

Это загрузит пользователя и связанные с ним изображения ... затем в вашем сервисе вы вызываете такой метод (проблема с этим решением заключается в том, что загружается байт[] image, даже если вам нужны только другие атрибуты ImageEntity)

Лучшее решение - расширить решение # 2, чтобы получить только те атрибуты, которые вам нужны для ImageEntity, в результатев таком запросе:

SELECT
    ua,
    img.id, img.name, img.type
FROM
    UserAccount ua
    LEFT JOIN ua.images img
WHERE
    ua.id = :id

Затем ваш метод хранилища должен вернуть JPA Tuple, а в вашем методе обслуживания вы преобразуете этот кортеж в пользователя, которыйвы хотите вернуть (включая метаданные связанных изображений) ... (ОБНОВЛЕНИЕ) Пример (используя метод, который вы указали в ваших комментариях):

// @Transactional  // Remove the transactional annotation to avoid cascade issues!
public User getUserById(Long id) {
    List<ImageEntity> images;
    List<Tuple> tuples;
    User user;

    tuples = repository.getUserById(id);
    user   = null;

    if (!tuples.isEmpty()) {
        user   = tuples.get(0).get(0, User.class);
        images = new ArrayList<>();

        for (Tuple t : tuples) {
            if (t.get(1) != null) {
                images.add(new ImageEntity(
                    t.get(1, Long.class),
                    t.get(2, String.class)
                ));
            }
        }
        user.setImages(images);
    }
    return user;
}

Для того чтобыДля работы вам необходимо:

  1. Изменить сигнатуру метода getUserById (в вашем хранилище), чтобы получить список Tuple
  2. Создать метод конструкторав классе ImageEntity с этой подписью: ImageEntity(long id, String name) { ... }
  3. Пользовательский объект должен иметь метод setImages(List<ImageEntity> images) { ... }

UPDATE2: , чтобы сделать что-то вродедля этого при извлечении всех пользователей вам потребуется:

1) Создать (или переопределить) метод в репозитории User, запрос которого будет похож (давайте назовем его findAll):

SELECT
    ua,
    img.id, img.name, img.type
FROM
    UserAccount ua
    LEFT JOIN ua.images img

2) В вашем сервисе реализуйте такой метод:

public List<User> findAll(Long id) {
    List<ImageEntity> images;
    List<Tuple> tuples;
    Map<Long, User> index;

    tuples = repository.findAll();
    index  = new HashMap<>();

    for (Tuple t : tuples) {
        user = t.get(0, User.class);

        if (!index.containsKey(user.getId()) {
            images = new ArrayList<>();

            user.setImages(images);
            index.put(user.getId(), user)
        } else {
            user   = index.get(user.getId());
            images = user.getImages():
        } 

        if (t.get(1) != null) {
            images.add(new ImageEntity(
               t.get(1, Long.class),
               t.get(2, String.class)
            ));
        }
    }
    return index.values();
}

ОБЪЯСНЕНИЕ: Ключевым моментом является то, что мы хотим получить пользователя с метаданными изображения (только код, имяи введите) избегая загрузки атрибута lob (потому что изображения могут быть МБ, и они выиграли 't / использоваться / сериализовано) ... вот почему мы выполняем запрос следующим образом:

    SELECT
    ua,
    img.id, img.name, img.type
FROM
    UserAccount ua
    LEFT JOIN ua.images img
  • LEFT JOIN заставляет извлекать всех пользователей (включая тех, у кого нет изображений)

  • ORM (т. Е. Реализация JPA, например, hibernate) отображает этот тип запроса в объект кортежа (всегда)!

  • Запроссоздает N x M кортежей ... где N - общее количество пользователей, а M - общее количество изображений ... например, если у вас есть только 1 пользователь с 2 изображениями ... результат будет 2 кортежа, где компонент первого кортежавсегда один и тот же пользователь, остальные компоненты будут атрибутами каждого изображения ...

  • Затем вам необходимо преобразовать объект кортежа в объект пользователя (это то, что мы делаемв методах обслуживания) ... ключевой момент здесь - использование нового ArrayList для атрибута images перед добавлением в него нового ImageEntity ... мы должны сделать это, потому что ORM внедряет список прокси для каждогоПользователь загружен ... если мы добавим что-то к этому прокси, ORM execиспользует ленивую загрузку такого прокси, извлекая связанные изображения (чего мы хотим избежать) ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...