Reputation: 1738
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.
Upvotes: 0
Views: 2981
Reputation: 1738
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.
Upvotes: 2
Reputation: 16452
The exception is related to Spring Data or WebMvc not being able to convert values. Mixing generated identifiers with other identifiers is not really possible. Why do you need both values anyway? It makes IMO no sense to have a composite id here. Just use this:
@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;
@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;
}
Upvotes: 1