Search code examples
spring-bootjpa

Can i customize result of @ManyToMany of entity?


Here is 2 entity obj:


import jakarta.persistence.*;
import lombok.*;

import java.util.Set;

@Entity
@Table(name = "category"
        , uniqueConstraints = {
        @UniqueConstraint(columnNames = {"name"})
})
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(name = "category_id")
    private String id;
    @Column(nullable = false)
    private String name;
    @Column(name = "is_deleted", columnDefinition = "boolean default false")
    private boolean isDeleted;
    @OneToMany(mappedBy = "category")
    private Set<Product> product;
    @ManyToMany(mappedBy = "categories")
    Set<Promotion> promotions;
}
import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDate;
import java.util.Set;

@Entity
@Table(name = "promotion")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Promotion {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(name = "promotion_id")
    private String id;
    @Column(nullable = false)
    private String name;
    @Column(name = "discount_percent", nullable = false)
    private double discountPercent;
    @Column(name = "expired_date", nullable = false)
    private LocalDate expiredDate;
    @Column(name = "started_date", nullable = false)
    private LocalDate startedDate;
    @Column(name = "is_active", nullable = false)
    private boolean active;
    @ManyToMany
    @JoinTable(
            name = "pro_cate",
            joinColumns = @JoinColumn(name = "promotion_id"),
            inverseJoinColumns = @JoinColumn(name = "category_id"))
    Set<Category> categories;
}

My service code:

public List<Category> gets() {
    return categoryRepository.findAll();
}

I want to get Category, an its Promotion with the condition that the expiredDate is greater than the date now.

The return set by default is always return all the promotion of category, is there any way to custom it.

Customize the return mapping object with condition.


Solution

  • You need to modify your JPA repository query to filter the data after fetching it from the database. Like this:

    @Repository
    public interface CategoryRepository extends CrudRepository<Category, String> {
    
        @Query("SELECT c FROM Category c JOIN FETCH c.promotions p WHERE p.expiredDate > CURRENT_DATE")
        List<Category> findAllWithActivePromotions();
    }
    

    If you prefer not to use a custom query, you can fetch all categories with their promotions and then filter the promotions in the service layer before returning the data. Like this:

    @Data
    @AllArgsConstructor
    public class CategoryDTO {
        private String id;
        private String name;
        private boolean isDeleted;
        private List<PromotionDTO> promotions;
    }
    
    @Data
    @AllArgsConstructor
    public class PromotionDTO {
        private String id;
        private String name;
        private double discountPercent;
        private LocalDate expiredDate;
        private LocalDate startedDate;
        private boolean active;
    }
    

    Then in your service use this to mapping the entites fetched with DTO classes. Like this:

    public List<CategoryDTO> getCategoriesWithActivePromotions() {
            List<Category> categories = categoryRepository.findAll();
    
            return categories.stream()
                    .map(this::mapToCategoryDTO)
                    .collect(Collectors.toList());
        }
    
        private CategoryDTO mapToCategoryDTO(Category category) {
            List<PromotionDTO> activePromotions = category.getPromotions().stream()
                    .filter(promotion -> promotion.getExpiredDate().isAfter(LocalDate.now()))
                    .map(this::mapToPromotionDTO)
                    .collect(Collectors.toList());
    
            return new CategoryDTO(category.getId(), category.getName(), category.isDeleted(), activePromotions);
        }
    
        private PromotionDTO mapToPromotionDTO(Promotion promotion) {
            return new PromotionDTO(
                    promotion.getId(),
                    promotion.getName(),
                    promotion.getDiscountPercent(),
                    promotion.getExpiredDate(),
                    promotion.getStartedDate(),
                    promotion.isActive()
            );
        }
    

    Both approaches allow you to customize the return of categories and their active promotions. The first approach uses a custom query to fetch only the desired data from the database, while the second approach fetches all data and then filters it in the service layer. Choose the approach that best fits your application's needs and architecture.