title: Detached entity passed to persist in Spring-Data while saving an object that contains a Map with key already saved in DB and selected from it now
body: <p>I'm coding my Harvest Spring Boot App and I've got such a complicated object that I'm getting from form and I want to save in DB. This object is <code>Tomatoes Season</code> that contains such fields as a <code>year</code> of season (Integer) and a map with key of <code>Tomatoes Variety</code> and value <code>Tomatoes Variety Specification</code>. <code>Tomatoes Variety</code> object when creating <code>Tomatoes Season</code> is selected from values that has already saved in DB. <code>Tomatoes Variety Specification</code> is filled in the form manually and is a new data.</p>
  <p>When trying to save such <code>Tomatoes Season</code> in DB I've got an error &quot;Detached entity passed to persist&quot; on <code>Tomatoes Variety</code>. If <code>Tomatoes Variety</code> is absolutely new data not previously stored in DB, there is no such error, but that is not my case.</p>
  <p>If I change cascade type from <code>Cascade Type.ALL</code> to <code>Cascade Type.MERGE</code> on @OneToMany map annotation in <code>Tomatoes Season</code> object I've got another error &quot;object references an unsaved transient instance&quot; on <code>Tomatoes Variety Specification</code>.</p>
  <p>Are there any suggestions how to solve the problem - to create <code>Tomatoes Season</code> with map key selected from DB and <code>Tomatoes Varierty Specification</code> manually typed from form?</p>
  <p>Here is my abstract <code>Season</code> class:</p>
  <pre><code>@Entity
  @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  public abstract class Season {
      
      @Id
      @SequenceGenerator(name = &quot;seasonSequence&quot;, sequenceName = &quot;sequence_season&quot;, allocationSize = 1, initialValue = 1)
      @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = &quot;seasonSequence&quot;)
      @Column(name = &quot;season_id&quot;)
      private Integer id;
  
      @Column(nullable = false)
      @NotNull(message = &quot;Field can't be empty!&quot;)
      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 result;
      }
  
      @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;
      }
  }
  </code></pre>
  <p>and <code>Tomatoes Season</code> class:</p>
  <pre><code>@Entity
  @Table(name = &quot;tomatoes_season&quot;)
  public class TomatoesSeason extends Season implements Serializable {
      private static final long serialVersionUID = 1L;
      
      @OneToMany(cascade = CascadeType.ALL)
      @JoinTable(name = &quot;tomatoes_season_varieties&quot;, 
        joinColumns = {@JoinColumn(name = &quot;season_id&quot;, referencedColumnName = &quot;season_id&quot;)},
        inverseJoinColumns = {@JoinColumn(name = &quot;variety_specification_id&quot;, referencedColumnName = &quot;variety_specification_id&quot;)})
      @MapKeyJoinColumn(name = &quot;variety_id&quot;)
      @Column(nullable = false)
      private Map&lt;TomatoesVariety, TomatoesVarietySpecification&gt; 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&lt;TomatoesVariety, TomatoesVarietySpecification&gt; tomatoesSeasonVarieties) {
          super(id, year);
          this.tomatoesSeasonVarieties = tomatoesSeasonVarieties;
      }
  
      public TomatoesSeason(Integer year, Map&lt;TomatoesVariety, TomatoesVarietySpecification&gt; tomatoesSeasonVarieties) {
          super(year);
          this.tomatoesSeasonVarieties = tomatoesSeasonVarieties;
      }
  
      public Map&lt;TomatoesVariety, TomatoesVarietySpecification&gt; getTomatoesSeasonVarieties() {
          return tomatoesSeasonVarieties;
      }
  
      public void setTomatoesSeasonVarieties(Map&lt;TomatoesVariety, TomatoesVarietySpecification&gt; 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 result;
      }
  
      @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 &quot;Id=&quot; + getId() + &quot;, Year=&quot; + getYear() + &quot;, TomatoesSeasonVarieties=&quot; + tomatoesSeasonVarieties;
      }
  }
  </code></pre>
  <p>abstract <code>Variety</code> class:</p>
  <pre><code>@Entity
  @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  public abstract class Variety {
  
      @Id
      @SequenceGenerator(name = &quot;varietySequence&quot;, sequenceName = &quot;sequence_variety&quot;, allocationSize = 1, initialValue = 1 )
      @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = &quot;varietySequence&quot;)
      @Column(name = &quot;variety_id&quot;)
      private Integer id;
  
      @Column(nullable = false)
      @NotBlank(message = &quot;Значение поля не может быть пустым!&quot;)
      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 result;
      }
  
      @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;
      }
  }
  </code></pre>
  <p>and <code>Tomatoes Variety</code> class:</p>
  <pre><code>@Entity
  @Table(name = &quot;tomatoes_variety&quot;)
  public class TomatoesVariety extends Variety implements Serializable {
      private static final long serialVersionUID = 1L;
      
      @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = &quot;variety&quot;)
      @Column(nullable = false)
      private Set&lt;TomatoesHarvesting&gt; tomatoes;
      
      @ManyToOne(cascade = CascadeType.ALL)
      @JoinColumn(name = &quot;season_id&quot;)
      private TomatoesSeason tomatoesSeason;
  
      public TomatoesVariety() {
          super();
      }
  
      public TomatoesVariety(Integer id, String name) {
          super(id, name);
      }
  
      public TomatoesVariety(String name) {
          super(name);
      }
  
      public Set&lt;TomatoesHarvesting&gt; getTomatoes() {
          return tomatoes;
      }
  
      public void setTomatoes(Set&lt;TomatoesHarvesting&gt; 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 result;
      }
  
      @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 &quot;Id=&quot; + getId() + &quot;, Name=&quot; + getName();
      }
  }
  </code></pre>
  <p>abstract <code>Variety Specification</code> class:</p>
  <pre><code>@Entity
  @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  public abstract class VarietySpecification {
  
      @Id
      @SequenceGenerator(name = &quot;varietySpecificationSequence&quot;, sequenceName = &quot;sequence_variety_specification&quot;, allocationSize = 1, initialValue = 1)
      @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = &quot;varietySpecificationSequence&quot;)
      @Column(name = &quot;variety_specification_id&quot;)
      private Integer id;
  
      @Column(nullable = false)
      @NotBlank(message = &quot;Значение поля не может быть пустым!&quot;)
      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 result;
      }
  
      @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;
      }
  }
  </code></pre>
  <p>and <code>Tomatoes Variety Specification</code> class:</p>
  <pre><code>@Entity
  @Table(name = &quot;tomatoes_variety_specification&quot;)
  public class TomatoesVarietySpecification extends VarietySpecification implements Serializable {
      private static final long serialVersionUID = 1L;
  
      @ManyToOne(cascade = CascadeType.ALL)
      @JoinColumn(name = &quot;variety_id&quot;)
      private TomatoesVariety tomatoesVariety;
      
      @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = &quot;tomatoesVarietySpecification&quot;)
      @Column(nullable = false)
      private Set&lt;TomatoesGardenbed&gt; 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&lt;TomatoesGardenbed&gt; tomatoesGardenbeds) {
          super(id, customId);
          this.tomatoesGardenbeds = tomatoesGardenbeds;
      }
  
      public TomatoesVarietySpecification(String customId, Set&lt;TomatoesGardenbed&gt; tomatoesGardenbeds) {
          super(customId);
          this.tomatoesGardenbeds = tomatoesGardenbeds;
      }
  
      public TomatoesVariety getTomatoesVariety() {
          return tomatoesVariety;
      }
  
      public void setTomatoesVariety(TomatoesVariety tomatoesVariety) {
          this.tomatoesVariety = tomatoesVariety;
      }
  
      public Set&lt;TomatoesGardenbed&gt; getTomatoesGardenbeds() {
          return tomatoesGardenbeds;
      }
  
      public void setTomatoesGardenbeds(Set&lt;TomatoesGardenbed&gt; 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 result;
      }
  
      @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 &quot;Id=&quot; + getId() + &quot;, CustomId=&quot; + getCustomId() + &quot;, TomatoesGardenbeds=&quot; + tomatoesGardenbeds;
      }
  }
  </code></pre>
  <p>and my controller create method:</p>
  <pre><code>    @PostMapping(&quot;/create&quot;)
      public String createTomatoesSeason(String refererURI, String superRefererURI, @Valid TomatoesSeason tomatoesSeason, @RequestParam(name = &quot;tomatoesVariety.id&quot;) TomatoesVariety tomatoesVariety, TomatoesVarietySpecification tomatoesVarietySpecification,  TomatoesGardenbed tomatoesGardenbed, BindingResult bindingResult, Model model) {
          if (bindingResult.hasErrors()) {
              Map&lt;String, String&gt; errors = ControllerUtils.getErrors(bindingResult);
              model.mergeAttributes(errors);
              model.addAttribute(&quot;refererURI&quot;, refererURI);
  
              if (superRefererURI != &quot;&quot;) {
                  model.addAttribute(&quot;superRefererURI&quot;, superRefererURI);
              }
  
              return &quot;tomatoesSeasonCreator&quot;;
          }
  
          tomatoesVarietySpecification.setTomatoesGardenbeds(new HashSet&lt;TomatoesGardenbed&gt;(Collections.singleton(tomatoesGardenbed)));
          HashMap&lt;TomatoesVariety, TomatoesVarietySpecification&gt; tomatoesSeasonVarieties = new HashMap&lt;TomatoesVariety, TomatoesVarietySpecification&gt;();
          tomatoesSeasonVarieties.put(tomatoesVariety, tomatoesVarietySpecification);
          tomatoesSeason.setTomatoesSeasonVarieties(tomatoesSeasonVarieties);
          boolean tomatoesSeasonExists = tomatoesSeasonService.createTomatoesSeason(tomatoesSeason);
          
          if (tomatoesSeasonExists) {
              model.addAttribute(&quot;seasonExistsMessage&quot;, &quot;Such season already exists!&quot;);
              model.addAttribute(&quot;refererURI&quot;, refererURI);
              
              if (superRefererURI != &quot;&quot;) {
                  model.addAttribute(&quot;superRefererURI&quot;, superRefererURI);
              }
              
              return &quot;tomatoesSeasonCreator&quot;;
          }
          
          if (superRefererURI != &quot;&quot;) {
              return &quot;redirect:&quot; + refererURI + &quot;?superRefererURI=&quot; + superRefererURI;
          }
          
          return &quot;redirect:&quot; + refererURI;
      }
  </code></pre>
  <p>and <code>Tomatoes Season Service</code> class:</p>
  <pre><code>@Service
  public class TomatoesSeasonService {
      Logger logger = LoggerFactory.getLogger(TomatoesSeasonService.class);
      
      @Autowired
      private TomatoesSeasonRepository tomatoesSeasonRepository;
  
      public List&lt;TomatoesSeason&gt; findAll() {
          logger.trace(&quot;Getting all tomatoes seasons from database...&quot;);
          
          return tomatoesSeasonRepository.findAll();
      }
  
      public boolean checkIfExists(TomatoesSeason tomatoesSeason) {
          logger.trace(&quot;Checking if stored tomatoes season already exists in database...&quot;);
          
          Optional&lt;TomatoesSeason&gt; tomatoesSeasonFromDb = tomatoesSeasonRepository.findByYear(tomatoesSeason.getYear());
      
          if (tomatoesSeasonFromDb.isPresent() &amp;&amp; tomatoesSeason.getId() != tomatoesSeasonFromDb.get().getId()) {
              logger.warn(&quot;Tomatoes season of \&quot;&quot; + tomatoesSeasonFromDb.get().getYear() + &quot;\&quot; year already exists in database...&quot;);
              return true;
          }
          return false;
      }
      
      public boolean createTomatoesSeason(TomatoesSeason tomatoesSeason) {
          logger.trace(&quot;Adding new tomatoes season to database...&quot;);
          
          if (checkIfExists(tomatoesSeason))
              return false;
  
          logger.trace(&quot;Saving new tomatoes season in database...&quot;);
          tomatoesSeasonRepository.save(tomatoesSeason);
          return true;
      }
  
      public boolean updateTomatoesSeason(TomatoesSeason tomatoesSeason) {
          logger.trace(&quot;Updating tomatoes season in database...&quot;);
          
          if (checkIfExists(tomatoesSeason))
              return false;
          
          logger.trace(&quot;Saving updated tomatoes season in database...&quot;);
          tomatoesSeasonRepository.save(tomatoesSeason);
          return true;
      }
  
      public void deleteTomatoesSeason(TomatoesSeason tomatoesSeason) {
          logger.trace(&quot;Deleting tomatoes season from database...&quot;);
          
          tomatoesSeasonRepository.delete(tomatoesSeason);
      }
  }
  </code></pre>