You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot问题:上传照片时覆盖已有对象而非创建新对象

问题:Event关联的Photo无法新增,每次上传会覆盖已有照片

开发照片归档系统时,希望每个Event关联多张Photo,但实际情况是每个Event只能创建一张照片,再次上传会覆盖已有数据。

相关代码

Photo实体类

@Entity
@Table(name = "photographies")
public class Photo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(unique = true, nullable = false)
    private String label;

    private String photographer;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnore
    private Event event;

    //@Column(nullable = false)
    private Blob image;

    private Date date;

    public Photo(){};

    // getter和setter方法省略...

    public void assignEvent(Event event){
        this.event = event;
        event.getPhotos().add(this);
    }

    // equals和hashCode方法省略...
}

Event实体类

@Entity
@Table(name = "event")
public class Event {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String label;

    // 重复定义的字段,存在错误
    @OneToMany(mappedBy = "event", fetch = FetchType.LAZY)
    private List<Photo> photos = new LinkedList<>();

    public Event(String label) {
        this.label = label;
    }

    public Event(){};

    // 重复定义的字段,存在错误
    @OneToMany(mappedBy = "event", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Photo> photos = new LinkedList<>();

    // getter和setter方法省略...

    public void addPhoto(Photo photo){
        photos.add(photo);
    }

    // equals和hashCode方法省略...
}

Photo Service

@Service
public class PhotoService {

    @Autowired
    private PhotoRepository photoRepository;

    // 缺失EventRepository的注入
    // @Autowired
    // private EventRepository eventRepository;

    public List<Photo> findAllPhotosOrderedByDate() {
        return photoRepository.findAllByOrderByDateAsc();
    }

    public void deletePhoto(long id) {
        Photo photo = photoRepository.findById(id).get();
        photoRepository.delete(photo);
    }

    public void savePhoto(Photo photo){
        photoRepository.save(photo);
    }

    public Photo getPhotoById(long id){
        return photoRepository.findPhotoById(id);
    }

    @Transactional
    public void assignEvent(Photo photo, long id){
        // 此处会因为eventRepository未注入导致空指针
        photo.assignEvent(eventRepository.findById(id).get());
        photoRepository.save(photo);
    }
}

Event Service

@Service
public class EventService {

    @Autowired
    private EventRepository eventrepository;

    @Autowired
    private PhotoRepository photoRepository;

    public List<Event> showEvents() {
        return eventrepository.findAll();
    }

    public void saveEvent(Event event) {
        eventrepository.save(event);
    }

    public void deleteEvent(long id) {
        Event deletedEvent = eventrepository.findById(id).get();

        for (Photo photo : deletedEvent.getPhotos()){
            photo.assignEvent(null);
            photoRepository.save(photo);
        }

        eventrepository.delete(deletedEvent);
    }

    public Event getEventById(long id){
        return eventrepository.findById(id).get();
    }
}

Photo Controller

@Controller
public class PhotoController {

    @Autowired
    private PhotoService photoService;

    @Autowired
    private EventService eventService;

    @GetMapping("/photo/upload/{id}")
    public String uploadPhoto(Model model, @PathVariable long id){
        Photo photo = new Photo();
        model.addAttribute("photo", photo);
        model.addAttribute("id", id);
        return "uploadPhoto";
    }

    @PostMapping("/photo/upload/{id}") 
    public String uploadPhoto(@ModelAttribute ("photo") Photo photo, @PathVariable long id){
        photoService.assignEvent(photo, id);
        return "redirect:/";
    }
}

首页HTML

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Photo Upload</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand"> <img src="/images/sticker.webp" width="50" height="50" alt="An image of Bloome">
        <span class="navbar-brand mb-0 h1 fs-3 align-middle">Bloome Fursuit Photographies</span>
        </a>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto">
                <li class="nav-item">
                    <a class="nav-link" href="/event/add"><p class="text-light bg-primary m-2 p-2">Create new event</p></a>
                </li>
            </ul>
        </div>
    </nav>
    <br/>
    <div class="container">
        <div class="row">
            <h1>List of events</h1>
        </div>
        <br/>
        <table class="table table-striped">
            <thead class="table-dark">
            <tr>
                <th scope="col">Label</th>
                <th scope="col">Number of photos</th>
                <th scope="col">Actions</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each = "event : ${events}">
                <th scope="row" th:text = "${event.label}"></th>
                <td th:text = "${event.photos.size}"></td>
                <td>
                    <a th:href="@{/photo/upload/{id} (id=${event.id})}" class="btn btn-primary">Upload photo</a>
                    <a th:href="@{/event/delete/{id} (id=${event.id})}" class="btn btn-danger">Delete event</a>
                </td>
            </tr>
            </tbody>
        </table>
    </div>
</body>
</html>

照片上传表单HTML

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Upload photo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <br/>
    <div class="container">
        <form th:action = "@{/photo/upload/{id} (id=${id})}" th:object="${photo}" method = "POST">
            <div class="form-group row">
                <div class="col">
                    <label for="labelPhoto" class="col-sm-2 col-form-label col-form-label-lg">Label</label>
                    <input type="text" class="form-control form-control-lg rounded-pill" id="labelPhoto" th:field = "*{label}" placeholder="Enter label">
                </div>
                <div class="col">
                    <label for="photographer" class="col-sm-2 col-form-label col-form-label-lg">Photographer</label>
                    <input type="text" class="form-control form-control-lg rounded-pill" id="photographer" th:field = "*{photographer}" placeholder="Enter photographer">
                </div>
            </div>
            <br/>
            <div class="col-sm">
                <button type="submit" class="btn btn-primary rounded-pill m-2 p-2 align-text-top">Submit</button>
            </div>
        </form>
    </div>
</body>
</html>

问题原因及解决方案

1. Event实体类重复定义集合字段

Event类中重复定义了photos字段的@OneToMany映射,导致Hibernate无法正确维护关联关系,集合无法正常添加新元素。

修复:
删除重复的字段定义,保留一个正确的映射:

@Entity
@Table(name = "event")
public class Event {

    // 其他字段省略...

    @OneToMany(mappedBy = "event", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Photo> photos = new LinkedList<>();

    // 其他方法省略...
}

添加orphanRemoval = true可以确保当Photo从集合中移除时自动删除数据库中的对应记录。

2. PhotoService缺失EventRepository注入

PhotoService的assignEvent方法中使用了eventRepository但未注入,会导致空指针异常,需确保注入正确:

@Service
public class PhotoService {

    @Autowired
    private PhotoRepository photoRepository;

    @Autowired
    private EventRepository eventRepository;

    // 其他方法省略...
}

3. Photo的label字段设置了唯一约束

@Column(unique = true)要求所有Photo的label必须唯一,如果两次上传使用了相同的label,Hibernate会将第二次的请求视为更新已有记录(而非插入新记录),导致覆盖。

修复:

  • 如果允许不同Photo使用相同label,直接删除unique = true约束:
@Column(nullable = false)
private String label;
  • 如果必须保持label唯一,需要在上传表单中添加校验,提示用户输入唯一的label,避免重复提交。

4. 双向关联维护不完整

Photo的assignEvent方法未处理旧的关联关系,可能导致集合维护异常,建议修改为:

public void assignEvent(Event event){
    // 移除与旧Event的关联
    if (this.event != null) {
        this.event.getPhotos().remove(this);
    }
    this.event = event;
    // 添加与新Event的关联
    if (event != null) {
        event.getPhotos().add(this);
    }
}

5. 确保保存新实体

检查Photo的id字段:新创建的Photo id为0(long类型默认值),JPA的save方法会识别为新实体执行插入操作。确保数据库的id自增从1开始,且没有手动插入id=0的记录。

内容的提问来源于stack exchange,提问作者Bloome

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 02:01:01