#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);
}
}