Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
krkarma777 opened this issue Feb 29, 2024 · 1 comment
Assignees
Labels
help wanted Extra attention is needed

Comments

@krkarma777
Copy link
Owner

krkarma777 commented Feb 29, 2024

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?

@krkarma777 krkarma777 added the help wanted Extra attention is needed label Feb 29, 2024
@krkarma777 krkarma777 self-assigned this Feb 29, 2024
@krkarma777 krkarma777 pinned this issue Feb 29, 2024
@krkarma777
Copy link
Owner Author

krkarma777 commented Mar 1, 2024

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;
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

1 participant