【问题标题】:Could not write JSON: Unable to access lob stream无法写入 JSON:无法访问 lob 流
【发布时间】:2019-05-29 13:02:41
【问题描述】:

我正在使用 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"])

我阅读了一些类似的文章,并尝试在 Image @Lob 图像的 getter 上提供 @JsonIgnore 我将 @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"
    }
 }
}

【问题讨论】:

    标签: java json postgresql blob


    【解决方案1】:

    最快的解决方案(不是最好的解决方案)是将fecth = EAGER添加到OneToMany图像关系...这个解决方案的问题是总是会加载图像实体(包括字节[]图像)时处理用户实体(这可能是一个性能问题)...

    下一个“最佳”解决方案是省略前面描述的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 并在您的服务方法中将该元组转换为您想要返回的用户(包括相关图像的元数据)... (更新) 示例(使用您在 cmets 中指定的方法):

    // @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&lt;ImageEntity&gt; images) { ... }

    UPDATE2:为了做这样的事情,在检索所有用户时,您需要:

    1) 在用户存储库中创建(或覆盖)一个方法,其查询类似于(我们称之为 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 实现,例如休眠)将这种查询映射到元组对象(总是)!

    • 查询产生 N x M 元组...其中 N 是用户总数,M 是图像总数...例如,如果您只有 1 个用户和 2 个图像...结果将是2个元组,其中第一个元组的组件始终是同一用户,其他组件将是每个图像的属性...

    • 1234563在向它添加新的 ImageEntity 之前......我们需要这样做,因为 ORM 为每个加载的用户注入一个代理列表......如果我们向这个代理添加一些东西,ORM 执行此类代理的延迟加载,检索关联的图片(这是我们想要避免的)...

    【讨论】:

    • 所以我做了第三个选项,但首先我将@JsonIgnore 添加到图像字段。我这样写:@Query("SELECT re, img.id, img.name FROM User re JOIN re.images img WHERE re.id = :id") Optional getUserById(@Param("id") Long id );但我得到这个 JSON { "id":"1", "firstName"; "test", "lastName": "test_ln" } 42, IMG12.jpg 有什么建议可以解决这个问题,让 JSON 结构出现在主要问题中吗? { "id":"1", "firstName"; “test”,“lastName”:“test_ln”,“images”:{“id”:42,“name”:“IMG12.jpg”} } ??
    • 这也仅在我得到一张图像时才有效。当我添加 4 张图片时,我得到了 NonUniqueResultException: query did not return a unique result: 4
    • 我猜你忘了写 user.addImage(new ImageEntity())。我这样做了,但它不起作用。我在向用户添加新的 ImageEntity 时得到Caused by: org.hibernate.HibernateException: Unable to access lob stream org.postgresql.util.PSQLException: Large Objects may not be used in auto-commit mode.
    • 顺便说一句,当您使用 @Transactional 注释时,您必须小心处理从数据库中检索到的对象,特别是如果这些对象具有允许的级联类型...如果您不小心,在我们的示例中,您以向检索用户添加新图像结束...这就是为什么我告诉您:删除 @Transactional 注释或从实体管理器中分离用户实体(为了不修改实体本身)
    • 我为 /users 端点添加了一个示例
    猜你喜欢
    • 2012-02-19
    • 1970-01-01
    • 2019-03-03
    • 1970-01-01
    • 2019-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-29
    相关资源
    最近更新 更多