krkarma777 / online-store

SEED: An open-market platform built with JDK 17, Spring Boot, and Oracle DB, focusing on RESTful architecture and secure user experiences.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

java.lang.UnsupportedOperationException: null / the entity via @ElementCollection or a similar collection mapping

krkarma777 opened this issue · comments

image

@Query(value = "SELECT new com.bulkpurchase.domain.dto.product.ProductForSalesVolumeSortDTO" +
            "(p.productID, p.productName, p.price, p.stock, p.user.username, p.imageUrls,SUM(od.quantity) , p.user)" +
            "FROM OrderDetail od JOIN od.product p " +
            "WHERE p.productName LIKE %:productName% " +
            "GROUP BY p.productID, p.productName, p.price, p.stock, p.user.username,  p.imageUrls,od.order, p.user " +
            "ORDER BY SUM(od.quantity) DESC",
            countQuery = "SELECT COUNT(DISTINCT p.productID) " +
                    "FROM OrderDetail od JOIN od.product p " +
                    "WHERE p.productName LIKE %:productName%",
            nativeQuery = false)
    Page<ProductForSalesVolumeSortDTO> findByProductNameContainingAndOrderBySalesVolume(@Param("productName") String productName, Pageable pageable);
@Getter
@Setter
public class ProductForSalesVolumeSortDTO {
    private Long productID;
    private String productName;
    private Double price;
    private Integer stock;
    private String username; // 판매자 이름
    private List<String> imageUrls;
    private Long totalQuantity;
    private User user;

    public ProductForSalesVolumeSortDTO(Long productID, String productName, Double price, Integer stock, String username, List<String> imageUrls, Long totalQuantity, User user) {
        this.productID = productID;
        this.productName = productName;
        this.price = price;
        this.stock = stock;
        this.username = username;
        this.imageUrls = imageUrls;
        this.totalQuantity = totalQuantity;
        this.user = user;
    }

    public ProductForSalesVolumeSortDTO(Long productID, String productName, Double price, Integer stock, String username, Long totalQuantity, User user) {
        this.productID = productID;
        this.productName = productName;
        this.price = price;
        this.stock = stock;
        this.username = username;
        this.totalQuantity = totalQuantity;
        this.user = user;
    }



    public ProductForSalesVolumeSortDTO() {
    }
}
@Entity
@Table(name = "Products")
@Getter
@Setter
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @NotNull(groups =  UpdateCheck.class)
    private Long productID;

    @NotEmpty(groups = {SaveCheck.class, UpdateCheck.class})
    @Column(nullable = false, length = 100)
    private String productName;

    @Column
    @NotEmpty(groups = {SaveCheck.class, UpdateCheck.class})
    @Lob
    private String description;

    @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
    @Column(nullable = false)
    @Range(min = 1000, max = 1000000, groups = {SaveCheck.class})
    private Double price;

    @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
    @Column(nullable = false)
    private Integer stock;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "UserID")
    @JsonBackReference
    private User user;

    @ElementCollection(fetch = FetchType.LAZY)
    @CollectionTable(name = "product_sales_regions", joinColumns = @JoinColumn(name = "productID"))
    @Column(name = "region", nullable = false)
    @Enumerated(EnumType.STRING)
    private Set<SalesRegion> salesRegions = new HashSet<>();

    @ElementCollection
    @CollectionTable(name = "product_image_urls", joinColumns = @JoinColumn(name = "productID"))
    @Column(name = "imageUrl")
    private List<String> imageUrls;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 20, columnDefinition = "varchar(20) default 'ACTIVE'")
    private ProductStatus status = ProductStatus.ACTIVE;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "categoryID")
    private Category category;


    @OneToMany(mappedBy = "product")
    private List<FavoriteProduct> favoritedByUsers;

    public ProductStatus getOppositeStatus() {
        return this.status == ProductStatus.ACTIVE ? ProductStatus.INACTIVE : ProductStatus.ACTIVE;
    }
}

I'm encountering an issue with handling collections, such as p.imageUrls, in Hibernate. It appears that while p.imageUrls is included in the entity via @ElementCollection or a similar collection mapping, attempting to directly reference it in a query is not supported by Hibernate, resulting in this problem. Could someone please help me with this?

Issue Faced with Collection Entities in Hibernate

During the development of our project using a Microservices Architecture (MSA), we encountered a challenge while implementing a specific feature involving the direct querying of an entity's collection with Hibernate. The problem arose when attempting to handle the imageUrls field (a collection type) of the Product entity. This field, mapped using @ElementCollection, led to a java.lang.UnsupportedOperationException when directly referenced in a JPA query, indicating Hibernate's lack of support for direct collection references.

Approach and Resolution

Identifying the Problem:
Our initial attempt used a JPQL query to create ProductForSalesVolumeSortDTO objects, directly referencing the imageUrls collection along with other fields of the Product entity. This resulted in an operation unsupported by Hibernate, as evidenced by runtime errors.

Changing Our Strategy:
To resolve this, we opted for executing native SQL queries using the EntityManager. This approach involved manually creating ProductForSalesVolumeSortDTO objects without directly including the imageUrls in the initial query results, addressing them separately in the logic.

Implementation Details:
Executing Native SQL Queries: We crafted and executed an SQL query to fetch product information and sales volumes, excluding the imageUrls field.
Paging Implementation: Utilizing the Pageable object, we implemented paging. This involved calculating the total result count before query execution and setting the paging information (start point and page size) on the query.
Fetching Image URLs and Completing DTOs: For each product ID, we executed separate native queries to retrieve imageUrls, which were then set on the corresponding ProductForSalesVolumeSortDTO objects.
Returning Results: The finalized list of ProductForSalesVolumeSortDTO objects was returned within a PageImpl object, providing a paginated result set to the client.

Conclusion:
Successfully applied a workaround for Hibernate's limitation on direct collection references through native SQL queries and additional data processing at the application level. This approach is particularly beneficial for applications with complex queries and diverse data processing needs. The process underscored the importance of paging and application-level data post-processing strategies.

@Controller
@RequiredArgsConstructor
public class ProductSearchController {

    private final ProductService productService;

    @GetMapping("/product/search")
    public String productSearchView(@RequestParam(value = "q", required = false) String productName,
                                    @RequestParam(value = "p", required = false) Integer page,
                                    @RequestParam(value = "sortField", required = false) String sortField,
                                    @RequestParam(value = "sortDir", required = false) String sortDir ,
                                    @RequestParam(value = "size", required = false) Integer size,
                                    Model model) {
        if (size == null) {
            size = 12;
        }
        if (page==null) {
            page = 1;
        }

        if (sortDir == null) {
            sortDir = "desc";
        }
        if (sortField == null) {
            sortField = "productID";
        }
        if (productName == null) {
            return "redirect:/";
        }
        if (sortField.equals("salesVolume")) {
            Page<ProductForSalesVolumeSortDTO> initialProductsPage = productService.findProductsBySalesVolume(productName, PageRequest.of(page - 1, size));
            List<ProductForSalesVolumeSortDTO> productPage = productService.completeProductDTOs(initialProductsPage.getContent());
            model.addAttribute("productPage", productPage);
            model.addAttribute("totalPages", initialProductsPage.getTotalPages());
        } else {
            Sort sort = Sort.by(Sort.Direction.fromString(sortDir), sortField);
            Pageable pageable = PageRequest.of(page-1, size, sort);
            Page<Product> productPage = productService.findPageByProductNameContaining(pageable, productName);
            model.addAttribute("productPage", productPage);
            model.addAttribute("totalPages", productPage.getTotalPages());
        }

        model.addAttribute("page", page);
        model.addAttribute("q", productName);
        model.addAttribute("sortField", sortField);
        model.addAttribute("sortDir", sortDir);
        return "product/productSearchView";
    }

}
package com.bulkpurchase.domain.dto.product;

import com.bulkpurchase.domain.entity.user.User;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
public class ProductForSalesVolumeSortDTO {
    private Long productID;
    private String productName;
    private Double price;
    private Integer stock;
    private List<String> imageUrls;
    private Long totalQuantity;
    private User user;

    public ProductForSalesVolumeSortDTO(Long productID, String productName, Double price, Integer stock, List<String> imageUrls, Long totalQuantity, User user) {
        this.productID = productID;
        this.productName = productName;
        this.price = price;
        this.stock = stock;
        this.imageUrls = imageUrls;
        this.totalQuantity = totalQuantity;
        this.user = user;
    }


    public ProductForSalesVolumeSortDTO() {
    }
}
 public Page<ProductForSalesVolumeSortDTO> findProductsBySalesVolume(String productName, Pageable pageable) {
        String sql = "SELECT p.PRODUCTID as productID, p.PRODUCT_NAME as productName, p.PRICE as price, p.STOCK as stock, u.USERNAME as username, SUM(od.quantity) as totalQuantity " +
                "FROM order_details od " +
                "JOIN products p ON od.PRODUCTID = p.PRODUCTID " +
                "JOIN users u ON p.USERID = u.USERID " +
                "WHERE p.product_name LIKE :productName " +
                "GROUP BY p.PRODUCTID, u.USERNAME, p.STOCK, p.PRICE, p.PRODUCT_NAME " +
                "ORDER BY totalQuantity DESC";

        Query query = entityManager.createNativeQuery(sql);
        query.setParameter("productName", "%" + productName + "%");

        // 페이징 처리
        int totalRows = query.getResultList().size(); // 전체 결과 수
        query.setFirstResult((int) pageable.getOffset()); // 페이징 시작점
        query.setMaxResults(pageable.getPageSize()); // 페이지 크기

        List<Object[]> results = query.getResultList();
        List<ProductForSalesVolumeSortDTO> products = new ArrayList<>();
        for (Object[] result : results) {
            User user = new User();
            user.setUsername((String) result[4]);

            products.add(new ProductForSalesVolumeSortDTO(
                    ((Number) result[0]).longValue(), // productID
                    (String) result[1], // productName
                    ((Number) result[2]).doubleValue(), // price
                    ((Number) result[3]).intValue(), // stock
                    null, // imageUrls 추후 추가
                    ((Number) result[5]).longValue(), // totalQuantity
                    user // User=
            ));
        }

        return new PageImpl<>(products, pageable, totalRows);
    }

    public List<String> findImageUrlsByProductId(Long productID) {
        String sql = "SELECT IMAGE_URL FROM KRKARMA777.PRODUCT_IMAGE_URLS WHERE PRODUCTID = :productID";
        Query query = entityManager.createNativeQuery(sql);
        query.setParameter("productID", productID);
        return query.getResultList();
    }

    public List<ProductForSalesVolumeSortDTO> completeProductDTOs(List<ProductForSalesVolumeSortDTO> products) {
        for (ProductForSalesVolumeSortDTO product : products) {
            List<String> imageUrls = findImageUrlsByProductId(product.getProductID());
            product.setImageUrls(imageUrls);
        }
        return products;
    }