I want to create a unit test that will use reflection to find all missing fields in dto that implement BaseDto by their persistence entities. This is what I did.
#Slf4j
public class EntityAuditDtoTest {
#Test
public void find_MissingAndExtraFieldsThatUsedInAuditDtosByEntity_ReturnMissingAndExtraFields() throws ClassNotFoundException {
// Arrange
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(AuditEntityType.class));
// Find all classes annotated with #AuditEntityType in the package com.example.dto
Set<BeanDefinition> auditDtoBeans = scanner.findCandidateComponents("com.example.dto");
// Act
for (BeanDefinition auditDtoBean : auditDtoBeans) {
Class<?> auditDtoClass = Class.forName(auditDtoBean.getBeanClassName());
// Make sure the DTO class implements BaseAuditDto
if (!BaseAuditDto.class.isAssignableFrom(auditDtoClass)) {
continue;
}
Class<?> entityClass = getEntityClassForDto(auditDtoClass);
Field[] dtoFields = auditDtoClass.getDeclaredFields();
Field[] entityFields = entityClass.getDeclaredFields();
List<String> missingFields = Arrays.stream(entityFields).map(Field::getName)
.filter(field -> Arrays.stream(dtoFields).noneMatch(f -> f.getName().equals(field))).toList();
if (!missingFields.isEmpty()) {
log.error("Missing fields in DTO class: {} \nfor entity class: {} : {}", auditDtoClass.getName(),
entityClass.getName(), missingFields);
}
List<String> extraFields = Arrays.stream(dtoFields).map(Field::getName)
.filter(field -> Arrays.stream(entityFields).noneMatch(f -> f.getName().equals(field))).toList();
if (!extraFields.isEmpty()) {
log.error("Extra fields in DTO class: {} \nfor entity class: {} : {}", auditDtoClass.getName(),
entityClass.getName(), extraFields);
}
}
}
}
But the problem is that the dto may have a field that is in the entity class, but the test will think that this is a missing field.
For example:
Dto class: ContractAudit has customerId field (customerId). And ContractEntity has public CustomerEntity customer.
This is the same fields. But of course for test they are different. I don't understand how to ignore them. I also don't want to hardcode filter that skip all endings with 'id' prefix.
#Data
#AuditEntityType("Contract")
public class ContractAudit implements BaseAuditDto {
private Long id;
private String ref;
private String status;
private Long customerId;
}
#Entity
#Table(name = "contract")
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class ContractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
#ToString.Include
private Long id;
#Column(name = "ref", updatable = true)
#ToString.Include
private String ref;
#Column(name = "status")
#ToString.Include
#Enumerated(value = EnumType.STRING)
private ContractStatusEnum status;
#ManyToOne
#JoinColumn(name = "customer_id")
public CustomerEntity customer;
#Column(name = "deleted")
#ToString.Include
private boolean deleted;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "contract_id")
private List<ContractDocumentEntity> documents;
}
Output:
Missing fields in DTO class: ContractAudit for entity class: ContractEntity : [customer, deleted, documents]
Extra fields in DTO class: ContractAudit for entity class: ContractEntity : [customerId]
I want to have missing fields: [deleted, documents]
If you have any other ideas on how to do this, I'd love to hear it. I am not asking for implementation. Suggestions only)
Lol. I found solution for my case.
My previous approach was incorrect. Because it's impossible to find 'missing' and 'extra' fields by name correctly for every case. I decided to use:
assertThat(entityClass.getDeclaredFields()).hasSameSizeAs(auditDtoClass.getDeclaredFields());
So this code is checking if the entityClass and the DtoClass have the same number of fields (properties) declared. If not it fail test and print all fields from each classes. If anyone has better ideas I'll be happy to hear.
I’m starting my very first steps with Mapstruct mapper. I want to map a JPA data entity class to a DTO class. This is my source class:
#Entity
#Data
#Table(name = "projects")
public class Project {
#Id
private Long Id;
private String projectName;
private String description;
#OneToMany(mappedBy = "project")
List<Sprint> sprints;
#OneToMany(mappedBy = "project")
List<Epic> epics;
#OneToMany(mappedBy = "project")
List<Story> stories;
public Project(Long id, String projectName, String description) {
Id = id;
this.projectName = projectName;
this.description = description;
}
}
This is my target class:
#Data
#AllArgsConstructor
public class ProjectDTO {
private Long Id;
private String projectName;
private String description;
}
The #Data annotation is from Lombok.
I want to make a mapper to map the Project to ProjectDTO, the attributes like sprints, epics, stories SHOULD NOT be included in ProjectDTO. This is my mapper interface:
#Mapper
public interface ProjectMapper extends Mapper {
ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class)
ProjectDTO projectToProjectDTO(Project project);
}
When I try to build it, this is the error message I got:
[ERROR] Can't generate mapping method with no input arguments.
I guess it’s related to the missing properties in ProjectDTO, but don’t know to solve it. With the #Mapping, I cannot do it like:
#Mapping(source=“sprints”, target= null)
Any help would be appreciated!
Add the '#NoArgConstructor' as well. MapStruct cannot (yet) deal with constructing objects via constructor. Another option would be using '#Builder' in stead if your objects are truly immutable
You should not extend the annotation Mapper. It is enough when you just use it at the type declaration level of your interface
I have my entity object in Spring Boot & Hibernate REST API. This class has many fields. Part of them is embedded and validation constraints such as #Min #Max are not working on fields in #Embeddable class. Same validation rules work perfect in #Entity classes. I am using javax.validation.constraints.Max
My main object looks like this:
#Entity
public class Notice extends BaseEntity {
#Embedded
private MyEmbeddedClass myEmbeddedClass;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "entity_class_id")
private MyEntityClass myEntityClass;
}
And my #embedded class:
#Embeddable
public class MyEmbeddedClass {
#Size(max = 50)
private String label;
#Max(100)
private Integer percent;
}
#Max constraint on percent field is ignored, but #size is working perfectly
#Entity
public class MyEntityClass extends BaseEntity {
#Size(max = 50)
private String name;
#Max(6000)
private Integer size;
}
And here #Max constraint and #size constraint on fields size are beeeing created
Is there a way to fix this? My Spring boot version is 2.1.1 and I can create my database scripts manually but I'd like to avoid that and get almost perfect script thanks to hibernate
You need to add #Valid annotation on your embedded object if you want to validate the constraints defined in your #Embeddable object:
#Entity
public class Notice extends BaseEntity {
#Embedded
#Valid
private MyEmbeddedClass myEmbeddedClass;
...
}
I am using Spring Data REST and I have the following entity in my project.
#Data
#Entity
public class Loan{
#Id
#GeneratedValue
private Long id;
#JsonIgnore
private Long createdDate;
private Long amount;
private Long repaymentStartDate;
}
Now I want to sort the loans by the createdDate which will be automatically filled and JSONIgnored to prevent it from being updated. But I am unable to sort the loans by the createdDate when I call the endpoint loans?sort=createdDate.
How do I fix this?
Here is my repository:
public interface LoanRepository extends PagingAndSortingRepository<Loan, Long>{
}
To workaround try to replace #JsonIgnore to #JsonProperty(access = READ_ONLY). It prevents createdDate from changing but remains it in the json body.
UPDATED
For Spring Boot 1.5.10+ instead of #JsonProperty(access = READ_ONLY) you can use #JsonIgnoreProperties("createdDate") on top of the entity.
I have a two objects with simple #OneToMany relationship which looks as follows:
parent:
#Entity
public class ParentAccount {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parentAccount")
private Set<LinkedAccount> linkedAccounts;
}
child:
#Entity
public class LinkedAccount {
#Id
#GeneratedValue
private long id;
#ManyToOne(optional = false)
private ParentAccount parentAccount;
private String name;
// empty constructor for JPA
public LinkedAccount() {
}
}
I ma using Spring CrudRepository to operate with these entities. However, when calling ParentAccount parent = parentAccountRepository.findOne(id);, some kind of infinite loop starts happening and hibernate spams this all over the console:
Hibernate: select linkedacco0_.parent_account_id as parent_a6_1_0_, linkedacco0_.id as id1_0_0_, linkedacco0_.id as id1_0_1_, linkedacco0_.aws_id as aws_id2_0_1_, linkedacco0_.key_id as key_id3_0_1_, linkedacco0_.name as name4_0_1_, linkedacco0_.parent_account_id as parent_a6_0_1_, linkedacco0_.secret_key as secret_k5_0_1_ from linked_account linkedacco0_ where linkedacco0_.parent_account_id=?
I tried changed the fetch type to LAZY but then I get this error:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.berrycloud.scheduler.model.ParentAccount.linkedAccounts, could not initialize proxy - no Session
(It seems that it is trying to do the lazy load outside of the transactional context).
This is my CRUD repository:
#Repository
public interface ParentAccountRepository extends CrudRepository<ParentAccount, Long> {
}
Could someone tell me how to resolve this issue? I would prefer the solution with EAGER fetch. Thank you for any tips
EDIT: here is the schema I am using
CREATE TABLE parent_account (
id BIGINT auto_increment,
name VARCHAR(80) null,
PRIMARY KEY (`id`)
);
CREATE TABLE linked_account (
id BIGINT auto_increment,
parent_account_id BIGINT,
name VARCHAR(80) null,
FOREIGN KEY (`parent_account_id`) REFERENCES `parent_account` (`id`),
PRIMARY KEY (`id`)
);
As the first answer suggests:
Do not use Lombok's #Data annotation on #Entity classes.
Reason: #Data generates hashcode(), equals() and toString() methods that use the generated getters. Using the getter means of course fetching new data even if the property was marked with FetchType=LAZY.
Somewhere along the way hibernate tries to log the data with toString() and it crashes.
Problem solved. I was using a custom #toString method in the LinkedAccount which was referencing the ParentAccount. I had no idea that this could cause any problem and therefor I did not include the toString in my question.
Apparently, this was causing an infinite loop of lazy loading and removing this reference fixed the problem.
As user1819111 told, #Data from Lombok is not compatible with #Entity and FetchType=LAZY. I had used Lombok.Data (#Data) and I was getting this error.
As I don't want do create all get/set, I just put the Lombok #Setter and #Getter in your class and all will work fine.
#Setter
#Getter
#Entity
#Table(name = "file")
#SequenceGenerator(name = "File_Sequence", allocationSize=1, sequenceName = "file_id_seq")
public class MyClass{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "File_Sequence")
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "file", cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
private Set<Base2FileDetail> details = new HashSet<>();
}
Something like this does not work?
#Entity
public class Account {
#Id
#GeneratedValue
private long id;
private String name;
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="manager_id")
private Account manager;
#OneToMany((fetch = FetchType.EAGER, mappedBy="manager")
private Set<Account> linkedAccounts = new HashSet<Account>();
}
I recently had this issue due to a poorly defined Jackson2HttpMessageConverter.
I had done something like the following.
#Bean
RestTemplate restTemplate(#Qualifier("halJacksonHttpMessageConverter")
TypeConstrainedMappingJackson2HttpMessageConverter halConverter) {
final RestTemplate template = new RestTemplateBuilder().build();
halConverter.setSupportedMediaTypes(List.of(/* some media types */));
final List<HttpMessageConverter<?>> converters = template.getMessageConverters();
converters.add(halConverter);
template.setMessageConverters(converters);
return template;
}
This caused a problem because the media types did not include all the defaults. Changing it to the following fixed the issue for me.
halConverter.setSupportedMediaTypes(
new ImmutableList.Builder<MediaType>()
.addAll(halConverter.getSupportedMediaTypes())
.add(/* my custom media type */)
.build()
);
This simple way worked for me. Just use JsonIgnoreProperties .
#JsonIgnoreProperties(value = {"linkedAccounts"})
#ManyToOne(cascade = { CascadeType.PERSIST})
#JoinColumn(name = "abc", referencedColumnName = "abc")
private ParentAccount parentAccount;
This way worked for me without removing #ToSring annotation:
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "parent_accounts")
public class ParentAccount {
#JsonIgnoreProperties({"parentAccount"})
#OneToMany(mappedBy = "parentAccount",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<LinkedAccount> linkedAcounts;
// ...
}
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "linked_accounts")
public class LinkedAccount {
#JsonIgnoreProperties("linkedAcounts")
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "parentAccount_id")
private ParentAccount parentAccount;
// ...
}
PS: In #JsonIgnoreProperties You can also ignore more than one field for preventing infinite loop