hibernate - 使用 mapstruct 映射 Hibernate 实体

标签 hibernate spring-boot spring-data-jpa mapstruct

我是这些技术的新手,所以提前道歉。

我在我的应用程序中使用 springboot、Spring JPA、hibernate 和 mapstruct。

我已插入 id 为“1”的父记录。现在,当我尝试为 parent 插入一个 child 时,它会抛出

org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing :



那么我如何指示mapstruct生成代码,以便它获取父记录,插入创建一个新对象,我猜这会导致上述问题。
    /**
     * Mapper class for the entity {@link Child} and its corresponding data transfer object {@link ParentTlDTO}.
     */
    @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.WARN, uses = {ParentMapper.class})
    public interface ChildMapper extends EntityMapper<ChildDTO, Child> {

      @Mapping(source = "parent.id", target = "parentId")
      @Override
      ChildDTO toDto(Child child);

      @Mapping(source = "parentId", target = "parent.id")
      @Override
      Child toEntity(ChildDTO childDTO);

      default Child fromId(String id) {
        if (id == null) {
          return null;
        }
        Child Child = new Child();
        Child.setId(id);
        return Child;
      }
    }


    @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = {})
    public interface ParentMapper extends EntityMapper<ParentDTO, Parent> {

      @Mapping(target = "childs", ignore = true)
      Parent toEntity(ParentDTO parentDTO);

      default Parent fromId(String id) {
        if (id == null) {
          return null;
        }
        Parent parent = new Parent();
        parent.setId(id);
        return parent;
      }
    }

----- 模型类 ----
 @ApiModel(description = "Child holds the descriptive information of currencies translated into various languages supported by the application.")
 @Entity
 @Table(name = "child")
 @Cache(usage = CacheConparentStrategy.NONSTRICT_READ_WRITE)
 @Document(indexName = "child")
 public class Child extends AbstractEntity {

   private static final long serialVersionUID = 6419585975683709213L;

   /**
    * Identifier of the descriptive information.
    */
   @Id
   @GeneratedValue(generator = "system-uuid")
   @GenericGenerator(name = "system-uuid", strategy = "uuid2")
   @Column(name = "id", columnDefinition = "CHAR", length = 36, nullable = false)
   private String id;

   /**
    * Name of the parent.
    */
   @NotNull
   @Size(min = 3, max = 120)
   @Column(name = "entity_name", columnDefinition = "VARCHAR", length = 120, nullable = false)
   private String entityName;

   /**
    * Brief description about the parent.
    */
   @Size(max = 256)
   @Column(name = "entity_desc", columnDefinition = "VARCHAR", length = 256, nullable = true)
   private String entityDesc;

   @ManyToOne(fetch = FetchType.EAGER)
   @JsonIgnoreProperties("childs")
   private Parent parent;

   @ManyToOne(fetch = FetchType.EAGER)
   private Language language;

   /**
    * Callback method that is triggered before persisting the descriptive information of the parent where default
    * values are set for mandatory attributes and values are massaged without compromising on data integrity.
    */
   @PrePersist
   public void setDefaultValues() {
     this.entityName = StringUtils.capitalize(StringUtils.trim(this.entityName));
     this.entityDesc = StringUtils.trim(this.entityDesc);
   }

   /**
    * @return the id
    */
   public String getId() {
     return id;
   }

   /**
    * @param id the id to set
    */
   public void setId(String id) {
     this.id = id;
   }

   /**
    *
    * @return
    */
   public String getEntityName() {
     return entityName;
   }

   /**
    *
    * @param entityName
    * @return
    */
   public Child entityName(String entityName) {
     this.entityName = entityName;
     return this;
   }

   /**
    *
    * @param entityName
    */
   public void setEntityName(String entityName) {
     this.entityName = entityName;
   }

   /**
    *
    * @return
    */
   public String getEntityDesc() {
     return entityDesc;
   }

   /**
    *
    * @param entityDesc
    * @return
    */
   public Child entityDesc(String entityDesc) {
     this.entityDesc = entityDesc;
     return this;
   }

   /**
    *
    * @param entityDesc
    */
   public void setEntityDesc(String entityDesc) {
     this.entityDesc = entityDesc;
   }

   /**
    *
    * @return
    */
   public Parent getParent() {
     return parent;
   }

   /**
    *
    * @param parent
    * @return
    */
   public Child parent(Parent parent) {
     this.parent = parent;
     return this;
   }

   /**
    *
    * @param parent
    */
   public void setParent(Parent parent) {
     this.parent = parent;
   }

   public Language getLanguage() {
     return language;
   }

   public Child language(Language language) {
     this.language = language;
     return this;
   }

   public void setLanguage(Language language) {
     this.language = language;
   }

   @Override
   public boolean equals(Object o) {
     if (this == o) {
       return true;
     }
     if (o == null || getClass() != o.getClass()) {
       return false;
     }
     Child child = (Child) o;
     if (child.getId() == null || getId() == null) {
       return false;
     }
     return Objects.equals(getId(), child.getId());
   }

   @Override
   public int hashCode() {
     return Objects.hashCode(getId());
   }

   @Override
   public String toString() {
     return "Child{"
       + "id=" + getId()
       + ", entityName='" + getEntityName() + "'"
       + ", entityDesc='" + getEntityDesc() + "'"
       + "}";
   }
 }



@ApiModel(description = "Parent class holds the primary definition of a parent such as parent code, parent symbol etc.")
@Entity
@Table(name = "parent")
@Cache(usage = CacheConparentStrategy.NONSTRICT_READ_WRITE)
@Document(indexName = "parent")
public class Parent extends AbstractEffectiveEntity {

  private static final long serialVersionUID = 5671242791959882244L;

  /**
   * Identifier of the parent.
   */
  @Id
  @GeneratedValue(generator = "system-uuid")
  @GenericGenerator(name = "system-uuid", strategy = "uuid2")
  @Column(name = "id", columnDefinition = "CHAR", length = 36, nullable = false)
  private String id;

  /**
   * Code assigned to the parent.
   */
  @NotNull
  @Size(min = 2, max = 5)
  @Column(name = "parent_code", columnDefinition = "CHAR", length = 5, nullable = false, updatable = false)
  private String parentCode;

  /**
   * Symbol associated with the parent.
   */
  @NotNull
  @Size(min = 2, max = 10)
  @Column(name = "parent_symbol", columnDefinition = "VARCHAR", length = 10, nullable = false, updatable = false)
  private String parentSymbol;

  /**
   * Flag to denote whether parent is installed in the product suite to support converting it to other currencies.
   */
  @Column(name = "installed_flag", columnDefinition = "CHAR", length = 1, nullable = false)
  private String installedFlag;

  /**
   * Bi-directional OneToMany is not the best and most efficient way to model a one to many relationship. But in this
   * case we prefer to keep a collection of descriptive information (children) in Parent (parent) to retrieve the
   * descriptive information via Parent, since the count of descriptive information shall be less (depending on the
   * number of languages supported in the application for translation). Limitations of this are (1) inability to limit
   * the number of descriptive information (Child) loaded and thus no support for pagination, (2) inability to sort
   * descriptive information since @OrderColumn annotation can be expensive.
   */
  @OneToMany(mappedBy = "parent")

  @Cache(usage = CacheConparentStrategy.NONSTRICT_READ_WRITE)
  private Set<Child> childs = new HashSet<>();

  /**
   * Callback method that is triggered before persisting the primary information of the parent where default values
   * are set for mandatory attributes and values of key attributes are massaged without compromising on data integrity.
   */
  @PrePersist
  public void setDefaultValues() {
    this.parentCode = StringUtils.upperCase(StringUtils.trim(this.parentCode));
    this.parentSymbol = StringUtils.trim(this.parentSymbol);
    this.installedFlag = StringUtils.upperCase(StringUtils.trim(this.installedFlag));
    if (StringUtils.isBlank(this.installedFlag)) {
      this.installedFlag = "N";
    }
  }

  /**
   * Returns the identifier of the parent.
   *
   * @return the parent identifier.
   */
  public String getId() {
    return id;
  }

  /**
   * Sets the identifier of the parent.
   *
   * @param id the identifier to be set for the parent.
   */
  public void setId(String id) {
    this.id = id;
  }

  /**
   * Returns the code associated with the parent.
   *
   * @return the parentCode
   */
  public String getParentCode() {
    return parentCode;
  }

  /**
   * Sets the code associated with the parent.
   *
   * @param parentCode the parentCode to set
   */
  public void setParentCode(String parentCode) {
    this.parentCode = parentCode;
  }

  /**
   * Sets the code of the parent in the Parent object.
   *
   * @param parentCode
   * @return parent the Parent object with parentCode set.
   */
  public Parent parentCode(String parentCode) {
    this.parentCode = parentCode;
    return this;
  }

  /**
   * Returns the symbol associated with the parent.
   *
   * @return the parentSymbol
   */
  public String getParentSymbol() {
    return parentSymbol;
  }

  /**
   * Sets the symbol associated with the parent.
   *
   * @param parentSymbol the parentSymbol to set
   */
  public void setParentSymbol(String parentSymbol) {
    this.parentSymbol = parentSymbol;
  }

  /**
   * Sets the symbol of the parent in the Parent object.
   *
   * @param parentSymbol
   * @return parent the Parent object with parentSymbol set.
   */
  public Parent parentSymbol(String parentSymbol) {
    this.parentSymbol = parentSymbol;
    return this;
  }

  /**
   * Return true if the parent is marked as installed in the product suite, otherwise false.
   *
   * @return the installedFlag
   */
  public String getInstalledFlag() {
    return installedFlag;
  }

  /**
   * Setter method to specify whether the parent is marked as installed in the product suite or not.
   *
   * @param installedFlag the installedFlag to set
   */
  public void setInstalledFlag(String installedFlag) {
    this.installedFlag = installedFlag;
  }

  /**
   * Sets the flag to denote whether the parent is installed in the product suite or note in the Parent object.
   *
   * @param installedFlag
   * @return the Parent object with installedFlag set.
   */
  public Parent installedFlag(String installedFlag) {
    this.installedFlag = installedFlag;
    return this;
  }

  /**
   *
   * @return
   */
  public Set<Child> getChilds() {
    return childs;
  }

  /**
   *
   * @param childs
   * @return
   */
  public Parent childs(Set<Child> childs) {
    this.childs = childs;
    return this;
  }

  /**
   *
   * @param child
   * @return
   */
  public Parent addChild(Child child) {
    this.childs.add(child);
    child.setParent(this);
    return this;
  }

  /**
   *
   * @param child
   * @return
   */
  public Parent removeChild(Child child) {
    this.childs.remove(child);
    child.setParent(null);
    return this;
  }

  /**
   *
   * @param childs
   */
  public void setChilds(Set<Child> childs) {
    this.childs = childs;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Parent parent = (Parent) o;
    if (parent.getId() == null || getId() == null) {
      return false;
    }
    return Objects.equals(getId(), parent.getId());
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(getId());
  }

  @Override
  public String toString() {
    return "Parent{" + "id=" + getId() + ", parentCode='" + getParentCode() + "'" + ", parentSymbol='"
      + getParentSymbol() + "'" + ", installedFlag='" + getInstalledFlag() + "'" + "}";
  }

}

最佳答案

您正在寻找的可能类似于 Passing the mapping target type to custom mapperslookup-entity-by-id例子。

您可以拥有一个抽象映射器,您可以在其中注入(inject)存储库并执行实体查找。或者您甚至可以将存储库作为 @Context 传递范围。

假设您有 ParentRepository在里面你有findById(String id)
所以不要使用 ParentMapper在您的 ChildMapper您将使用 ParentRepository .然后 MapStruct 将生成如下代码:

@Component
public class ChildMapperImpl implements ChildMapper {

    @Autowired
    private ParentRepository parentRepository;

    @Autowired
    private ChildFactory childFactory;


    @Override
    public Child toEntity(ChildDTO childDTO) {
        Child child = childFactory.create(childDTO);

        child.setParent(parentRepository.findById(childDTO.getParentId());
    }
}

您当然需要将映射调整为 @Mapping(source = "parentId", target = "parent")ChildMapper#toEntity方法。

如果您想获取实体进行更新,您需要使用 Object Factory .您需要输入 ChildFactory在子映射器中也使用注释。

例如,为了获取 child 以执行更新,您可以使用以下内容:
public class ChildFactory {

    protected final ChildRepository childRepository;

    public ChildFactory(ChildRepository childRepository) {
        this.childRepository = childRepository;
    }

    @ObjectFactory
    public Child create(ChildDTO childDTO) {
        Child child = childRepository.findById(childDTO.getId());
        return child == null ? new Child() : child;
    }
}

关于hibernate - 使用 mapstruct 映射 Hibernate 实体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51129128/

相关文章:

java - Spring JPA - 当数据库中有数据时,为什么我的 findAll 返回 null?

java - (Java/Spring)什么是 ".from()"? (与示例一起使用)

java - 如何使用jsf管理表单上的多对多关系

java - Hibernate 取消唯一化表中的列

java - Swagger Codegen 3 和 Spring HATEOAS

java - HEROKU - 无法使用 jdk11 部署 java 应用程序

java - 我可以将 Play 配置为使用 mysql 枚举而不是整数吗?

spring - 如何使用 spring 数据和基于 java 的配置创建 ddl 文件?

java - Spring Boot Actuator - 端点敏感性与安全启用

java - Spring 数据 JPA : Creating Specification Query Fetch Joins