0

好吧,我的问题与我尝试在方便的存储库中使用相应的默认 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
4

0 回答 0