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

我读了一些类似的文章,我试图给@JsonIgnore提供关于Image@Lob Image的getter,我在服务方法中添加了@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 = EAGER添加到OneToMany图像关系。。。此解决方案的问题是,在处理用户实体(可能是性能问题)时,始终会加载图像实体(包括byte[]图像)。。。
下一个“最佳”解决方案是省略前面描述的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

然后,您的repository方法应该返回JPATuple,在您的服务方法中,您将该元组转换为您想要返回的用户(包括相关图像的元数据)。。。(更新)示例(使用您在注释中指示的方法):
// @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;
}

为了使其发挥作用,您需要:
修改方法的签名(在存储库中)以返回Tuple的列表
在ImageEntity类中创建具有以下签名的构造函数方法:getUserById
用户实体应该有一个方法ImageEntity(long id, String name) { ... }
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

setImages(List<ImageEntity> images) { ... }强制检索所有用户(包括那些没有图像的用户)
ORM(例如JPA实现,hibernate)将这种查询映射到Tuple对象(总是)!
查询生成N x M元组。。。其中N是用户总数,M是图像总数。。。例如,如果只有一个用户有两个图像。。。结果将是2个元组,其中第一个元组的组件始终是同一个用户,其他组件将是每个图像的属性。。。
然后,您需要将元组对象转换为用户对象(这是我们在服务方法中所做的)。。。这里的关键点是在为images属性添加新的ImageEntity之前,使用一个新的LEFT JOIN。。。我们需要这样做,因为ORM为每个加载的用户注入一个代理列表。。。如果我们向这个代理添加了一些内容,ORM将执行延迟加载这个代理,检索相关的图像(这是我们要避免的)。。。

10-08 12:44