Mapstruct throw exception when using both Mapping with and without qualifiedByName option - spring-boot

I have an interface mapper for profile:
#Mapper(componentModel = "spring", builder = #Builder(disableBuilder = true))
public interface SellerProfileMapper {
#Mapping(target = "companyProfileDTO.companyProfileId", source = "id")
#Mapping(target = "companyProfileDTO.companyPicture", source = "company.picture", qualifiedByName = "buildBase64EncodingProfilePicture")
SellerProfileResponseDTO entityToSellerProfileResponseDTO(SellerProfileV2 sellerProfileV2);
#Named("buildBase64EncodingProfilePicture")
default String buildBase64EncodingProfilePicture(Image picture) {
return ofNullable(picture)
.map(image -> Base64.getEncoder().encodeToString(image.getContent()))
.orElse(null);
}
The problem is that the implementation for this mapper can only work if I remove either
#Mapping(target = "companyProfileDTO.companyProfileId", source = "id")
or this
#Mapping(target = "companyProfileDTO.companyPicture", source = "company.picture", qualifiedByName = "buildBase64EncodingProfilePicture")
then the implementation is generated! The question is why? Did I miss anything?
This is the DTO:
#Data
#Builder
#AllArgsConstructor
public class SellerProfileResponseDTO {
private CompanyProfileDTO companyProfileDTO;
private SellerProfileDTO sellerProfileDTO;
}
#NoArgsConstructor
#AllArgsConstructor
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#Schema(description = "Being used as a based class for Adviser, Buyer and Seller company profile.")
public class CompanyProfileDTO {
#Schema(description = "Company logo as a base64 img", type = "String")
private String companyPicture;
private String companyPictureFileName;
private String companyProfileId;
private String companyId;
}
this is the entity I want to map:
public class SellerProfileV2 {
private String id;
private String alias;
private Company company;
...
}
The error that I get is:
Internal error in the mapping processor: java.lang.RuntimeException: org.ma pstruct.ap.shaded.freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
public interface SellerProfileMapper {
^
==> ext.targetBeanName [in template "org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl" at line 31, column 12]
----
Tip: It's the step after the last dot that caused this error, not those before it.
----
Tip: If the failing expression is known to be legally refer to something that's null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (Thes
e only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)?
This is the generated class for the mapper:
#Component
public class SellerProfileMapperImpl implements SellerProfileMapper {
#Override
public SellerProfileResponseDTO entityToSellerProfileResponseDTO(SellerProfileV2 sellerProfileV2) {
if ( sellerProfileV2 == null ) {
return null;
}
CompanyProfileDTO companyProfileDTO = null;
CompanyProfileDTO companyProfileDTO = null;
if ( sellerProfileV2.getCompany() != null ) {
if ( FreeMarker template error:
The following has evaluated to null or missing:
==> ext.targetBeanName [in template "org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl" at line 31, column 12]
----
Tip: It's the step after the last dot that caused this error, not those before it.

Related

Problem when attempting a saveAndFlush commit (JPA ) when primary key is auto-generated from postGres trigger

I am using spring JPA to attempt to write records to a postGres DB. At the time of the commit, I am getting the following error:
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "col_id" violates not-null constraint
Detail: Failing row contains (null, null, null, null, null)
I have the following repository interface:
public interface MyRepo extends JpaRepository <MyModel, String> {
}
, the following model class:
#Entity
#Validated
#Table(name = "my_table", schema="common")
public class MyModel {
#Id
#Column(name = "col_id")
private String id;
#Column(name = "second_col")
private String secCol;
#Column(name = "third_col")
private String thirdCol;
#Column(name = "fourth_col")
private String fourthCol;
#Column(name = "fifth_col")
private String fifthCol;
public MyModel() {
}
public MyModel(String id, String secCol, String thirdCol, String fourthCol, String fifthCol) {
this.id = id;
this.secCol = secCol;
this.thirdCol = thirdCol;
this.fourthCol = fourthCol;
this.fifthCol = fifthCol;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSecCol() {
return secCol;
}
public void setSecCol(String secCol) {
this.secCol = secCol;
}
public String getThirdCol() {
return thirdCol;
}
public void setThirdCol(String thirdCol) {
this.thirdCol = thirdCol;
}
public String getFourthCol() {
return fourthCol;
}
public void setFourthCol(String fourthCol) {
this.fourthCol = fourthCol;
}
public String getFifthCol() {
return fifthCol;
}
public void setFifthCol(String fifthCol) {
this.fifthCol = fifthCol;
}
}
, and the relevant part of the service class:
public MyModel myModel (MyModel myModel) {
MyModel mm = null;
try {
mm = myRepo.saveAndFlush(myModel);
} catch ( Exception e) {
e.printStackTrace();
}
return mm;
}
UPDATE:
I finally realized that my problem is due to a database trigger that auto-generates primary key against a complex business rule. Also, I assume I might need to use a custom save method rather than the default repo.saveAndFlush? I would be grateful for any ideas given new information. Thanks!
I reproduced the exact same code in a test project with Postgres and it worked well for me. You are absolutely correct that the values of the model class are not populated. You must share your controller also. It may really help me to help you if I can get a look where your service is being called from. Only that will help me to deduce why your model values are being passed as null in the service call.

Generate UUID key using CypherdslStatementExecutor

I am using neo4j-cypher-dsl-codegen-sdn6 to generate a static model to use in Cypher-DSL.
This is my source #Node
#Node
class Person(
#Id
#GeneratedValue(GeneratedValue.UUIDGenerator::class)
var id: UUID?,
val name: String,
var born: Int
) {
#JsonCreator
constructor(#JsonProperty("name") name: String, #JsonProperty("born") born: Int) : this(null, name, born) {
}
}
Generated static model
#Generated(
value = "org.neo4j.cypherdsl.codegen.core.NodeImplBuilder",
date = "2022-03-03T14:26:04.213265104+02:00",
comments = "This class is generated by the Neo4j Cypher-DSL. All changes to it will be lost after regeneration."
)
public final class Person_ extends NodeBase<Person_> {
public static final Person_ PERSON = new Person_();
public final Property ID = this.property("id");
public final Property NAME = this.property("name");
public final Property BORN = this.property("born");
public Person_() {
super("Person");
}
private Person_(SymbolicName symbolicName, List<NodeLabel> labels, Properties properties) {
super(symbolicName, labels, properties);
}
#Override
public Person_ named(SymbolicName newSymbolicName) {
return new Person_(newSymbolicName, getLabels(), getProperties());
}
#Override
public Person_ withProperties(MapExpression newProperties) {
return new Person_(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties));
}
}
Repository
interface PeopleRepository : Neo4jRepository<Person, UUID>, CypherdslStatementExecutor<Person?>
Failing test
#Test
fun createPerson(#Autowired peopleRepository: PeopleRepository) {
val person: Person_ = Person_.PERSON.withProperties(
Person_.PERSON.NAME, Cypher.literalOf<String>("Micheal"),
Person_.PERSON.BORN, Cypher.literalOf<Int>(1999)
)
val result: Optional<Person?> = peopleRepository.findOne(Cypher.create(person).returning(person).build())
assertNotNull(result.get().id)
}
I would have expected that the UUID gets generated.
Please can someone try to replicate this error, or advise if there is another way to generate the UUID.

What is the ideal way to serialize and deserialize polymorphic entity attribute in spring boot?

I have an Entity class with a column attribute whose type is an abstract class. I want to serialize (object to JSON string) while saving it in the database column and deserialize it into an abstract class (which in turn converts the string to the appropriate concrete class) when it is retrieved from the database.
Here's how I accomplished it:
ProductEntity.java
#Entity
#Table(name="PRODUCT")
#Data
public class ProductEntity{
#Id
#Column(name = "ID", insertable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger id;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "NAME")
private String name;
#Column(name = "PRODUCT_TYPE")
private String productType;
#Column(name = "PRODUCT_SPECS")
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property =
"productType") // -------------------> Map to concrete class based on productType value
#Convert(converter = ObjectConverter.class) // ------------> custom converter
private ProductSpecification productSpec;
}
NOTE : "PRODUCT_SPECS" database column is of JSON type.
ProductSpecification.java
#NoArgsConstructor
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.WRAPPER_OBJECT,
#JsonSubTypes({
#JsonSubTypes.Type(value = ComputerSpecification.class, name = "computer"),
#JsonSubTypes.Type(value = SpeakerSpecification.class, name = "speaker")
})
public abstract class ProductSpecification{ }
ComputerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class ComputerSpecification extends ProductSpecification {
String memory;
String displaySize;
String processor;
#JsonCreator
public ComputerSpecification (#JsonProperty("memory") String memory,
#JsonProperty("displaysize") String displaySize,
#JsonProperty("processor") String processor){
super();
this.memory = memory;
this.displaySize = displaySize;
this.processor = processor;
}
}
SpeakerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class SpeakerSpecification extends ProductSpecification {
String dimension;
String sensitivity;
String bassPrinciple;
String amplifierPower;
#JsonCreator
public SpeakerSpecification (#JsonProperty("sensitivity") String sensitivity,
#JsonProperty("dimension") String dimension,
#JsonProperty("bassPrinciple") String bassPrinciple,
#JsonProperty("amplifierPower") String amplifierPower){
super();
this.sensitivity = sensitivity;
this.dimension = dimension;
this.bassPrinciple = bassPrinciple;
this.amplifierPower = amplifierPower;
}
}
ObjectConverter.java
NOTE: I am using Jackson ObjectMapper for serialization and deserialization.
public class ObjectConverter implements AttributeConverter<Object, String>{
private final static Logger LOGGER = LoggerFactory.getLogger(ObjectConverter.class);
private static final ObjectMapper mapper;
static {
mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
}
#Override
public String convertToDatabaseColumn(Object attributeObject) {
if (attributeObject == null) {
return "";
}
try {
return mapper.writeValueAsString(attributeObject);
} catch (JsonProcessingException e) {
LOGGER.error("Could not convert to database column", e);
return null;
}
}
#Override
public Object convertToEntityAttribute(String dbColumnValue) {
try {
if (StringUtils.isBlank(dbColumnValue)) {
return null;
}
return mapper.readValue(dbColumnValue, ProductSpecification.class); // ----> mapped to
abstract class
} catch (Exception e) {
LOGGER.error("Could not convert to entity attribute", e);
return null;
}
}
}
Request body 1:
{
"name" : "Bose Bass Module 700 - Black- Wireless, Compact Subwoofer",
"description" : "This wireless, compact subwoofer is designed to be paired with the Bose sound
bar 700 to bring music, movies, and TV to life with Deep, dramatic bass. ",
"productSpec" : {
"sensitivity" : "90 dB",
"bassPrinciple" : "reflex",
"amplifierPower" : "700 watts",
"dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD"
}
}
This request gets saved in the database column "PRODUCT_SPECS" as :
{".SpeakerSpecification ":{"sensitivity" : "90 dB","bassPrinciple" : "reflex", "amplifierPower" :"700
watts", "dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD" }}
Now this solution works perfectly fine. The "SpeakerSpecification " key neither appears in the response of GET API call nor in the swagger doc. But having to store the type info in the database really bothers me.
Is there a better approach to this problem where I could avoid having the typeinfo (".SpeakerSpecification ") in the column value?

Jackson Inheritance

Can't seem to figure this out. I keep getting various errors so I'll just write this with the current error I'm getting from Jackson.
public class ResponseDetail {
private Response response;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = ResponseTypeOne.class, name = "ResponseTypeOne"),
#JsonSubTypes.Type(value = ResponseTypeTwo.class, name = "ResponseTypeTwo"),
#JsonSubTypes.Type(value = ResponseTypeThree.class, name = "ResponseTypeThree")
})
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class Response {
}
In other packages I have these three:
public class ResponseTypeOne extends Response {
private Integer status;
}
public class ResponseTypeTwo extends Response {
private String message;
}
public class ResponseTypeThree extends Response {
private String value;
}
Error:
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.services.models.Response]: missing type id property '#type' (for POJO property 'response')
I have tried various iterations of this #JsonTypeInfo with various includes and various property's also with Id.CLASS with no luck.
You need to declare how the type should be recognized.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "#ttype")
#JsonSubTypes({
#JsonSubTypes.Type(value = ResponseTypeOne.class, name = "ResponseTypeOne"),
#JsonSubTypes.Type(value = ResponseTypeTwo.class, name = "ResponseTypeTwo"),
#JsonSubTypes.Type(value = ResponseTypeThree.class, name = "ResponseTypeThree")
})
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class Response {
#JsonProperty("#ttype")
public abstract String getChildType();
}
And in child types do like below:
#JsonTypeName("ResponseTypeOne")
public class ResponseTypeOne extends Response {
#Override
public String getChildType() {
return "ResponseTypeOne";
}
}
And incoming json should be like below to enable jackson to find the correct child implementation:
{
//some attributes of child Response
"#ttype": "ResponseTypeOne"
}

No composite key property found for type error in Spring JPA2

I have an error in spring JPA
org.springframework.data.mapping.PropertyReferenceException: No property CompanyId found for type CompanyUserDetail!
#Embeddable
public class CompanyUserKey implements Serializable {
public CompanyUserKey() {
}
#Column(name = "company_id")
private UUID companyId;
#Column(name = "user_name")
private String userName;
public UUID getCompanyId() {
return companyId;
}
public void setCompanyId(UUID companyId) {
this.companyId = companyId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
#Entity
#Table(name = "company_user_detail")
public class CompanyUserDetail {
#EmbeddedId
CompanyUserKey companyUserkey;
public CompanyUserKey getCompanyUserkey() {
return companyUserkey;
}
public void setCompanyUserkey(CompanyUserKey companyUserkey) {
this.companyUserkey = companyUserkey;
}
}
I am trying to access below method Service layer
#Component
public interface CompanyUserRepository extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
public List<CompanyUserDetail> findByCompanyId(UUID companyId);
}
How can I achieve this ?
Thanks
Since in java model your CompanyUserKey is a property in the CompanyUserDetail class, I believe you should use full path (companyUserkey.companyId) to reach companyId:
public List<CompanyUserDetail> findByCompanyUserkeyCompanyId(UUID companyId);
Also note that you have a naming inconsistency: field in CompanyUserDetail is named companyUserkey instead of companyUserKey.
Assuming you are not using spring-data-jpa's auto generated implementations, your method contents might look something like the following:
FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId
Now simply provide that query to the EntityManager
entityManager.createQuery( queryString, CompanyUserDetail.class )
.setParameter( "companyId", companyId )
.getResultList();
The key points are:
Query uses a named bind parameter called :companyId (not the leading :).
Parameter values are bound in a secondary step using setParameter method variants.
createQuery uses a second argument to influence type safety so that the return value from getResultList is a List<CompanyUserDetail> just like you requested.
Looking at spring-data-jpa's implementation however, I suspect it could look like this:
public interface CustomerUserRepository
extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
#Query("select c FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId")
List<CompanyUserDetail> findByCompanyId(#Param("companyId") UUID companyId);
}

Resources