cross-posted from: https://lemmy.world/post/16301329
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, AtenantEntity
could have multipleusers
assigned, and multipleusersEntity
could belong to atenantEntity
With that understanding, i try to create my
tenantEntity
anduserEntity
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
- Free Code Camp | Springboot Full Course Youtube
- Baledung Explenation of ManyToMany
- vladmihalcea -> Just found out today, will read it later
- GeeksForGeeks ManyToMany
Can you explain how to do it?
I've registered recently but cant spin up a vm because its full on my region.
What caveat should i aware of when using this service?
And what instance do you choose?
Thanks :D