使用 mapstruct 映射休眠实体



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

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

我插入了 id 为"1"的父记录。现在,当我尝试为父项插入一个孩子时,它会抛出

org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: 对象引用 未保存的瞬态实例 - 之前保存瞬态实例 flushing : nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: 对象引用 未保存的瞬态实例 - 之前保存瞬态实例 冲洗:

那么我如何指示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() + "'" + "}";
}
}

您要查找的内容可能类似于将映射目标类型传递给自定义映射器或按 ID 查找实体的示例。

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

假设你有ParentRepository,里面有findById(String id)

因此,您将使用ParentRepository,而不是在ChildMapper中使用ParentMapper。然后,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方法。

如果要获取实体以进行更新,则需要使用对象工厂。您还需要将ChildFactory放在子映射器使用注释中。

例如,为了获取孩子以执行更新,您可以拥有如下内容:

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

最新更新