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
相关产品推荐
相关产品推荐

