好吧,我的问题与我尝试在方便的存储库中使用相应的默认 CRUD 方法保存具有其他实体(AcmeFloat)集合的实体(Parade)有关(代码附在下面)。当它到达 save() 方法时,它会抛出异常。
我试图保存需要手动更新的 AcmeFloat 类的相关实体,但是,无论我做什么(无论是先保存更新的 Parade,然后更新并保存每个 AcmeFloat 还是由内而外)都会引发异常。
所以我进入了 Stack Overflow,我发现的解决方案是在 @ManyToMany 注释中放置一个 'cascade=CascadeType.ALL' ,但没有成功。我也尝试做这两件事,但也失败了。
这是代码:
游行(领域类):
package domain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.Valid;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.format.annotation.DateTimeFormat;
@Entity
@Access(AccessType.PROPERTY)
public class Parade extends DomainEntity {
// Fields -----------------------------------------------------------------
private String title;
private String description;
private Date moment;
private String ticker;
private boolean isDraft;
// Relationships ----------------------------------------------------------
private Brotherhood brotherhood;
private Collection<AcmeFloat> acmeFloats;
// Field access methods ---------------------------------------------------
@NotBlank
public String getTitle() {
return this.title;
}
public void setTitle(final String title) {
this.title = title;
}
@NotBlank
public String getDescription() {
return this.description;
}
public void setDescription(final String description) {
this.description = description;
}
@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(pattern = "dd/MM/yyyy HH:mm")
public Date getMoment() {
return this.moment;
}
public void setMoment(final Date moment) {
this.moment = moment;
}
@NotBlank
@Pattern(regexp = "^([\\d]){6}-([A-Z]){5}$")
@Column(unique = true)
public String getTicker() {
return this.ticker;
}
public void setTicker(final String ticker) {
this.ticker = ticker;
}
@Basic
public boolean getIsDraft() {
return this.isDraft;
}
public void setIsDraft(final boolean isDraft) {
this.isDraft = isDraft;
}
// Relationship access methods --------------------------------------------
@Valid
@ManyToOne(optional = true)
public Brotherhood getBrotherhood() {
return this.brotherhood;
}
public void setBrotherhood(final Brotherhood brotherhood) {
this.brotherhood = brotherhood;
}
@Valid
@ManyToMany(mappedBy = "parades", cascade = CascadeType.ALL)
public Collection<AcmeFloat> getAcmeFloats() {
return new ArrayList<AcmeFloat>(this.acmeFloats);
}
public void setAcmeFloats(final Collection<AcmeFloat> acmeFloats) {
this.acmeFloats = acmeFloats;
}
}
AcmeFloat(域类):
package domain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
@Entity
@Access(AccessType.PROPERTY)
public class AcmeFloat extends DomainEntity {
// Fields -----------------------------------------------------------------
private String title;
private String description;
private List<String> pictures;
// Relationships ----------------------------------------------------------
private Collection<Parade> parades;
private Brotherhood brotherhood;
// Field access methods ---------------------------------------------------
@NotNull
@NotBlank
public String getTitle() {
return this.title;
}
public void setTitle(final String title) {
this.title = title;
}
@NotNull
@NotBlank
public String getDescription() {
return this.description;
}
public void setDescription(final String description) {
this.description = description;
}
//Optional
//@URL
@NotNull
@ElementCollection
public List<String> getPictures() {
return this.pictures;
}
public void setPictures(final List<String> pictures) {
this.pictures = pictures;
}
// Relationship access methods --------------------------------------------
@ManyToMany(cascade = CascadeType.ALL)
@Valid
public Collection<Parade> getParades() {
return this.parades;
}
public void setParades(final Collection<Parade> parades) {
this.parades = new ArrayList<Parade>(parades);
}
@ManyToOne
@Valid
public Brotherhood getBrotherhood() {
return this.brotherhood;
}
public void setBrotherhood(final Brotherhood brotherhood) {
this.brotherhood = brotherhood;
}
}
ParadeForm(表单对象):
package forms;
import java.util.Collection;
import java.util.Date;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import domain.AcmeFloat;
public class ParadeForm {
// Fields -----------------------------------------------------------------
private int id;
private String title;
private String description;
private Date moment;
// Relationships ----------------------------------------------------------
private Collection<AcmeFloat> acmeFloats;
// Field access methods ---------------------------------------------------
@Range(min = 0)
public int getId() {
return this.id;
}
public void setId(final int id) {
this.id = id;
}
@NotBlank
public String getTitle() {
return this.title;
}
public void setTitle(final String title) {
this.title = title;
}
@NotBlank
public String getDescription() {
return this.description;
}
public void setDescription(final String description) {
this.description = description;
}
@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(pattern = "dd/MM/yyyy HH:mm")
public Date getMoment() {
return this.moment;
}
public void setMoment(final Date moment) {
this.moment = moment;
}
// Relationship access methods --------------------------------------------
@NotNull
public Collection<AcmeFloat> getAcmeFloats() {
return this.acmeFloats;
}
public void setAcmeFloats(final Collection<AcmeFloat> acmeFloats) {
this.acmeFloats = acmeFloats;
}
}
游行存储库:
package repositories;
import java.util.Date;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import domain.Parade;
@Repository
public interface ParadeRepository extends JpaRepository<Parade, Integer> {
@Query("select p from Parade p where p.ticker like ?1")
List<Parade> findByTicker(String ticker);
@Query("select p from Parade p where p.moment < ?1 and p.isDraft = false")
List<Parade> findBeforeDate(Date date);
@Query("select p from Parade p where p.isDraft = false")
List<Parade> findAllFinal();
@Query("select p from Parade p where p.isDraft = false and brotherhood.userAccount.id = ?1")
List<Parade> findAllFinalByBrotherhoodAccountId(int id);
@Query("select p from Parade p where brotherhood.userAccount.id = ?1")
List<Parade> findAllByBrotherhoodAccountId(int id);
@Query("select p from Parade p join p.brotherhood.enrolments e where e.member.id= ?1")
List<Parade> findPossibleMemberParades(int id);
}
AcmeFloatRepository:
package repositories;
import java.util.Collection;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import domain.AcmeFloat;
@Repository
public interface AcmeFloatRepository extends JpaRepository<AcmeFloat, Integer> {
@Query("select f from AcmeFloat f where f.brotherhood.userAccount.id = ?1")
Collection<AcmeFloat> findAcmeFloats(int principalId);
}
游行服务:
package services;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Random;
import javax.validation.ValidationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import repositories.ParadeRepository;
import security.LoginService;
import domain.AcmeFloat;
import domain.Parade;
import forms.ParadeForm;
@Service
@Transactional
public class ParadeService {
////////////////////////////////////////////////////////////////////////////////
// Managed repository
@Autowired
private ParadeRepository paradeRepository;
////////////////////////////////////////////////////////////////////////////////
// Supporting services
@Autowired
private BrotherhoodService brotherhoodService;
////////////////////////////////////////////////////////////////////////////////
// Supporting services
@Autowired
private Validator validator;
////////////////////////////////////////////////////////////////////////////////
// Ticker generation fields
private static final String TICKER_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int TICKER_LENGTH = 5;
private final Random random = new Random();
////////////////////////////////////////////////////////////////////////////////
// Constructors
public ParadeService() {
super();
}
////////////////////////////////////////////////////////////////////////////////
// CRUD methods
public Parade create() {
final Parade parade = new Parade();
parade.setBrotherhood(this.brotherhoodService.findByUserAccountId(LoginService.getPrincipal().getId()));
parade.setAcmeFloats(new ArrayList<AcmeFloat>());
parade.setIsDraft(true);
parade.setDescription("");
parade.setTitle("");
if (parade.getTicker() == null || parade.getTicker().isEmpty()) {
final Calendar calendar = new GregorianCalendar();
String dateString = "";
dateString += String.format("%02d", calendar.get(Calendar.YEAR) % 100);
dateString += String.format("%02d", calendar.get(Calendar.MONTH) + 1);
dateString += String.format("%02d", calendar.get(Calendar.DAY_OF_MONTH));
dateString += "-";
String ticker;
do {
ticker = dateString;
for (int i = 0; i < ParadeService.TICKER_LENGTH; ++i)
ticker += ParadeService.TICKER_ALPHABET.charAt(this.random.nextInt(ParadeService.TICKER_ALPHABET.length()));
} while (this.paradeRepository.findByTicker(ticker).size() > 0);
parade.setTicker(ticker);
}
return parade;
}
public Parade save(final Parade parade) {
Assert.notNull(parade);
//final Parade originalParade = this.paradeRepository.findOne(parade.getId());
//if (originalParade != null)
Assert.isTrue(parade.getIsDraft());
Assert.isTrue(parade.getMoment().after(new Date()));
//TODO: if ticker existe en BBDD, generar nuevo, else, se guarda
return this.paradeRepository.save(parade);
}
public void delete(final Parade parade) {
Assert.notNull(parade);
Assert.isTrue(parade.getIsDraft());
this.paradeRepository.delete(parade);
}
public Parade findOne(final int id) {
return this.paradeRepository.findOne(id);
}
public List<Parade> findAll() {
return this.paradeRepository.findAll();
}
////////////////////////////////////////////////////////////////////////////////
// Ancillary methods
public List<Parade> findWithin30Days() {
final Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.DATE, 30);
final Date plus30Days = calendar.getTime();
return this.paradeRepository.findBeforeDate(plus30Days);
}
public List<Parade> findAllByBrotherhoodAccountId(final int id) {
return this.paradeRepository.findAllByBrotherhoodAccountId(id);
}
public List<Parade> findAllFinalByBrotherhoodAccountId(final int id) {
return this.paradeRepository.findAllFinalByBrotherhoodAccountId(id);
}
public List<Parade> findAllFinal() {
return this.paradeRepository.findAllFinal();
}
public List<Parade> findPossibleMemberParades(final int memberId) {
return this.paradeRepository.findPossibleMemberParades(memberId);
}
public Parade reconstruct(final ParadeForm paradeForm, final BindingResult binding) {
Parade result;
if (paradeForm.getId() == 0)
result = this.create();
else
result = this.paradeRepository.findOne(paradeForm.getId());
result.setTitle(paradeForm.getTitle());
result.setDescription(paradeForm.getDescription());
result.setMoment(paradeForm.getMoment());
result.setAcmeFloats(paradeForm.getAcmeFloats());
this.validator.validate(result, binding);
this.paradeRepository.flush();
if (binding.hasErrors())
throw new ValidationException();
return result;
}
}
AcmeFloatService:
package services;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import repositories.AcmeFloatRepository;
import domain.AcmeFloat;
import domain.Parade;
@Service
@Transactional
public class AcmeFloatService {
////////////////////////////////////////////////////////////////////////////////
// Managed repository
@Autowired
private AcmeFloatRepository acmeFloatRepository;
////////////////////////////////////////////////////////////////////////////////
// Supporting services
////////////////////////////////////////////////////////////////////////////////
// Constructors
public AcmeFloatService() {
super();
}
////////////////////////////////////////////////////////////////////////////////
// CRUD methods
public AcmeFloat create() {
final AcmeFloat result = new AcmeFloat();
// set fields
result.setTitle("");
result.setDescription("");
result.setPictures(new ArrayList<String>());
// set relationships
result.setParades(new ArrayList<Parade>());
result.setBrotherhood(null);
return result;
}
public AcmeFloat save(final AcmeFloat acmeFloat) {
Assert.isTrue(acmeFloat != null);
return this.acmeFloatRepository.save(acmeFloat);
}
public Iterable<AcmeFloat> save(final Iterable<AcmeFloat> acmeFloats) {
Assert.isTrue(acmeFloats != null);
return this.acmeFloatRepository.save(acmeFloats);
}
public void delete(final AcmeFloat acmeFloat) {
Assert.isTrue(acmeFloat != null);
this.acmeFloatRepository.delete(acmeFloat);
}
public void delete(final Iterable<AcmeFloat> acmeFloats) {
Assert.isTrue(acmeFloats != null);
this.acmeFloatRepository.delete(acmeFloats);
}
public AcmeFloat findOne(final int id) {
return this.acmeFloatRepository.findOne(id);
}
public List<AcmeFloat> findAll() {
return this.acmeFloatRepository.findAll();
}
////////////////////////////////////////////////////////////////////////////////
// Ancillary methods
public Collection<AcmeFloat> findAcmeFloats(final int id) {
return this.acmeFloatRepository.findAcmeFloats(id);
}
}
游行控制器:
package controllers;
import java.util.Collection;
import javax.validation.Valid;
import javax.validation.ValidationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import security.LoginService;
import security.UserAccount;
import services.AcmeFloatService;
import services.BrotherhoodService;
import services.ParadeService;
import domain.AcmeFloat;
import domain.Brotherhood;
import domain.Parade;
import forms.ParadeForm;
@Controller
@RequestMapping("/parade")
public class ParadeController extends AbstractController {
// Services ---------------------------------------------------------------
@Autowired
private ParadeService paradeService;
@Autowired
private BrotherhoodService brotherhoodService;
@Autowired
private AcmeFloatService acmeFloatService;
// Constructors -----------------------------------------------------------
public ParadeController() {
}
// List -------------------------------------------------------------------
@RequestMapping(value = "/brotherhood/list", method = RequestMethod.GET)
public ModelAndView list() {
final ModelAndView result;
Collection<Parade> parades;
parades = this.paradeService.findAllByBrotherhoodAccountId(LoginService.getPrincipal().getId());
result = new ModelAndView("parade/brotherhood/list");
result.addObject("parades", parades);
result.addObject("requestURI", "parade/brotherhood/list.do");
return result;
}
// Create -----------------------------------------------------------------
@RequestMapping(value = "/brotherhood/create", method = RequestMethod.GET)
public ModelAndView create() {
final ModelAndView result;
Parade parade;
parade = this.paradeService.create();
parade.setIsDraft(true);
result = this.createEditModelAndView(parade, "create");
return result;
}
// Edit -------------------------------------------------------------------
@RequestMapping(value = "/brotherhood/edit", method = RequestMethod.GET)
public ModelAndView edit(@RequestParam final int paradeId) {
ModelAndView result;
Parade parade;
parade = this.paradeService.findOne(paradeId);
Assert.notNull(parade);
result = this.createEditModelAndView(parade, "edit");
return result;
}
// Save -------------------------------------------------------------------
@RequestMapping(value = "/brotherhood/edit", method = RequestMethod.POST, params = "save")
public ModelAndView save(@ModelAttribute("parade") final ParadeForm paradeForm, final BindingResult binding) {
ModelAndView result;
Parade parade;
Parade oldParade;
parade = this.paradeService.reconstruct(paradeForm, binding);
oldParade = this.paradeService.findOne(paradeForm.getId());
try {
for(AcmeFloat f : parade.getAcmeFloats()){
Collection<Parade> parades = f.getParades();
parades.add(parade);
f.setParades(parades);
this.acmeFloatService.save(f);
}
if(parade.getId() != 0){
Collection<AcmeFloat> paradesRemoved = oldParade.getAcmeFloats();
paradesRemoved.removeAll(parade.getAcmeFloats());
for(AcmeFloat f : paradesRemoved){
final Collection<Parade> parades = f.getParades();
parades.remove(parade);
f.setParades(parades);
this.acmeFloatService.save(f);
}
}
this.paradeService.save(parade);
result = new ModelAndView("redirect:list.do");
} catch (final ValidationException oops) {
result = this.createEditModelAndView(parade, "edit");
} catch (final Throwable oops) {
result = this.createEditModelAndView(parade, "parade.commit.error", "edit");
}
return result;
}
/*
final Parade paradeUpdated = this.paradeService.reconstruct(paradeForm, binding);
Collection<AcmeFloat> paradesRemoved = new ArrayList<>();
if (paradeForm.getId() != 0)
paradesRemoved = parade.getAcmeFloats();
if (paradeUpdated.getId() != 0)
paradesRemoved.removeAll(paradeUpdated.getAcmeFloats());
final Parade paradeSaved = this.paradeService.save(paradeUpdated);
for (final AcmeFloat f : paradeUpdated.getAcmeFloats()) {
final Collection<Parade> parades = f.getParades();
parades.add(paradeSaved);
f.setParades(parades);
this.acmeFloatService.save(f);
}
if (paradeUpdated.getId() != 0)
for (final AcmeFloat f : paradesRemoved) {
final Collection<Parade> parades = f.getParades();
parades.remove(parade);
f.setParades(parades);
this.acmeFloatService.save(f);
*/
// Delete -----------------------------------------------------------------
@RequestMapping(value = "/brotherhood/edit", method = RequestMethod.POST, params = "delete")
public ModelAndView delete(final Parade parade, final BindingResult binding) {
ModelAndView result;
try {
this.paradeService.delete(parade);
result = new ModelAndView("redirect:list.do");
} catch (final Throwable oops) {
result = this.createEditModelAndView(parade, "parade.commit.error", "edit");
}
return result;
}
// Save in Final Mode -----------------------------------------------------
@RequestMapping(value = "/brotherhood/edit", method = RequestMethod.POST, params = "finalMode")
public ModelAndView finalMode(@Valid final Parade parade, final BindingResult binding) {
ModelAndView result;
if (binding.hasErrors())
result = this.createEditModelAndView(parade, "edit");
else
try {
parade.setIsDraft(false);
this.paradeService.save(parade);
result = new ModelAndView("redirect:list.do");
} catch (final Throwable oops) {
result = this.createEditModelAndView(parade, "parade.commit.error", "edit");
}
return result;
}
// Show -------------------------------------------------------------------
@RequestMapping(value = "/public/show", method = RequestMethod.GET)
public ModelAndView show(@RequestParam final int paradeId) {
ModelAndView result;
Parade parade;
parade = this.paradeService.findOne(paradeId);
Assert.notNull(parade);
Assert.isTrue(parade.getIsDraft());
result = new ModelAndView("parade/public/" + "show");
result.addObject("parade", parade);
// result.addObject("messageCode", null);
return result;
}
// Ancillary Methods ------------------------------------------------------
protected ModelAndView createEditModelAndView(final Parade parade, final String method) {
ModelAndView result;
result = this.createEditModelAndView(parade, null, method);
return result;
}
protected ModelAndView createEditModelAndView(final Parade parade, final String messageCode, final String method) {
final ModelAndView result;
final Brotherhood brotherhood;
final Collection<AcmeFloat> acmeFloats;
final UserAccount userAccount = LoginService.getPrincipal();
brotherhood = this.brotherhoodService.findPrincipal();
acmeFloats = this.acmeFloatService.findAcmeFloats(userAccount.getId());
result = new ModelAndView("parade/brotherhood/" + method);
result.addObject("brotherhood", brotherhood);
result.addObject("acmeFloats", acmeFloats);
result.addObject("parade", parade);
result.addObject("messageCode", messageCode);
return result;
}
}
在 Create 案例中,流程是下一个:在填写表单和发送后的创建视图中,在 ParadeController 中调用 save() 方法。它的输入是一个 id=0 的 ParadeForm 对象。“parade”(在这种情况下是新的 Parade,在版本的情况下是更新的 Parade)和“oldParade”(在这种情况下是 null,但在更新情况下是更新前的 Parade)对象无论如何都会被创建和声明。然后,我们进入 try/catch。首先,它获取 parade 的 acmeFloats 以更新它们,在他们的 parades 集合中添加刚刚创建的 Parade。但是,在第一次尝试保存时,它会抛出以下内容:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance
首先保存新的 Parade 会得到相同的结果。如果它是一个版本(因此 Parade 之前存在并且可能有 AcmeFloats),我打算找到已从 Parade 中删除的 AcmeFloats 并更新它们,然后保存 Parade。所以我需要编辑案例中的 oldParade 来检查我必须从哪些 AcmeFloats 中删除 Parade。
另外,我不知道我是否必须用@ManyToMany 中的级联来做所有这些事情,但只是在重建后保存游行,但它无论如何都不起作用,所以我决定发布那部分代码以便你知道没有级联它是如何工作的。
在上个月和之前,我一直在处理这个问题。提前致谢。
编辑1:
当我在存储库中保存后放置一个 flush() 时,它会在保存时引发以下异常:
org.springframework.orm.jpa.JpaSystemException: Exception occurred inside getter of domain.Parade.acmeFloats; nested exception is org.hibernate.PropertyAccessException: Exception occurred inside getter of domain.Parade.acmeFloats