Ehsan
Ehsan

Reputation: 78

Spring Boot Hibernate ManyToMany Relation using embedded model

I have a question about ManyToMany realation. Let's assume we have two model domains like Menu and RoleGroup,my Menu model includes a field named ' private RoleGroup roleGroup ' with @ManyToMany relation and @JoinTable with JoinColumns and InverseJoinColumns. Imagine I already created an object like Menu that includes RoleGroup field with entityRepository .save in my database Here is my question, is there any way to update my object (considering to ManyToMany relation) without using native query? because when we use ManyToMany relationship hibernate handles it by creating third table like "Menu_RoleGroup" in database where as we don't have a domain like Menu_RoleGroup .

//Here is my application

   package org.sayar.layout.domain;

    import java.util.HashSet;
    import java.util.Set;

    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.JoinColumn;




    import javax.persistence.JoinTable;
    import javax.persistence.ManyToMany;
    import javax.persistence.OneToMany;
    import javax.persistence.Table;

    import org.hibernate.annotations.Nationalized;
    import org.sayar.layout.base.general.domain.GeneralDomain;
    import org.sayar.layout.constants.SchemaList;
    import org.sayar.layout.domain.uaa.Role;
    import org.sayar.layout.domain.uaa.RoleGroup;
    import org.springframework.data.annotation.Transient;

    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonManagedReference;

    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;

    /**
     * This domain for store and transfer Menu
     *
     * @author Ehsan
     */
    @Setter
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Entity(name = Menu.TABLE_NAME)
    @Table(name = Menu.TABLE_NAME, schema = SchemaList.LAYOUT)
    public class Menu extends GeneralDomain {
        @Transient
        public final static String TABLE_NAME = "Menu";


        public class CN {
            public final static String title = "title";
            public final static String description = "description";
            public final static String banner = "banner";
        }

        @Nationalized
        @Column(name = CN.title, length = 128, nullable = false)
        private String title;

        @Nationalized
        @Column(name = CN.description, length = 1000)
        private String description;

        @Nationalized
        @Column(name = CN.banner, length = 128)
        private String banner;

        /* Relationships */

        @ManyToMany(fetch = FetchType.EAGER)
        @JoinTable(name = "Menu_RoleGroup",
                schema = SchemaList.LAYOUT,
                joinColumns = @JoinColumn(name = "menuId", referencedColumnName = GCN.id), //
                inverseJoinColumns = @JoinColumn(name = "roleGroupId", referencedColumnName = GCN.id))
        private Set<RoleGroup> roleGroupSet;

        @OneToMany(mappedBy = MenuItem.CN.menu, fetch = FetchType.EAGER)
        @JsonManagedReference
        private Set<MenuItem> menuItemSet = new HashSet<>();

        /* Constructors */
        public Menu(String title, String banner, String description) {
            this.title = title;
            this.banner = banner;
            this.description = description;
        }

        public Menu(String title, String banner, String description, Set<RoleGroup> roleGroupSet) {
            this.title = title;
            this.banner = banner;
            this.description = description;
            this.roleGroupSet = roleGroupSet;
        }

        public Menu(String id) {
            super(id);
        }
        /* Other classes or enumerations */

    }
    .
    .
    .
    .
    .

    package org.sayar.layout.domain.uaa;

    import java.util.Date;

    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.EnumType;
    import javax.persistence.Enumerated;
    import javax.persistence.Table;

    import org.hibernate.annotations.Nationalized;
    import org.sayar.layout.base.general.domain.GeneralDomain;
    import org.sayar.layout.constants.SchemaList;
    import org.springframework.data.annotation.Transient;

    import com.fasterxml.jackson.annotation.JsonInclude;

    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;

    /**
     * This domain for store and transfer RoleGroup
     *
     * @author Ehsan
     *
     */
    @Setter
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Entity(name = RoleGroup.TABLE_NAME)
    @Table(name = RoleGroup.TABLE_NAME, schema = SchemaList.UAA)
    public class RoleGroup extends GeneralDomain {
        @Transient
        public final static String TABLE_NAME = "RoleGroup";

        public class CN {
            public final static String title = "title";
            public final static String status = "status";
        }

        @Nationalized
        @Column(name = CN.title, length = 128, nullable = false)
        private String title;

        @Enumerated(EnumType.STRING)
        @Column(name = CN.status, nullable = false)
        private Status status;

        public RoleGroup(String id) {
            super(id);
        }

        /* Relationships */
        /* Constructors */
        /* Other classes or enumerations */
        public enum Status {
            ACTIVE, DE_ACTIVE
        }
    }
    .
    .
    .
    package org.sayar.layout.service.menu;

    import org.sayar.layout.base.util.Print;
    import org.sayar.layout.domain.Menu;
    import org.sayar.layout.repository.megaitem.MegaItemRepository;
    import org.sayar.layout.repository.menu.MenuRepository;
    import org.sayar.layout.repository.menuitem.MenuItemRepository;
    import org.sayar.layout.rest.menu.dto.*;
    import org.springframework.stereotype.Service;
    import org.sayar.layout.dao.menu.MenuDaoImpl;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    import org.springframework.http.ResponseEntity;

    import java.util.List;

    import lombok.RequiredArgsConstructor;
    import org.springframework.transaction.annotation.Transactional;
    import reactor.core.publisher.Mono;

    /**
     * Spring service implementation for the Menu entity.
     *
     * @author Ehsan
     */
    @Service
    @RequiredArgsConstructor
    public class MenuServiceImpl implements MenuService {

        private final MenuDaoImpl entityDao;
        private final MenuRepository entityRepository;
        private final MenuItemRepository menuItemRepository;
        private final MegaItemRepository megaItemRepository;

        @Override
        public Mono<ResponseEntity<Boolean>> create(ReqMenuCreateDTO entity, String userId) {
            return Mono.just(entityRepository.save(entity.map(userId)))
                    .flatMap(createdEntity -> Mono.just(ResponseEntity.ok().body(true))
                    )
                    .defaultIfEmpty(ResponseEntity.ok().body(false));
        }

        @Transactional
        @Override
        public Mono<ResponseEntity<Boolean>> update(String id, ReqMenuUpdateDTO entity) {

            Mono.just(entityRepository.update(id, entity.getTitle(), entity.getBanner(), entity.getDescription())).subscribe();
            Mono.just(entityRepository.deleteRoleMenu(id)).subscribe();
            Print.print("title", entity);

            if (entity.getRoleGroupIdSet() != null && !entity.getRoleGroupIdSet().isEmpty())
                for (String roleId : entity.getRoleGroupIdSet()) {
                    Mono.just(entityRepository.updateRoleMenu(id, roleId)).subscribe();
                }
            return Mono.just(ResponseEntity.ok().body(true));

        }
    .
    .
    .
    .
    .
    package org.sayar.layout.repository.menu;


    import org.sayar.layout.constants.SchemaList;
    import org.sayar.layout.repository.menu.dto.ResRoleGetListRepoDTO;
    import org.sayar.layout.rest.menu.dto.ResMenuGetListDTO;
    import org.sayar.layout.repository.menu.dto.ResMenuGetOneRepoDTO;
    import org.sayar.layout.rest.menu.dto.ResMenuGetOneDTO;
    import org.sayar.layout.rest.menu.dto.ResMenuGetPageDTO;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;

    import java.util.List;
    import java.util.Optional;

    import org.sayar.layout.domain.Menu;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.jpa.repository.Modifying;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.data.repository.query.Param;
    import org.springframework.transaction.annotation.Transactional;

    /**
     * Spring Data SQL Server repository for the Menu entity.
     *
     * @author Ehsan
     */
    @Repository
    public interface MenuRepository extends JpaRepository<Menu, String> {

        //  UPDATE ALL STARTS HERE
        @Transactional
        @Modifying
        @Query(value = "UPDATE Menu AS m SET m.title = :title , m.description = :description, m.banner = :banner WHERE m.id = :id")
        Integer update(@Param("id") String id, @Param("title") String title, @Param("description") String description, @Param("banner") String banner);

        @Transactional
        @Modifying
        @Query(value = "DELETE FROM layout.Menu_RoleGroup WHERE menuId = :id", nativeQuery = true)
        Integer deleteRoleMenu(@Param("id") String id);

        @Transactional
        @Modifying
        @Query(value = "INSERT INTO layout.Menu_RoleGroup (menuId,roleGroupId) VALUES (:menuId,:roleGroupId)", nativeQuery = true)
        Integer updateRoleMenu(@Param("menuId") String menuId, @Param("roleGroupId") String roleGroupId);
    }

Upvotes: 0

Views: 1146

Answers (1)

Ehsan
Ehsan

Reputation: 78

Actually I find out how to solve it! In this case I have to create a domain model like MenuRoleGroup as an Entity and then add an Embeddable field e.g MenuRoleGroupEmbeddable and use create @EmbeddedId field within MenuRoleGroup. then add the primary key of Menu and RoleGroup within MenuRoleGroupEmbeddable.

so Spring Data is able to create MenuRoleGroupRepository that extends JPA Repository and but notice that this table has two primary keys not one.

notice that MenuRoleGroupEmbeddable field within MenuRoleGroup doesn't has relationship but Menu and RoleGroup both have @ManyToOne relation with MenuRoleGroupEmbeddable model.

here is the example.

package org.sayar.wms.domain.Menu;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;
import org.sayar.wms.base.general.domain.GeneralDomain;
import org.sayar.wms.constants.SchemaList;
import org.sayar.wms.domain.container.Container;
import org.sayar.wms.domain.product.ProductContainerEmbeddable;
import org.springframework.data.annotation.Transient;

import javax.persistence.*;
import java.util.Date;


@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@Entity(name = MenuRoleGroup.TABLE_NAME)
@Table(name = MenuRoleGroup.TABLE_NAME, schema = SchemaList.WMS)
public class MenuRoleGroup {

    @Transient
    public final static String TABLE_NAME = "MenuRoleGroup";

    public class CN {
        public final static String startDate = "startDate";
    }

    @EmbeddedId
    private MenuRoleGroupEmbeddable menuRoleGroupEmbeddable;

    @JoinColumn(name = CN.startDate, referencedColumnName = GeneralDomain.GCN.id)
    private Date startDate;

}


package org.sayar.wms.domain.Menu;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.sayar.wms.base.general.domain.GeneralDomain;
import org.sayar.wms.domain.container.Container;

import javax.persistence.CascadeType;
import javax.persistence.Embeddable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.io.Serializable;
import java.util.Set;

/**
 * This domain for store and transfer Warehouse
 *
 * @author masoud
 */
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@Embeddable
public class ProductContainerEmbeddable implements Serializable {

    public class CN {
        public final static String menuId = "menuId";
        public final static String roleGroupId = "roleGroupId";
    }

    /* Relationships */

    @ManyToOne
    @JoinColumn(name = CN.menu, referencedColumnName = "id")
    private Menu menu;

    @ManyToOne
    @JoinColumn(name = CN.roleGroup, referencedColumnName ="id")
    private RoleGroup roleGroup;

    /* Constructors */


    /* Other classes or enumerations */

}

menuId and roleGroupId are both primary keys of hte MenuRoleGroup entity.

Upvotes: 0

Related Questions