Here is my folder structure:
In my IAppUserMapper I have a method to convert every AppUser entity instance to Data Transfer Object Model. Here is the code in IAppUserMapper interface:
import com.server.ecommerceapp.dto.AppUserDTO;
import com.server.ecommerceapp.model.AppUser;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
#Mapper
public interface IAppUserMapper {
IAppUserMapper appUserMapper = Mappers.getMapper(IAppUserMapper.class);
#Mapping(target = "username")
#Mapping(target = "email")
#Mapping(target = "password")
#Mapping(target = "roles", expression = "java(appUser.getRoles().stream().map(this::getRoleName).collect(Collectors.toList()))")
AppUserDTO toAppUserDTO(AppUser appUser);
default String getRoleName(Role role) {
return role.getRoleName();
}
}
And here is the MapperConfiguration class code where I configure both Product and user mappers:
import com.server.ecommerceapp.mapper.IAppUserMapper;
import com.server.ecommerceapp.mapper.IProductMapper;
import org.mapstruct.factory.Mappers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MapperConfiguration {
#Bean
public IAppUserMapper appUserMapper() {
return Mappers.getMapper(IAppUserMapper.class);
}
#Bean
public IProductMapper productMapper() {
return Mappers.getMapper(IProductMapper.class);
}
}
The error I get:
Error creating bean with name 'appUserMapper' defined in class path
resource
[com/server/ecommerceapp/configuration/MapperConfiguration.class]:
Bean instantiation via factory method failed; nested exception is
org.springframework.beans.BeanInstantiationException: Failed to
instantiate [com.server.ecommerceapp.mapper.IAppUserMapper]: Factory
method 'appUserMapper' threw exception; nested exception is
java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot
find implementation for com.server.ecommerceapp.mapper.IAppUserMapper
I was told I should make META-INF package in resources, with service package and the com.server.ecommerceapp.mapper.AppUserMapper txt with the content same as the name of the file, so that Spring can scan and find the package following the path:
src/main/resources/META-INF/service/com.server.ecommerceapp.mapper.AppUserMapper
but it didnt work. Any ideas how to solve this, and by the way, is it bad practise to start interface names with capital I cause Im coming from ASP?
Edit:
I added #Mapper(componentModel = "spring") to my interfaces and implemented them as DI with Autowired. I dont know if its related to that problem that I had but now I get error that it cant find collectors. Im trying to map a collection of Roles from AppUser to AppUserDTO. Here are both AppUser and AppUserDTO classes:
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Data
public class AppUser {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "email", nullable = false, unique = true)
private String email;
#Column(name = "password", nullable = false)
private String password;
#ManyToMany(fetch = EAGER)
private Collection<Role> roles;
}
And DTO:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class AppUserDTO {
private String username;
private String email;
private String password;
private Collection<String> roles;
}
So you're using Spring, but you are trying to not use Spring.
You should make your mappers use Spring component model:
#Mapper(componentModel = "spring")
public interface MyMapper {
Target map(Source source);
}
Check docs for dependency injection: https://mapstruct.org/documentation/stable/reference/html/#using-dependency-injection
Or do it with shared configuration: https://mapstruct.org/documentation/stable/reference/html/#shared-configurations
After that you can just #Autowired MyMapper myMapper; as any other Spring bean. No need to create instance in interface (the "Mappers.getMapper" thing) and no need to create mappers in java configuration, bean creation will be handled by framework.
#Mapping(target = "roles", expression = "java(appUser.getRoles().stream().map(this::getRoleName).collect(Collectors.toList()))")
now I get error that it cant find collectors
You are using an expression with Collectors class. As stated in the documentation https://mapstruct.org/documentation/stable/reference/html/#expressions:
Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the TimeAndFormat class (unless it’s used otherwise explicitly in the SourceTargetMapper). This can be resolved by defining imports on the #Mapper annotation.
So you either need to fully qualify java.util.stream.Collectors in your expression or set "imports" parameter in #Mapper annotation: #Mapper(imports = Collectors.class).
I would also say, you could just write a normal Java method for roles mapping and not be dealing with expressions. But that's up to your taste.
The file name of the service should be the interface and its content the implementation. You have named it by the implementation.
Related
There is entity:
#Getter
#Setter
#ToString()
#Entity
#Table
#Builder
#NoArgsConstructor
#AllArgsConstructor
class DocumentEntity implements Serializable {
(...)
#Enumerated(EnumType.STRING)
public DocumentStatus documentStatus;
}
I have serializable class:
#Data
#NoArgsConstructor
public class DocumentCriteria implements Serializable, Criteria {
private StringFilter documentStatus;
(...)
}
and auto generated class:
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(DocumentEntity.class)
public abstract class DocumentEntity_ {
public static volatile SingularAttribute<DocumentEntity, DocumentStatus> documentStatus;
public static final String DOCUMENT_STATUS = "documentStatus";
(...)
}
DocumentStatus is simple enum:
public enum DocumentStatus {
A,
B
}
I want to add specification that I search only entites with DocumentStatus set to A:
private Specification<DocumentEntity> createSpecification(DocumentCriteria criteria) {
Specification<DocumentEntity> specification = Specification.where(null);
if (criteria != null) {
StringFilter globalStringFilter = new StringFilter();
globalStringFilter.setContains(DocumentStatus.A.name());
specification.and(buildStringSpecification(globalStringFilter, DocumentEntity_.documentStatus));
I have an error here saying:
Required type:
SingularAttribute
<? super DocumentEntity,
String>
Provided:
SingularAttribute
<DocumentEntity,
DocumentStatus>
How can I search for it?
I tried also:
specification = specification.and(buildSpecification(criteria.getDocumentStatus(), DocumentStatus.A.name());
but it says:
Cannot resolve method 'buildSpecification(StringFilter, String)
Should i use other type than StringFilter even though database type is varchar ?
Does trying using String like: DocumentStatus.A.name() does not help here ?
Another option that comes in my head is writing something like that:
RangeFilter<DocumentStatus> globalStringFilter = new RangeFilter<DocumentStatus>();
List<DocumentStatus> documentStatuses = new ArrayList<>();
documentStatuses.add(DocumentStatus.A);
globalStringFilter.setIn(documentStatuses);
specification.and(buildRangeSpecification(globalStringFilter, DocumentEntity_.documentStatus));
And changing DocumentCriteria documentStatus field type to RangeFilter.
Above option does not seem to take effect when running application.
Solution:
RangeFilter<DocumentStatus> globalStringFilter = new RangeFilter<DocumentStatus>();
List<DocumentStatus> documentStatuses = new ArrayList<>();
documentStatuses.add(DocumentStatus.A);
globalStringFilter.setIn(documentStatuses);
specification=specification.and(buildRangeSpecification(globalStringFilter, DocumentEntity_.documentStatus));
And changing DocumentCriteria documentStatus field type to RangeFilter.
Make sure that specification= is present, so that result is consumed.
I want to create a new table every day with the full date as table name if the day is new create table for the that day
I have seen that when I change #Table(name="") name to a new string it make a new table but I can't automate this work
#Entity
#Table(name="orders_25_10_2021")
public class game2data {
#Id
private int cid;
private String name;
private Integer address;
private Integer gender;
private String date;
private String Done;
In simple words, I want to pass the name of #Table(name="") as a variable with dynamic according to date.
You can achieve this custom naming strategy by extending SpringPhysicalNamingStrategy and overriding its toPhysicalTableName method :
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.context.annotation.Configuration;
#Configuration
public class TableNamingStrategy extends SpringPhysicalNamingStrategy {
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd_MM_YYYY");
public static final PhysicalNamingStrategyStandardImpl INSTANCE =
new PhysicalNamingStrategyStandardImpl();
#Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
if (!name.getText().equals("game2data"))
return name;
StringBuilder customName =
new StringBuilder("orders").append('_').append(DATE_FORMATTER.format(LocalDate.now()));
return new Identifier(customName.toString(), name.isQuoted());
}
}
and registering it in your application.properties:
spring.jpa.hibernate.naming.physical-strategy=com.stackoverflow.questions.TableNamingStrategy
and removing the #Table annotation from the game2data entity.
This method is limited by the fact table names are determined at application start-up.
As a proof of concept, here's a way to update the table name every day by extending StatementInspector. By using this, you won't be able to read old data. You'll also have to use custom implementations of the JpaRepository's methods to create the new table every day before you insert data in it.
This error occurs when objectMapper.convertValue(cityEntity, City.class)) is called.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Builder class com.example.PostgresApp.dto.City$Builder does not have build method (name: 'build')
package com.example.PostgresApp.dto;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.*;
import org.apache.commons.lang3.StringUtils;
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = City.Builder.class)
public class City {
String name;
String description;
#JsonPOJOBuilder(withPrefix = StringUtils.EMPTY)
public static class Builder {
}
}
Service calling repo seems to be where the exception is thrown
public List<City> getCities(){
return cityRepo.findAll().stream().map(cityEntity -> objectMapper
.convertValue(cityEntity, City.class))
.collect(Collectors.toList());
}
The problem is that Jackson cannot deserialize the object value.
My solution was to add the following annotations to my class:
// constructor with no args
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
// constructor with all args
#AllArgsConstructor
// ignore unknown properties during deserialization
#JsonIgnoreProperties(ignoreUnknown = true)
My class ended up looking like this:
#Getter
#Builder
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyClass {
private boolean flag;
private boolean flag2;
private MyClassA objectA;
private MyClassB objectB;
}
If you want to read more on why should we use #NoArgsConstructor and #AllArgsConstructor together, here is a good answer.
Are You sure You always pass name and description to the class Builder?
I got the same error and In my case I was trying to to use a generated Builder to create an Object but I did not pass all of the arguments, so the generated method was not the one spring was looking for. It was searching the N+1 arguments method, but I was passing only N arguments. In this case it will look for a different method signature that can not find.
Environment:
spring-boot v2.0.4 RELEASE
spring-data-aerospike v2.0.1.RELEASE
java - 8
Here are my application code and properties.
// application.properties
aerospike.hosts=xxx:3000
aerospike.namespace=test
// aerospike configuration class
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(AerospikeConfiguration.AerospikeConfigurationProperties.class)
#EnableAerospikeRepositories(basePackageClassses = TestAeroRepository.class)
public class AerospikeConfiguration extends AbstractAerospikeDataConfiguration {
private final AerospikeConfigurationProperties aerospikeConfigurationProperties;
#Override
protected Collection<Host> getHosts() {
return Host.parseServiceHosts(aerospikeConfigurationProperties.getHosts());
}
#Override
protected String nameSpace() {
return aerospikeConfigurationProperties.getNamespace();
}
#Data
#Validate
#ConfigurationProperties("aerospike")
public static class AerospikeConfigurationProperties {
#NotEmpty
String hsots;
#NotEmpty
String namespace;
}
}
# Entity class
#Value
#Document
#Builder(toBuilder = true)
#AllArgsConstructor
public class testEntity() {
#Id
int id;
#Field
String name;
#Field
String timestamp;
}
#Repository
public interface TestAeroRepository extends AerospikeRepository<TestEntity, Integer> {
}
public interface TestAeroService {
void save();
}
#Service
#RequiredArgsConstructor
public class TestAeroServiceImpl implements TestAeroService {
private final TestAeroRepository testAeroRepository;
#Override
public void save(TestEntity entity) {
testAeroRepository.save(entity);
}
}
I checked Aerospike client connection has no problem.
But error is occurred when save() method is executed.
org.springframework.cglib.core.ReflectUtils.defineClass(Ljava/lang/String;[BLjava/lang/ClassLoader;Ljava/security/ProtectionDomain;Ljava/lang/Class;)Ljava/lang/Class;
Have to make sets before execute the application? I didn't make sets.
Any problem with my code?
You’re using an old version of spring-data-aerospike (2.0.1.RELEASE was released on April 2019) is there any chance you can upgrade to the latest version? 2.4.2.RELEASE
You can see how to setup a simple spring data aerospike application here: https://medium.com/aerospike-developer-blog/simple-web-application-using-java-spring-boot-aerospike-database-and-docker-ad13795e0089
Please share the entire project’s code and the entire exception.
I would look into:
The configuration class (The Aerospike Beans creation).
The content of the testEntity class - are you using #Id annotation on the primary key field?
Extending the repository class with specifying the testEntity object (… extends AerospikeRepository<testEntity, Object> {) you can see an example in the link I added.
The set is automatically created and takes the name of your object class, which is testEntity in your case. For example, based on your code, if you do not specify a collection in the #Document annotation a set named "testEntity" will automatically be created. I added the #Document(collection = "testEntitys") annotation and all I did was create two set. Once you insert your first record, run the "SHOW SETS" aql command and it will be there. So that's one way to do it.
I have an entity that looks like this
#Entity(name = "encounter_pdf_export")
public class EncounterPDFExport<T extends Encounter> implements Serializable {
public static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long pdfExportId;
#Any(metaColumn = #Column(name = "encounter_type"))
#Cascade(CascadeType.ALL)
#AnyMetaDef(
idType = "long",
metaType = "string",
metaValues = {
#MetaValue(value = "FooEncounter", targetEntity = FooEncounter.class)
})
#JoinColumn(name = "encounter_id")
private T encounter;
The abstract type that I'm extending is:
public abstract class Encounter {
public abstract Long getEncounterId();
}
Here is my Spring Data Repository
#Repository
public interface EncounterPDFExportRepository extends PagingAndSortingRepository<EncounterPDFExport, Long> {
EncounterPDFExport findOneByEncounter_encounterId(#Param("encounterId") Long encounterId);
}
I am getting a stack trace when starting up the application related to to the findOneByEncounter_encounterId method:
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [encounter] on this ManagedType [com.iimassociates.distiller.domain.EncounterPDFExport]
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.checkNotNull(AbstractManagedType.java:144)
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.getAttribute(AbstractManagedType.java:130)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:468)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.getTypedPath(JpaQueryCreator.java:300)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.build(JpaQueryCreator.java:243)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.toPredicate(JpaQueryCreator.java:148)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:88)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:46)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:109)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:88)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:73)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.<init>(PartTreeJpaQuery.java:116)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$CountQueryPreparer.<init>(PartTreeJpaQuery.java:237)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:65)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:100)
I am assuming that either Spring Data JPA doesn't support abstracted/generic fields? If that's the case, would creating a #Query be a sufficient workaround?
Not sure if this will be helpful to anyone, but I did get this working.
Removed the abstract class and made it an interface with a single public getEncounterId() method
Modified FooEncounter to implement the above interface
Removed generics from the EncounterPDFExport class
Modified the encounter field to utilize the above interface rather than a generic
Apparently, I'm hitting some Hibernate bug/limitation when accessing fields within FooEncounter. Accessing Encounter within EncounterPDFExport works OK, though. I modified my Spring Data JPA Repository to look like the following (note the modification from finding by encounter.encounterId vs. just encounter):
#Repository
public interface EncounterPDFExportRepository extends PagingAndSortingRepository<EncounterPDFExport, Long> {
EncounterPDFExport findOneByEncounter(#Param("encounter") Encounter encounter);
}
The Hibernate bug in question seems to be related to https://jira.spring.io/browse/DATAJPA-836.