0

I am working on a Spring Boot project using Spring Data JPA and Hibernate and I have the following problem.

I have a database like this:

enter image description here

The portal_user table will contain the user of an application. A user can have one or multipe type represented by the user_type typological table and by the portal_user_user_type MANY TO MANY relationship table.

For semplicity at the moment assume that an user have a single user type. following two possible user types:

  • CLIENT: it is a client that use services of my application.
  • SUB-AGENT: it is an user that bring CLIENT user into the platform. You can consider this use as a financial broker having its own client portfolio.

To handle this situation I have the subagent_to_client relation table between that is a "recursive" table. Basically it is a MANY TO MANY relationship table that link together a SUB-AGENT user with its CLIENT* users. This relation table define the SUB-AGENT user clients portfolio.

NOTE: it will be the application that ensure the data correctness (basically the business logic will allow only to set a clients list for a SUB-AGENT user but not to other user's types).

This is my entity class mapping my portal_user database table:

@Entity
@Table(name = "portal_user")
@Getter
@Setter
public class User implements Serializable {
     
    private static final long serialVersionUID = 5062673109048808267L;
    
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    @Column(name = "first_name")
    @NotNull(message = "{NotNull.User.firstName.Validation}")
    private String firstName;
    
    @Column(name = "middle_name")
    private String middleName;
    
    @Column(name = "surname")
    @NotNull(message = "{NotNull.User.surname.Validation}")
    private String surname;
    
    @Column(name = "sex")
    @NotNull(message = "{NotNull.User.sex.Validation}")
    private char sex;
    
    @Column(name = "birthdate")
    @NotNull(message = "{NotNull.User.birthdate.Validation}")
    private Date birthdate;
    
    @Column(name = "tax_code")
    @NotNull(message = "{NotNull.User.taxCode.Validation}")
    private String taxCode;
    
    @Column(name = "e_mail")
    @NotNull(message = "{NotNull.User.email.Validation}")
    private String email;
    
    @Column(name = "pswd")
    @NotNull(message = "{NotNull.User.pswd.Validation}")
    private String pswd;
    
    @Column(name = "contact_number")
    @NotNull(message = "{NotNull.User.contactNumber.Validation}")
    private String contactNumber;
    
    @Temporal(TemporalType.DATE)
    @Column(name = "created_at")
    private Date createdAt;
    
    @Column(name = "is_active")
    private boolean is_active;
    
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
    @JsonManagedReference
    private Set<Address> addressesList = new HashSet<>();
    
    @ManyToMany(cascade = { CascadeType.MERGE })
    @JoinTable(
        name = "portal_user_user_type", 
        joinColumns = { @JoinColumn(name = "portal_user_id_fk") }, 
        inverseJoinColumns = { @JoinColumn(name = "user_type_id_fk") }
    )
    Set<UserType> userTypes;
    
    
    @ManyToMany(cascade = { CascadeType.MERGE })
    @JoinTable(
        name = "subagent_to_client", 
        joinColumns = { @JoinColumn(name = "fk_subagent_user_id") }, 
        inverseJoinColumns = { @JoinColumn(name = "fk_client_user_id") }
    )
    Set<User> clientsListOfASubagent;
    

    public User() {
        super();
        // TODO Auto-generated constructor stub
    }


    public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
            String email, String pswd, String contactNumber, Date createdAt, boolean is_active) {
        super();
        this.firstName = firstName;
        this.middleName = middleName;
        this.surname = surname;
        this.sex = sex;
        this.birthdate = birthdate;
        this.taxCode = taxCode;
        this.email = email;
        this.pswd = pswd;
        this.contactNumber = contactNumber;
        this.createdAt = createdAt;
        this.is_active = is_active;
    }
    

}

As you can see in the previous entity class I put this field:

@ManyToMany(cascade = { CascadeType.MERGE })
@JoinTable(
    name = "subagent_to_client", 
    joinColumns = { @JoinColumn(name = "fk_subagent_user_id") }, 
    inverseJoinColumns = { @JoinColumn(name = "fk_client_user_id") }
)
Set<User> clientsListOfASubagent;

to handle the list of clients of a specific SUB-AGENT user.

I have this unit test method:

@Test
@Order(7)
@Transactional
public void retrieveSubagentUserAndCheckClientsListTest() {
    
    User user = this.userRepository.findByemail("luca.bianchi@gmail.com");
    assertNotNull(user, "Retrieved user is not null");
    assertTrue(user.getEmail().equals("luca.bianchi@gmail.com"), "E-mail luca.bianchi@gmail.com");
    assertTrue(user.getFirstName().equals("Luca"));
    assertTrue(user.getSurname().equals("Bianchi"));
    
    assertTrue(subAgentUser.getClientsListOfASubagent().size() == 2);
}

This method simply retrieve a **SUB-AGENT user and check that its clients list contain two clients. And it works perfectly fine...infact this list contains the two client of this SUB-AGENT user (set into my subagent_to_client database table).

Ok, now I need to implement a similar behavior for the CLIENT user, the precise behavior for a CLIENT user have to be: RETRIEVE THE SINGLE SUBAGENT OF THIS CLIENT USER (this information is in the same subagent_to_client DB table).

In a first time I thought that the previous portal_user_user_type field will contain this information also for the CLIENT user so I implemented this new test method:

/**
 * Retrieve a specific "CLIENT" user by its eMail and check its single "SUB-AGENT" user
 */
@Test
@Order(8)
@Transactional
public void retrieveClientUserAndCheckSingleSubAgentTest() {
    
    User clientUser = this.userRepository.findByemail("mauro.colombo@gmail.com");
    assertNotNull(clientUser, "Retrieved user is not null");
    assertTrue(clientUser.getEmail().equals("mauro.colombo@gmail.com"), "E-mail mauro.colombo@gmail.com");
    assertTrue(clientUser.getFirstName().equals("Mauro"));
    assertTrue(clientUser.getSurname().equals("Colombo"));
    
    assertTrue(clientUser.getClientsListOfASubagent().size() == 1);
    
}

But in this case it is not working as I expected. This because my clientUser.getClientsListOfASubagent() retrieve an empty list (also if the information is into my subagent_to_client DB table. Infact this table contain this record:

id  fk_subagent_user_id fk_client_user_id
------------------------------------------
..........................................
3   53  59
..........................................
..........................................
..........................................

representing the relationship between myCLIENT user (having e-mail: mauro.colombo@gmail.com) and the related SUB-AGENT user.

It seems that this field:

@ManyToMany(cascade = { CascadeType.MERGE })
@JoinTable(
    name = "subagent_to_client", 
    joinColumns = { @JoinColumn(name = "fk_subagent_user_id") }, 
    inverseJoinColumns = { @JoinColumn(name = "fk_client_user_id") }
)
Set<User> clientsListOfASubagent;

is correctly working in the direction RETRIEVE THE CLIENTS OF A SUBAGENT but not in the opposite direction needed by this use case **RETRIEVE THE LIST COMPOSED BY A SINGLE RECORD REPRESENTING THE SUB-AGENT OF A SPECIFIC CLIENT).

Why? What is wrong? What am I missing? How can I solve this issue? I was trying to add an additional field named subagentOfAClient inverting the joinColumns and the inverseJoinColumns but I don't know if it can be a correct solution.

AndreaNobili
  • 38,251
  • 94
  • 277
  • 514

0 Answers0