Hello Java Community.

Sorry if there’s any grammar / spelling mistakes in this post. I’ll try to convey my questions as clear as possible.

Introduction

I have basic knowledge about Java Programming. I took Programming Courses at my Uni and somewhat familiar with Java and OOP concepts in general ( barely ).

So for the last few weeks, I’ve been watching a great course from BouAli Free Code Camp Springboot Full Course on Youtube.

After completing the course, I challenge my self to create a basic E-Commerce Application with Springboot as the Framework, in order to practice my self with the knowledge i got from the course.

This Application will be a Simple Multi Tenant Application, where each tenant can create a product page, and a customer can purchase. Mind you i create this app to challenge my understanding about Springboot, and will not be a complex app for use.

Set Up

In the tutorial, there’s a usage of @ManyToMany to map Many to Many Relationship. I thought that i could use Many To Many Mapping on Tenants <-> Users Relationship. In my understanding, A tenantEntity could have multiple users assigned, and multiple usersEntity could belong to a tenantEntity

With that understanding, i try to create my tenantEntity and userEntity as follow

// tenantEntity.java
// Import Ommited for simplicity
// Using Lombok Setter, Getter, AllArgs, NoArgs
@Entity
@Table(name = "T_TENANT")  
public class TenantEntity {  
    @Id  
    @GeneratedValue(strategy = GenerationType.UUID)  
    private UUID id;  
  
    @Column(nullable = false)  
    private String tenantName;  
  
    @Column(length = 2000)  
    private String tenantDescription;  
  
    private String website;  
  
    @Column(nullable = false)  
    private Boolean isEnabled;  
  
    private Instant subscriptionExpiryDate;  
  
    @Column(length = 10)  
    private int gracePeriodDays;  
  
    @ManyToMany(mappedBy = "tenants", fetch = FetchType.LAZY)  
    private Set<UserEntity> users = new HashSet<>();  
  
    @Column(updatable = false, nullable = false)  
    @CreationTimestamp  
    private Instant createdAt;  
  
    @Column  
    @UpdateTimestamp   
    private Instant updatedAt;  
}
// userEntity.java
// Import Ommited for simplicity
// Using Lombok Setter, Getter, AllArgs, NoArgs
@Entity
@Table(name = "T_USER")  
public class UserEntity {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    @Column(nullable = false, unique = true)  
    private String username;  
  
    @Column(nullable = false, unique = true)  
    private String email;  
  
    @Column(nullable = false)  
    private String firstName;  
  
    private String lastName;  
  
    @Column(nullable = false)  
    private String password;  
  
    @Column(nullable = false)  
    private boolean isEnabled;  
  
    @ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})  
    @JoinTable(  
            name = "t_users_tenants_mapping",  
            joinColumns = @JoinColumn(  
                    name = "fk_user_id", referencedColumnName = "id"  
            ),  
            inverseJoinColumns = @JoinColumn(  
                    name = "fk_tenant_id", referencedColumnName = "id"  
            )  
    )  
    private Set<TenantEntity> tenants = new HashSet<>();  
  
    @Column(updatable = false, nullable = false)  
    @CreationTimestamp  
    private Instant createdAt;  
  
    @Column  
    @UpdateTimestamp    
    private Instant updatedAt;  
}

Generating this relationship in the database

Creating New User

When creating new user, i could assign the Tenant to the User, resulting in these data

// POST  api/v1/users
{
  "username": "newuser01",
  "first_name": "new",
  "last_name": "user",
  "email": "newuser@localhost",
  "password": "<P@ssw0rd/>",
  "is_enabled": true,
  "tenant_id": [
    "79cf0ecf-976a-472c-b250-2192e630a4e4",
    "ac5b5786-c467-4dd6-b74d-7f70c83e1827"
  ]
}
fk_user_id|fk_tenant_id                        |
----------+------------------------------------+
         6|79cf0ecf-976a-472c-b250-2192e630a4e4|
         6|ac5b5786-c467-4dd6-b74d-7f70c83e1827|

Problem(s)

Now, how does one updates the data for the ManyToMany relationship? Say, i want to modify the membership of a user. i want to add new tenant or remove existing tenant?

I try creating update method on my userRepository

// userRepository
public interface UserEntityRepository extends JpaRepository<UserEntity, Long> {  
  
    @Transactional  
    @Modifying    @Query("""  
            update UserEntity u set u.username = :username, u.email = :email, u.firstName = :firstName, u.lastName = :lastName, u.isEnabled = :isEnabled, u.tenants = :tenants, u.updatedAt = :updatedAt            where u.id = :id""")  
    int updateUsernameAndEmailAndFirstNameAndLastNameAndIsEnabledAndTenantsAndUpdatedAtById(  
            @Param("username") String username,  
            @Param("email") String email,  
            @Param("firstName") String firstName,  
            @Param("lastName") String lastName,  
            @Param("isEnabled") boolean isEnabled,  
            @Param("tenants") Set<TenantEntity> tenants,  
            @Param("updatedAt") Instant updatedAt,  
            @Param("id") Long id  
    );  
}

which being called by userService

public UserResponseDTO updateUser(Long userId, UserRequestDto dto){  
        Instant updateAt = Instant.now().atZone(ZoneId.of("Continent/Ciy")).toInstant();  
        Set<TenantEntity> tenantEntitySet = getTenantEntitiesFromUserRequestDTO(dto);  
  
        int updateRows = userEntityRepository.updateUsernameAndEmailAndFirstNameAndLastNameAndIsEnabledAndTenantsAndUpdatedAtById(  
                dto.username(),  
                dto.email(),  
                dto.first_name(),  
                dto.last_name(),  
                dto.is_enabled(),  
                tenantEntitySet,  
                updateAt,  
                userId  
        );  
  
        UserResponseDTO userResponseDTO;  
        UserEntity userEntity;  
        if (updateRows == 1) {  
            userEntity = userEntityRepository.findById(userId).orElse(new UserEntity());  
        } else {  
            userEntity = new UserEntity();  
        }  
        return userMapper.mapUserEntityToUserResponseDto(userEntity);  
    }

resulting in a NullPointerException java.lang.NullPointerException: Cannot invoke "org.hibernate.sql.ast.tree.expression.Expression.getColumnReference()" because "pathSqlExpression" is null

in which resulted in a Hibernate Forum that states Updating *-to-many collections is not possible, but please submit a bug report to our issue tracker([https://hibernate.atlassian.net 14](https://hibernate.atlassian.net)) to improve the error message.

Questions

So what is the best practice of doing things with ManytoMany associations? I’ve found many tutorials that state the use of @ManyToMany Annotations, but left after they’re done inserting things to the database. How can i update the join table relations when the need to modify the data arises?

Is there a knowledge gap between me and Springboot especially with JPA? should i take another approach?

Any kind of feedback and suggestions are welcome

Thank You :D

Resources

  1. Free Code Camp | Springboot Full Course Youtube
  2. Baledung Explenation of ManyToMany
  3. vladmihalcea -> Just found out today, will read it later
  4. GeeksForGeeks ManyToMany