0

I have a OneToMany Relationship (User to EmailAddress)

Maybe I'm going about this the wrong way, My Database is empty but If I want to POST a User object and add it to the Database, along with the emailAdresses object and have the EmailAddress persisted also.

I want 2 records in the Database: 1 User and 1 EmailAddress (with a fk to User table)

Service Class

Currently what I've implemented to get this to work is this:

@Service
public class UserService {

private UserRepository userRepository;
private ModelMapper modelMapper;

   public UserService(UserRepository userRepository, ModelMapper modelMapper) {
      this.userRepository = userRepository;
      this.modelMapper = modelMapper;

      //Used for mapping List
      modelMapper.getConfiguration()
              .setFieldMatchingEnabled(true)
              .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE)
              .setSourceNamingConvention(NamingConventions.JAVABEANS_MUTATOR);

  }
  public User createUser(UserCreateDTO userCreateDTO) {

      User user = modelMapper.map(userCreateDTO, User.class);
      //persist User to EmailAddress object
      if(user.getEmailAddresses() != null){
          user.getEmailAddresses().forEach(user::persistUser);
      }

      return userRepository.save(user);
  }

  public UserDTO getUserById(Long id) {
      User found = userRepository.findById(id).get();
      return modelMapper.map(found, UserDTO.class);
  }
  // .....

Which I have seen used in some bidirectional relationships

User Entity

@Entity
@Table(name = "Users")
@Getter @Setter @ToString @NoArgsConstructor
public class User {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "user_id", updatable = false, nullable = false)
   private Long id;

   private String firstName;
   private String lastName;
   private int age;

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
   private List<EmailAddress> emailAddresses;

Email Address Entity

@Entity
@Table(name = "Email")
@Getter @Setter @ToString @NoArgsConstructor
public class EmailAddress {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name="email_id", updatable = false, nullable = false)
   private Long emailId;
   private String email;

   @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST )
   @JoinColumn(name = "user_id", nullable = false)
   @JsonIgnore
   private User user;

Is there a better way to set up the Join relationship?

Sample POST Request

{"firstName":"Joe", "lastName":"Bloggs", "age": 30, "emailAddresses" : [ "joe-private@email.com" , "joe-work@email.com" ] }
coding123
  • 823
  • 1
  • 8
  • 12
  • 1
    are you persisting user and retrieving user? Can you add service class where you actually calling entityManager or Spring repositoty? – Peter Šály Mar 05 '19 at 16:23
  • 1
    I think [this question](https://stackoverflow.com/q/54988105/6413377) near to duplicate to yours. – pirho Mar 05 '19 at 16:41

2 Answers2

1

I guess you need to associate this email with a user as well, not just set user to email entity.

public void persistUser(EmailAddress emailAddress) {
    // set this email to the user
    // if email EmailAddresses list is null you might need to init it first
    this.getEmailAddresses().add(emailAddress);   
    emailAddress.setUser(this);
}
ikos23
  • 4,879
  • 10
  • 41
  • 60
  • I had that also.. but when I was fetching the User by ID. It would return with 2 email elements... not sure why that was happening. Also, I only call this method if the emailAddresses is present in the request – coding123 Mar 05 '19 at 16:08
1

Firstly, I believe that a method persistUser should not be a part of a service layer - due to its implementation it mostly like a Domain layer method that should be implemented within a User entity class. Secondly, since it's a POST method you shouldn't care of emails existence - you are adding a new user with a new set of emails Closer to a question, I'd suggest you to try this:

public class UserService {

  /************/
  @Autowired
  private UserManager userManager;

  public void addUser(UserModel model) {
    User user = new User(model);
    List<EmailAddress> emails = model.getEmailAddresses().stream().map(EmailAddress::new).collect(Collectors.toList());
    user.setEmailAddresses(emails);
    userManager.saveUser(user);
  }  
}

and at the User add this:

public void setEmailAddresses(List<EmailAddress> emails) {
  emails.forEach(item -> item.setUser(this));
  this.emailAddresses = emails;
}

And don't forget to implement constructors for entities with model paremeters

Yuriy Tsarkov
  • 2,461
  • 2
  • 14
  • 28