java - 无法写入 JSON : Unable to access lob stream

标签 java json postgresql blob

我正在使用 PostgreSQL 编写带有 Spring-Boot 的服务器 我正在尝试获取有关链接到特定实体的图像的信息。 我正在尝试将用户信息从服务器获取到我的前端 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);
}
}

然后我创建端点以通过 id 获取用户

@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(并且我在 @Lob 字段上写 @JsonIgnore 因为我只想获得图像信息而不是实际图像)我得到这个错误

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"])

我读了一些类似的文章,我尝试在图像的 setter/getter 上给@JsonIgnore @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"
    }
 }
}

最佳答案

最快的解决方案(不是最好的)是添加 fecth = EAGEROneToMany图像关系...此解决方案的问题是在处理用户实体时总是会加载图像实体(包括 byte[] 图像)(这可能是性能问题)...

下一个“最佳”解决方案是省略 EAGER前面描述的配置,并在您的存储库中创建一个新方法...这样的方法应该执行这样的 JPA 查询:

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

这将加载用户及其相关图像...然后在您的服务中调用此类方法(此解决方案的问题是加载 byte[] 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并在您的服务方法中将该元组转换为您想要返回的用户(包括相关图像的元数据)... (UPDATE) 示例(使用您在评论中指定的方法):

// @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 属性(因为图像可以是 MB,它们不会被使用/序列化)...这就是为什么我们执行这样的查询:

    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 为每个加载的用户注入(inject)一个代理列表......如果我们向这个代理添加一些东西,ORM 会执行这样的延迟加载代理,检索关联的图像(这是我们想要避免的事情)...

关于java - 无法写入 JSON : Unable to access lob stream,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54013834/

相关文章:

sql - assignment_code 必须是唯一的,但我不希望它是

java - 在 Elastic Beanstalk 上设置 G1GC 时出错

java - 热插拔 ServerSocket (Java)

jquery - 在 C# .NET 3.5 中使用 HTTPHandler 和 jQuery 周日历

javascript - 如何在不刷新页面的情况下定时更新数据?

javascript - 如何在此 JSON 文件中引用此数据?

Java打印数组错误

java - 如何通过文件处理更改Java中文本文件中的名字?

php - 将 "INSERT"MySQL 查询转换为 Postgresql 查询

c# - npgsql中的日期类型是什么?