Отделенный объект, переданный для сохранения в Spring-данных при сохранении объекта, содержащего карту с ключом, уже сохраненным в БД и выбранным из нее сейчас

#java #spring #spring-boot #persistence

Вопрос:

Я кодирую свое приложение для весенней загрузки Harvest, и у меня есть такой сложный объект, который я получаю из формы, и я хочу сохранить его в БД. Этот объект Tomatoes Season содержит такие поля, как year сезон (целое число) и карта с ключом Tomatoes Variety и значением Tomatoes Variety Specification . Tomatoes Variety объект при создании Tomatoes Season выбирается из значений, которые уже сохранены в БД. Tomatoes Variety Specification заполняется в форме вручную и представляет собой новые данные.

При попытке сохранить такое Tomatoes Season в БД у меня появляется ошибка «Отсоединенный объект передан для сохранения» Tomatoes Variety . Если Tomatoes Variety это абсолютно новые данные, ранее не сохраненные в БД, то такой ошибки нет, но это не мой случай.

Если я изменю тип каскада с Cascade Type.ALL Cascade Type.MERGE на аннотацию карты @OneToMany в Tomatoes Season объекте, у меня появится еще одна ошибка «объект ссылается на несохраненный временный экземпляр» Tomatoes Variety Specification .

Есть ли какие — либо предложения, как решить проблему- создать Tomatoes Season с помощью ключа карты, выбранного из базы данных и Tomatoes Varierty Specification введенного вручную из формы?

Вот мой абстрактный Season класс:

 @Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Season {
    
    @Id
    @SequenceGenerator(name = "seasonSequence", sequenceName = "sequence_season", allocationSize = 1, initialValue = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seasonSequence")
    @Column(name = "season_id")
    private Integer id;

    @Column(nullable = false)
    @NotNull(message = "Field can't be empty!")
    private Integer year;

    public Season() {
    }

    public Season(Integer id, Integer year) {
        this.id = id;
        this.year = year;
    }

    public Season(Integer year) {
        this.year = year;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getYear() {
        return year;
    }

    public void setYear(Integer year) {
        this.year = year;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result   ((id == null) ? 0 : id.hashCode());
        result = prime * result   ((year == null) ? 0 : year.hashCode());
        return resu<
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Season other = (Season) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        if (year == null) {
            if (other.year != null)
                return false;
        } else if (!year.equals(other.year))
            return false;
        return true;
    }
}
 

and Tomatoes Season class:

 @Entity
@Table(name = "tomatoes_season")
public class TomatoesSeason extends Season implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @OneToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "tomatoes_season_varieties", 
      joinColumns = {@JoinColumn(name = "season_id", referencedColumnName = "season_id")},
      inverseJoinColumns = {@JoinColumn(name = "variety_specification_id", referencedColumnName = "variety_specification_id")})
    @MapKeyJoinColumn(name = "variety_id")
    @Column(nullable = false)
    private Map<TomatoesVariety, TomatoesVarietySpecification> tomatoesSeasonVarieties;
    
    public TomatoesSeason() {
        super();
    }

    public TomatoesSeason(Integer id, Integer year) {
        super(id, year);
    }

    public TomatoesSeason(Integer year) {
        super(year);
    }

    public TomatoesSeason(Integer id, Integer year,
            Map<TomatoesVariety, TomatoesVarietySpecification> tomatoesSeasonVarieties) {
        super(id, year);
        this.tomatoesSeasonVarieties = tomatoesSeasonVarieties;
    }

    public TomatoesSeason(Integer year, Map<TomatoesVariety, TomatoesVarietySpecification> tomatoesSeasonVarieties) {
        super(year);
        this.tomatoesSeasonVarieties = tomatoesSeasonVarieties;
    }

    public Map<TomatoesVariety, TomatoesVarietySpecification> getTomatoesSeasonVarieties() {
        return tomatoesSeasonVarieties;
    }

    public void setTomatoesSeasonVarieties(Map<TomatoesVariety, TomatoesVarietySpecification> tomatoesSeasonVarieties) {
        this.tomatoesSeasonVarieties = tomatoesSeasonVarieties;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result   ((tomatoesSeasonVarieties == null) ? 0 : tomatoesSeasonVarieties.hashCode());
        return resu<
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        TomatoesSeason other = (TomatoesSeason) obj;
        if (tomatoesSeasonVarieties == null) {
            if (other.tomatoesSeasonVarieties != null)
                return false;
        } else if (!tomatoesSeasonVarieties.equals(other.tomatoesSeasonVarieties))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Id="   getId()   ", Year="   getYear()   ", TomatoesSeasonVarieties="   tomatoesSeasonVarieties;
    }
}
 

abstract Variety class:

 @Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Variety {

    @Id
    @SequenceGenerator(name = "varietySequence", sequenceName = "sequence_variety", allocationSize = 1, initialValue = 1 )
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "varietySequence")
    @Column(name = "variety_id")
    private Integer id;

    @Column(nullable = false)
    @NotBlank(message = "Значение поля не может быть пустым!")
    private String name;

    public Variety() {
    }

    public Variety(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Variety(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result   ((id == null) ? 0 : id.hashCode());
        result = prime * result   ((name == null) ? 0 : name.hashCode());
        return resu<
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Variety other = (Variety) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;       
        return true;
    }
}
 

and Tomatoes Variety class:

 @Entity
@Table(name = "tomatoes_variety")
public class TomatoesVariety extends Variety implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "variety")
    @Column(nullable = false)
    private Set<TomatoesHarvesting> tomatoes;
    
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "season_id")
    private TomatoesSeason tomatoesSeason;

    public TomatoesVariety() {
        super();
    }

    public TomatoesVariety(Integer id, String name) {
        super(id, name);
    }

    public TomatoesVariety(String name) {
        super(name);
    }

    public Set<TomatoesHarvesting> getTomatoes() {
        return tomatoes;
    }

    public void setTomatoes(Set<TomatoesHarvesting> tomatoes) {
        this.tomatoes = tomatoes;
    }

    public TomatoesSeason getTomatoesSeason() {
        return tomatoesSeason;
    }

    public void setTomatoesSeason(TomatoesSeason tomatoesSeason) {
        this.tomatoesSeason = tomatoesSeason;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result   ((tomatoes == null) ? 0 : tomatoes.hashCode());
        result = prime * result   ((tomatoesSeason == null) ? 0 : tomatoesSeason.hashCode());
        return resu<
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        TomatoesVariety other = (TomatoesVariety) obj;
        if (tomatoes == null) {
            if (other.tomatoes != null)
                return false;
        } else if (!tomatoes.equals(other.tomatoes))
            return false;
        if (tomatoesSeason == null) {
            if (other.tomatoesSeason != null)
                return false;
        } else if (!tomatoesSeason.equals(other.tomatoesSeason))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Id="   getId()   ", Name="   getName();
    }
}
 

abstract Variety Specification class:

 @Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class VarietySpecification {

    @Id
    @SequenceGenerator(name = "varietySpecificationSequence", sequenceName = "sequence_variety_specification", allocationSize = 1, initialValue = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "varietySpecificationSequence")
    @Column(name = "variety_specification_id")
    private Integer id;

    @Column(nullable = false)
    @NotBlank(message = "Значение поля не может быть пустым!")
    private String customId;

    public VarietySpecification() {
        super();
    }

    public VarietySpecification(Integer id, String customId) {
        this.id = id;
        this.customId = customId;
    }

    public VarietySpecification(String customId) {
        this.customId = customId;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getCustomId() {
        return customId;
    }

    public void setCustomId(String customId) {
        this.customId = customId;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result   ((customId == null) ? 0 : customId.hashCode());
        result = prime * result   ((id == null) ? 0 : id.hashCode());
        return resu<
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        VarietySpecification other = (VarietySpecification) obj;
        if (customId == null) {
            if (other.customId != null)
                return false;
        } else if (!customId.equals(other.customId))
            return false;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }
}
 

and Tomatoes Variety Specification class:

 @Entity
@Table(name = "tomatoes_variety_specification")
public class TomatoesVarietySpecification extends VarietySpecification implements Serializable {
    private static final long serialVersionUID = 1L;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "variety_id")
    private TomatoesVariety tomatoesVariety;
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "tomatoesVarietySpecification")
    @Column(nullable = false)
    private Set<TomatoesGardenbed> tomatoesGardenbeds;

    public TomatoesVarietySpecification() {
    }

    public TomatoesVarietySpecification(Integer id, String customId) {
        super(id, customId);
    }

    public TomatoesVarietySpecification(String customId) {
        super(customId);
    }

    public TomatoesVarietySpecification(Integer id, String customId, Set<TomatoesGardenbed> tomatoesGardenbeds) {
        super(id, customId);
        this.tomatoesGardenbeds = tomatoesGardenbeds;
    }

    public TomatoesVarietySpecification(String customId, Set<TomatoesGardenbed> tomatoesGardenbeds) {
        super(customId);
        this.tomatoesGardenbeds = tomatoesGardenbeds;
    }

    public TomatoesVariety getTomatoesVariety() {
        return tomatoesVariety;
    }

    public void setTomatoesVariety(TomatoesVariety tomatoesVariety) {
        this.tomatoesVariety = tomatoesVariety;
    }

    public Set<TomatoesGardenbed> getTomatoesGardenbeds() {
        return tomatoesGardenbeds;
    }

    public void setTomatoesGardenbeds(Set<TomatoesGardenbed> tomatoesGardenbeds) {
        this.tomatoesGardenbeds = tomatoesGardenbeds;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result   ((tomatoesGardenbeds == null) ? 0 : tomatoesGardenbeds.hashCode());
        result = prime * result   ((tomatoesVariety == null) ? 0 : tomatoesVariety.hashCode());
        return resu<
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        TomatoesVarietySpecification other = (TomatoesVarietySpecification) obj;
        if (tomatoesGardenbeds == null) {
            if (other.tomatoesGardenbeds != null)
                return false;
        } else if (!tomatoesGardenbeds.equals(other.tomatoesGardenbeds))
            return false;
        if (tomatoesVariety == null) {
            if (other.tomatoesVariety != null)
                return false;
        } else if (!tomatoesVariety.equals(other.tomatoesVariety))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Id="   getId()   ", CustomId="   getCustomId()   ", TomatoesGardenbeds="   tomatoesGardenbeds;
    }
}
 

and my controller create method:

     @PostMapping("/create")
    public String createTomatoesSeason(String refererURI, String superRefererURI, @Valid TomatoesSeason tomatoesSeason, @RequestParam(name = "tomatoesVariety.id") TomatoesVariety tomatoesVariety, TomatoesVarietySpecification tomatoesVarietySpecification,  TomatoesGardenbed tomatoesGardenbed, BindingResult bindingResult, Model model) {
        if (bindingResult.hasErrors()) {
            Map<String, String> errors = ControllerUtils.getErrors(bindingResult);
            model.mergeAttributes(errors);
            model.addAttribute("refererURI", refererURI);

            if (superRefererURI != "") {
                model.addAttribute("superRefererURI", superRefererURI);
            }

            return "tomatoesSeasonCreator";
        }

        tomatoesVarietySpecification.setTomatoesGardenbeds(new HashSet<TomatoesGardenbed>(Collections.singleton(tomatoesGardenbed)));
        HashMap<TomatoesVariety, TomatoesVarietySpecification> tomatoesSeasonVarieties = new HashMap<TomatoesVariety, TomatoesVarietySpecification>();
        tomatoesSeasonVarieties.put(tomatoesVariety, tomatoesVarietySpecification);
        tomatoesSeason.setTomatoesSeasonVarieties(tomatoesSeasonVarieties);
        boolean tomatoesSeasonExists = tomatoesSeasonService.createTomatoesSeason(tomatoesSeason);
        
        if (tomatoesSeasonExists) {
            model.addAttribute("seasonExistsMessage", "Such season already exists!");
            model.addAttribute("refererURI", refererURI);
            
            if (superRefererURI != "") {
                model.addAttribute("superRefererURI", superRefererURI);
            }
            
            return "tomatoesSeasonCreator";
        }
        
        if (superRefererURI != "") {
            return "redirect:"   refererURI   "?superRefererURI="   superRefererURI;
        }
        
        return "redirect:"   refererURI;
    }
 

and Tomatoes Season Service class:

 @Service
public class TomatoesSeasonService {
    Logger logger = LoggerFactory.getLogger(TomatoesSeasonService.class);
    
    @Autowired
    private TomatoesSeasonRepository tomatoesSeasonRepository;

    public List<TomatoesSeason> findAll() {
        logger.trace("Getting all tomatoes seasons from database...");
        
        return tomatoesSeasonRepository.findAll();
    }

    public boolean checkIfExists(TomatoesSeason tomatoesSeason) {
        logger.trace("Checking if stored tomatoes season already exists in database...");
        
        Optional<TomatoesSeason> tomatoesSeasonFromDb = tomatoesSeasonRepository.findByYear(tomatoesSeason.getYear());
    
        if (tomatoesSeasonFromDb.isPresent() amp;amp; tomatoesSeason.getId() != tomatoesSeasonFromDb.get().getId()) {
            logger.warn("Tomatoes season of ""   tomatoesSeasonFromDb.get().getYear()   "" year already exists in database...");
            return true;
        }
        return false;
    }
    
    public boolean createTomatoesSeason(TomatoesSeason tomatoesSeason) {
        logger.trace("Adding new tomatoes season to database...");
        
        if (checkIfExists(tomatoesSeason))
            return false;

        logger.trace("Saving new tomatoes season in database...");
        tomatoesSeasonRepository.save(tomatoesSeason);
        return true;
    }

    public boolean updateTomatoesSeason(TomatoesSeason tomatoesSeason) {
        logger.trace("Updating tomatoes season in database...");
        
        if (checkIfExists(tomatoesSeason))
            return false;
        
        logger.trace("Saving updated tomatoes season in database...");
        tomatoesSeasonRepository.save(tomatoesSeason);
        return true;
    }

    public void deleteTomatoesSeason(TomatoesSeason tomatoesSeason) {
        logger.trace("Deleting tomatoes season from database...");
        
        tomatoesSeasonRepository.delete(tomatoesSeason);
    }
}