Pit*_*_OS 1 java postgresql json 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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后我将图像列表链接到用户帐户类别
@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);
}
}
Run Code Online (Sandbox Code Playgroud)
然后我创建端点以通过 id 获取用户
@GetMapping("users/{id}")
public Optional<User> getUserById(@PathVariable Long id) {
return service.getUserById(id);
}
Run Code Online (Sandbox Code Playgroud)
服务方法很简单
@Transactional
public Optional<User> getUserById(Long id) {
return repository.findById(id);
}
Run Code Online (Sandbox Code Playgroud)
我通过另一个端点添加了一些图像,效果很好,因为我能够在前端获取图像。
问题是当我想从服务器获取 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"])
Run Code Online (Sandbox Code Playgroud)
我读了一些类似的文章,我尝试在图像 @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"
}
}
}
Run Code Online (Sandbox Code Playgroud)
最快的解决方案(不是最好的解决方案)是添加fecth = EAGER图像OneToMany关系...该解决方案的问题是,在处理用户实体(可能是一个性能问题)...
下一个“最佳”解决方案是省略EAGER前面描述的配置,并在存储库中创建一个新方法...此类方法应执行如下 JPA 查询:
SELECT ua
FROM
UserAccount ua
LEFT JOIN FECTH ua.images img
WHERE
ua.id = :id
Run Code Online (Sandbox Code Playgroud)
这将加载用户及其相关图像...然后在您的服务中,您调用这样的方法(此解决方案的问题是image即使您只想要 的其他属性,也会加载 byte[] ImageEntity)
最好的解决方案是扩展解决方案 #2 以仅检索您想要的 属性ImageEntity,从而产生如下查询:
SELECT
ua,
img.id, img.name, img.type
FROM
UserAccount ua
LEFT JOIN ua.images img
WHERE
ua.id = :id
Run Code Online (Sandbox Code Playgroud)
然后,您的存储库方法应返回 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;
}
Run Code Online (Sandbox Code Playgroud)
为了使其发挥作用,您需要:
getUserById列表ImageEntity(long id, String name) { ... }setImages(List<ImageEntity> images) { ... }UPDATE2:为了执行类似的操作,在检索所有用户时,您将需要:
1)在用户存储库中创建(或覆盖)一个方法,其查询如下(我们称之为 findAll):
SELECT
ua,
img.id, img.name, img.type
FROM
UserAccount ua
LEFT JOIN ua.images img
Run Code Online (Sandbox Code Playgroud)
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();
}
Run Code Online (Sandbox Code Playgroud)
说明:关键点是我们希望使用图像元数据(仅代码、名称和类型)检索用户,避免加载 lob 属性(因为图像可以是 MB 并且它们不会被使用/序列化).. .这就是我们执行这样的查询的原因:
SELECT
ua,
img.id, img.name, img.type
FROM
UserAccount ua
LEFT JOIN ua.images img
Run Code Online (Sandbox Code Playgroud)
强制LEFT JOIN检索所有用户(包括没有图像的用户)
ORM(即JPA实现,例如hibernate)将这种查询映射到元组对象(总是)!
查询生成 N x M 元组...其中 N 是用户总数,M 是图像总数...例如,如果只有 1 个用户和 2 张图像...结果将是 2 个元组,其中第一个元组的组件始终是同一用户,其他组件将是每个图像的属性......
然后,您需要将元组对象转换为用户对象(这就是我们在服务方法中所做的)...这里的关键点是在ArrayList向其添加新的 ImageEntity 之前使用新的图像属性。 .我们需要这样做,因为 ORM 为每个加载的用户注入一个代理列表...如果我们向这个代理添加一些东西,ORM 会执行此类代理的延迟加载,检索关联的图像(这是我们想要的)避免)...