Mapping object's related entities by other field than ID

753 views Asked by At

I am using the following entities:

@Entity
@Table(name = "books")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @ManyToOne
    private User user;
    private UUID uuid = UUID.randomUUID();
}

and

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String email;
    private String firstName;
    private String lastName;
    private String password;
    @OneToMany(mappedBy = "user")
    private List<Book> books;
    private UUID uuid = UUID.randomUUID();
}

and the following DTO:

public class NewBookRequest {
    private UUID userUuid;
}

and the converter:

@Component
public class NewBookRequestToEntityConverter implements Converter<NewBookRequest, Book> {

    private final ModelMapper modelMapper;

    public NewBookRequestToEntityConverter(final ModelMapper modelMapper) {
        this.modelMapper = modelMapper;
    }

    @Override
    public Book convert(@NotNull final NewBookRequest source) {
        return modelMapper.map(source, Book.class);
    }
}

and there is part of service code:

public void addBook(final NewBookRequest newBookRequest) {
    final Book newBook = newBookRequestToEntityConverter.convert(newBookRequest);
    bookRepository.save(newBook);
}

When I try to save the converted Book entity I get the ConstraintViolationException exception, as newBook.user.id is null. However, newBook.user.uuid is correctly assigned. Is there any way to automatically map newBook's user by its uuid? Or the only solution is to do something like this:

add new method to converter:

@Component
public class NewBookRequestToEntityConverter implements Converter<NewBookRequest, Book> {

    private final ModelMapper modelMapper;

    public NewBookRequestToEntityConverter(final ModelMapper modelMapper) {
        this.modelMapper = modelMapper;
    }

    @Override
    public Book convert(@NotNull final NewBookRequest source) {
        return modelMapper.map(source, Book.class);
    }

    public Book convert(@NotNull final NewBookRequest source, final User user) {
        Book target = modelMapper.map(source, Book.class);
        target.setUser(user);
        return target;
    }
}

and modify service's code:

public void addBook(final NewBookRequest newBookRequest) {
    final User user = userRepository.getUserByUuid(newBookRequest.getUserUuid());
    final Book newBook = newBookRequestToEntityConverter.convert(newBookRequest, user);
    bookRepository.save(newBook);
}

? Thanks for any help!

2

There are 2 answers

1
Michal Drozd On

In Book or User entity you could use UUID as primary key. Did you try that?

@Id
@GeneratedValue(generator = "UUID")
@GenericGenerator(
    name = "UUID",
    strategy = "org.hibernate.id.UUIDGenerator",
)
@Column(name = "id", updatable = false, nullable = false)
private UUID id;

However on some databases there can be performance issue when using indexes based on UUID if there is high load.

3
Gordon from Blumberg On

I think problem is your converter does not initialize the primary key of User. You may either change the type of id field in the User class due to answer https://stackoverflow.com/a/64141178/14225495, or add the annotation

@JoinColumn(name = "user_uuid", referencedColumnName = "uuid")

to the user field in the Book class. In this case you should also add column "user_uuid" in the books table, make sure the users.uuid column is unique and not nullable to create foreign key.