Skip to main content

JPA implemntation many to many parent child relation of using one table and one mapping table

 JPA implemntation many to many parent child relation of using single table and one mapping table, with save api and fetch api by the root id.

In JPA (Java Persistence API), a many-to-many relationship on a single class can be modeled using a self-referencing join table. This is done by creating two entities, one for the class and another for the join table, and using the @ManyToMany annotation on the class with the @JoinTable annotation on the join table. The @JoinTable annotation is used to specify the name and columns of the join table, and the @ManyToMany annotation is used to specify the mapping between the class and the join table.

Here is an example of implementing a many-to-many parent-child relationship using JPA, with a Category table and a mapping table, as well as a save API and a fetch API by root ID:

  1. Create Category and Mapping entities.
@Entity  public class Category {
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)

  private Long id;

  private String name;

  @ManyToMany(cascade = CascadeType.ALL)

  @JoinTable(

      name = "category_mapping",

      joinColumns = @JoinColumn(name = "category_id"),

      inverseJoinColumns = @JoinColumn(name = "child_id")

  )

  private Set<Category> children;


  // getters and setters

}

@Entity

public class CategoryMapping {

  @Id

  @GeneratedValue(strategy = GenerationType.IDENTITY)

  private Long id;


  @ManyToOne

  @JoinColumn(name = "category_id")

  private Category category;


  @ManyToOne

  @JoinColumn(name = "child_id")

  private Category child;


  // getters and setters

}
  1. Save API:

public void save(Category parent, Category child) {

  parent.getChildren().add(child);

  CategoryMapping mapping = new CategoryMapping();

  mapping.setCategory(parent);

  mapping.setChild(child);

  entityManager.persist(mapping);

}

  1. Fetch API by root ID:

public Category findByRootId(Long rootId) {

  return entityManager.createQuery(

      "SELECT c " +

      "FROM Category c " +

      "LEFT JOIN FETCH c.children " +

      "WHERE c.id = :rootId", Category.class)

      .setParameter("rootId", rootId)

      .getSingleResult();

}

you could implement the fetch API in a JPA repository class:
With this implementation, you can use the findByRootIdWithChildren method in your service class to fetch a Category entity by its ID, along with its children, in a single query.


@Repository public interface CategoryRepository extends JpaRepository<Category, Long> { @Query("SELECT c FROM Category c LEFT JOIN FETCH c.children WHERE c.id = :rootId") Category findByRootIdWithChildren(@Param("rootId") Long rootId); }



@Service
public class CategoryService { private final CategoryRepository categoryRepository; public CategoryService(CategoryRepository categoryRepository) { this.categoryRepository = categoryRepository; } public Category findByRootId(Long rootId) { return categoryRepository.findByRootIdWithChildren(rootId); } }
---------------------------------
Here is an example of a JPA repository method that fetches a Category entity by its root ID, including all children at the leaf level:

@Repository public interface CategoryRepository extends JpaRepository<Category, Long> { @Query(value = "WITH RECURSIVE category_tree(id, name, parent_id) AS ( " + " SELECT id, name, NULL as parent_id " + " FROM category " + " WHERE id = :rootId " + " UNION ALL " + " SELECT c.id, c.name, c.parent_id " + " FROM category_tree t " + " JOIN category_mapping m ON t.id = m.parent_id " + " JOIN category c ON m.child_id = c.id " + ")" + "SELECT id, name, parent_id " + "FROM category_tree", nativeQuery = true) List<Object[]> findAllByRootIdWithChildren(@Param("rootId") Long rootId); }

This method uses a common table expression (CTE) and the WITH RECURSIVE syntax to build a recursive query that fetches all the descendants of the Category entity with the specified root ID. The query results are returned as an array of objects, which you can then map to Category entities if desired.

Note that this method requires a database that supports the WITH RECURSIVE syntax, such as PostgreSQL. If you are using a different database, you may need to use a different approach.


Yes, it is possible to create the query without using the WITH RECURSIVE syntax. Here is an example of how you could achieve this using multiple JOIN statements:

@Repository public interface CategoryRepository extends JpaRepository<Category, Long> { @Query(value = "SELECT c1.id, c1.name, c2.id as parent_id " + "FROM category c1 " + "LEFT JOIN category_mapping m ON c1.id = m.child_id " + "LEFT JOIN category c2 ON m.parent_id = c2.id " + "WHERE c1.id = :rootId " + "UNION ALL " + "SELECT c1.id, c1.name, c2.id as parent_id " + "FROM category_mapping m " + "JOIN category c1 ON m.child_id = c1.id " + "JOIN category c2 ON m.parent_id = c2.id " + "WHERE m.parent_id = :rootId", nativeQuery = true) List<Object[]> findAllByRootIdWithChildren(@Param("rootId") Long rootId); }

This query uses two separate JOIN statements to fetch the Category entity with the specified root ID, as well as all of its descendants. The results are combined using the UNION ALL operator, and returned as an array of objects.

This approach may be more suitable for databases that do not support the WITH RECURSIVE syntax. However, it may not be as efficient as the WITH RECURSIVE approach, as it requires multiple JOIN operations.

Oracle does not support the WITH RECURSIVE syntax for common table expressions (CTEs). Oracle has its own syntax for defining recursive subqueries, which is similar to the syntax used in other databases. However, if you need to perform recursive queries in Oracle, you will need to use a different approach, such as connecting the results of multiple subqueries using the CONNECT BY clause.


Here is an example of how you could write the query in Oracle to fetch a Category entity by its root ID, including all children at the leaf level:


@Repository public interface CategoryRepository extends JpaRepository<Category, Long> { @Query(value = "SELECT id, name, parent_id " + "FROM ( " + " SELECT c1.id, c1.name, c2.id as parent_id, " + " LEVEL as lvl " + " FROM category c1 " + " LEFT JOIN category_mapping m ON c1.id = m.child_id " + " LEFT JOIN category c2 ON m.parent_id = c2.id " + " WHERE c1.id = :rootId " + " UNION ALL " + " SELECT c1.id, c1.name, c2.id as parent_id, " + " lvl + 1 " + " FROM category_mapping m " + " JOIN category c1 ON m.child_id = c1.id " + " JOIN category c2 ON m.parent_id = c2.



here is an example of how you could write the query in Oracle using the CONNECT BY clause as a replacement for WITH RECURSIVE:
@Repository public interface CategoryRepository extends JpaRepository<Category, Long> { @Query(value = "SELECT c.id, c.name, m.parent_id " + "FROM category c " + "JOIN category_mapping m ON c.id = m.child_id " + "START WITH c.id = :rootId " + "CONNECT BY PRIOR m.child_id = m.parent_id", nativeQuery = true) List<Object[]> findAllByRootIdWithChildren(@Param("rootId") Long rootId); }
In this query, the JOIN clause is used to join the category and category_mapping tables based on the child_id column. The START WITH clause specifies the root node of the hierarchy, and the CONNECT BY PRIOR clause defines the relationship between parent and child nodes using the parent_id column from the category_mapping table. The results are returned as an array of objects, where each object represents a Category entity, its name, and its parent ID.
Here is an example of the JSON output for a sample hierarchy of categories:
[ { "id": 1, "name": "Root Category", "parent_id": null }, { "id": 2, "name": "Child Category 1", "parent_id": 1 }, { "id": 3, "name": "Child Category 2", "parent_id": 1 }, { "id": 4, "name": "Grandchild Category 1", "parent_id": 2 }, { "id": 5, "name": "Grandchild Category 2", "parent_id": 2 }, { "id": 6, "name": "Great-grandchild Category", "parent_id": 4 } ]
In this example, the hierarchy starts with a root category with an ID of 1 and two child categories with IDs of 2 and 3. The child category with ID 2 has two grandchild categories with IDs of 4 and 5, and the grandchild category with ID 4 has a great-grandchild category with ID 6. The parent ID of each category is listed in the parent_id field of the JSON object.

another method in repository class for oracle using connect by to get the parent child data for category in a hiararchy
You can use a recursive function to process the query results and create the parent-child structure. Here's an example of how you can do this in Java:
public class CategoryNode { private Long id; private String name; private Long parentId; private List<CategoryNode> children; public CategoryNode(Long id, String name, Long parentId) { this.id = id; this.name = name; this.parentId = parentId; this.children = new ArrayList<>(); } public void addChild(CategoryNode child) { this.children.add(child); } } @Repository public interface CategoryRepository extends JpaRepository<Category, Long> { @Query(value = "SELECT c.id, c.name, m.parent_id " + "FROM category c " + "JOIN category_mapping m ON c.id = m.child_id " + "START WITH c.id = :rootId " + "CONNECT BY PRIOR m.child_id = m.parent_id", nativeQuery = true) List<Object[]> findAllByRootIdWithHierarchy(@Param("rootId") Long rootId); default List<CategoryNode> getCategoryHierarchy(Long rootId) { List<Object[]> queryResult = findAllByRootIdWithHierarchy(rootId); Map<Long, CategoryNode> categoryNodeMap = new HashMap<>(); for (Object[] row : queryResult) { Long id = (Long) row[0]; String name = (String) row[1]; Long parentId = (Long) row[2]; categoryNodeMap.putIfAbsent(id, new CategoryNode(id, name, parentId)); } for (CategoryNode node : categoryNodeMap.values()) { if (node.parentId != null) { CategoryNode parent = categoryNodeMap.get(node.parentId); parent.addChild(node); } } return new ArrayList<>(categoryNodeMap.values()); } }
In this example, the CategoryNode class represents a node in the category hierarchy, with an ID, a name, a parent ID, and a list of children. The getCategoryHierarchy method retrieves the hierarchy for categories using the findAllByRootIdWithHierarchy query, and then processes the query results to create a map of CategoryNode objects. The method then adds each node to its parent node's list of children, and finally returns the list of all nodes in the hierarchy.

The output of the getCategoryHierarchy method will be a list of CategoryNode objects that represent the category hierarchy, starting from the root node specified by the rootId parameter.

Here's an example of what the output might look like for a hierarchy of categories:

[ { "id": 1, "name": "Root Category", "parentId": null, "children": [ { "id": 2, "name": "Child Category 1", "parentId": 1, "children": [ { "id": 3, "name": "Grandchild Category 1", "parentId": 2, "children": [] } ] }, { "id": 4, "name": "Child Category 2", "parentId": 1, "children": [] } ] } ]

In this example, the first element in the list represents the root category with ID 1, which has two children (Child Category 1 and Child Category 2), and Child Category 1 has one grandchild (Grandchild Category 1).

Comments

Popular posts from this blog

The relationship between Spring Data repositories methods and `Optional`

 In Spring Data repositories, the relationship between repository methods and `Optional` can vary depending on the specific use case and method signature. 1 . `Optional` as a return type:    When a repository method is defined to return a single entity or an optional result, you can use the `Optional` type as the return type. This indicates that the method may or may not find a matching entity in the database.    Example:    ```java    Optional<YourEntity> findById(Long id);    ```    In this case, the `findById` method is defined to return an `Optional<YourEntity>`. If the entity exists in the database, the `Optional` will contain the entity. Otherwise, it will be empty. 2. `Optional` as a parameter type:    You can also use `Optional` as a parameter type to indicate an optional input value for a repository method. This allows you to handle scenarios where a parameter may or may not be present.    Example:    ```java    List<YourEntity> findByCategory(Optional<Str

REST API for uploading file in chunks and merging them on the server-side.

Design Summary for Spring Boot REST API with Chunked File Upload: This design outlines a Spring Boot REST API for receiving file chunks and merging them on the server-side. The API allows clients to upload large files in smaller chunks, improving upload reliability and efficiency. Introduction: The system provides a RESTful API for uploading and merging file chunks. It addresses the challenge of uploading large files over HTTP efficiently. System Architecture: Utilizes a single Spring Boot application. RESTful architecture for client-server interaction. Relies on Spring's built-in components for handling HTTP requests. Data Design: Files are stored temporarily in memory. Utilizes an in-memory map to store uploaded chunks temporarily. Chunks are merged upon receiving all required parts. User Interface Design: This below design outlines a Spring Boot REST API that uses Apache Commons FileUpload to efficiently handle file uploads. It demonstrates a more advanced approach for handling

What is Sealed Classes in Java 17

Java 17, released in September 2021, introduced several new features and enhancements. While some of the most prominent changes in Java 17 have received widespread attention, here's a lesser-known unique feature: 1. Sealed Classes: Sealed classes are a feature that allows developers to control the extent to which other classes can inherit from them. By declaring a class as sealed, you can specify which other classes are allowed to subclass it. This feature enhances encapsulation and provides more control over class hierarchies and extensions. Sealed classes are declared using the `sealed` keyword, and the permitted subclasses are specified using the `permits` keyword. By default, a sealed class allows subclasses only from within the same module. However, you can also explicitly specify other modules that are permitted to subclass the sealed class. This feature enables developers to create more secure and maintainable code by restricting inheritance to a defined set of classes, prev