Issue
I cannot get @GeneratedValue to work with @IdClass if it includes a foreign key from another entity.
So what I have is an Option entity that looks like this
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "options")
public class Option extends UserDateAudit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "option_id")
private Long optionId;
@NotBlank
@Column(nullable = false)
private String name;
//one to many with optionValues entity
@OneToMany(mappedBy = "option", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<OptionValue> optionValues;
@OneToMany(mappedBy = "option", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<ProductOption> optionProducts;
}
and an OptionValue Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "option_values")
@IdClass(OptionValueId.class)
public class OptionValue extends UserDateAudit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "option_value_id")
private Long optionValueId;
@NotBlank
@Column(nullable = false)
private String valueName;
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "option_id", referencedColumnName = "option_id")
private Option option;
@OneToMany(mappedBy = "optionValue", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<VariantValue> variantValues;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OptionValueId implements Serializable {
private Long optionValueId;
private Option option;
}
and I try to save it
public ResponseEntity<OptionValue> create(Long optionId, OptionValueCreateDto optionValueCreateDto) {
Option option = optionRepository.findById(optionId).orElseThrow(
() -> new EntityNotFoundException("errors.option.notFound")
);
OptionValue optionValue = ObjectMapperUtils.map(optionValueCreateDto, OptionValue.class);
optionValue.setOption(option);
optionValue = optionValueRepository.save(optionValue);
return new ResponseEntity<>(optionValue, HttpStatus.CREATED);
}
but I get the following exception
Resolved [org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.Long' to required type 'com.ecommerce.product.model.Option' for property 'option'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Long' to required type 'com.ecommerce.product.model.Option' for property 'option': no matching editors or conversion strategy found]
and I cannot figure out what is wrong here
I also tried making my IdClass like this
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OptionValueId implements Serializable {
@Column(name = "option_value_id")
private Long optionValueId;
@Column(name = "option_id")
private Long option;
}
but it did not work as well and showed a similar exception
Edit 1 It turns out it has to be a compound key as this compound key is related used in another table which caused a lot of issues to remove the validation the compound key provides.
maybe I should have clarified that in the first place.
Solution
So here's the full explanation for what I did.
the Option
class stays the same
but updated the OptionValue
1- Option Value entity
package com.ecommerce.product.model;
import com.ecommerce.product.model.helper.OptionValueId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Set;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "option_values")
@IdClass(OptionValueId.class)
@SequenceGenerator(name="OPTION_VALUE", sequenceName="OPTION_VALUE_GENERATOR")
public class OptionValue implements Serializable {
private static final long serialVersionUID = -1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="OPTION_VALUE")
@Column(name = "option_value_id")
private Long optionValueId;
@Id
@Column(name = "option_id")
private Long optionId;
@NotBlank
@Column(nullable = false)
private String valueName;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "option_id", insertable = false, updatable = false)
@NotNull
private Option option;
@OneToMany(mappedBy = "optionValue", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<VariantValue> variantValues;
}
The difference here is the in the @GeneratedValue()
. Using the GenerationType.IDENTITY
is not valid when using a compound key it has to be GenerationType.SEQUENCE
to work well.
in this case providing a generator would save you from letting hibernate use its global one which will be shared with all entities using SEQUENCE
generator.
@SequenceGenerator(name="OPTION_VALUE", sequenceName="OPTION_VALUE_GENERATOR")
It just doesn't work with IDENTITY
. You can check this issue as well as this one.
2- The id class
a simple Id class would be like following
@Embeddable
@Data
@NoArgsConstructor
public class OptionValueId implements Serializable {
private static final long serialVersionUID = -1L;
private Long optionValueId;
private Long optionId;
}
3- Saving the entity
public ResponseEntity<OptionValueDto> create(Long optionId, OptionValueCreateDto optionValueCreateDto) {
Option option = optionRepository.findById(optionId).orElseThrow(
() -> new EntityNotFoundException("errors.option.notFound")
);
OptionValue optionValue = ObjectMapperUtils.map(optionValueCreateDto, OptionValue.class);
optionValue.setOption(option);
optionValue.setOptionId(optionId);
optionValue = optionValueRepository.save(optionValue);
return new ResponseEntity<>(ObjectMapperUtils.map(optionValue, OptionValueDto.class), HttpStatus.CREATED);
}
** NOTE that you provide the optionId
as well the option
but not the optionValueId
4- The entity that uses the compound Id for a relation
package com.ecommerce.product.model;
import com.ecommerce.product.model.helper.VariantValueId;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@Data
@NoArgsConstructor
@Entity
@Table(name = "variant_values")
@IdClass(VariantValueId.class)
public class VariantValue implements Serializable {
private static final long serialVersionUID = -1L;
@Id
@Column(name = "product_variant_id")
private Long productVariantId;
@Id
@Column(name = "option_id")
private Long optionId;
@Id
@Column(name = "product_id")
private Long productId;
@Column(name = "option_value_id")
private Long optionValueId;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumns({
@JoinColumn(name = "product_id", referencedColumnName = "product_id", insertable = false, updatable = false),
@JoinColumn(name = "product_variant_id", referencedColumnName = "product_variant_id", insertable = false, updatable = false)
})
private ProductVariant productVariant;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumns({
@JoinColumn(name = "option_id", referencedColumnName = "option_id", insertable = false, updatable = false),
@JoinColumn(name = "product_id", referencedColumnName = "product_id", insertable = false, updatable = false)
})
private ProductOption productOption;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumns({
@JoinColumn(name = "option_id", referencedColumnName = "option_id", insertable = false, updatable = false),
@JoinColumn(name = "option_value_id", referencedColumnName = "option_value_id", insertable = false, updatable = false)
})
@NotNull
private OptionValue optionValue;
}
Saving the last entity would be by providing instances of the compound Ids.
Answered By - Omar Abdelhady