Home > other >  Assigning a column value instead of an Entity in a JPA @OneToOne mapping
Assigning a column value instead of an Entity in a JPA @OneToOne mapping

Time:09-21

In my database, I have tables for users, roles, and classifications. A user can have exactly one role and one classification. The tables do not contain any foreign keys. Instead, I have two join tables; user_roles and user_classifications. Each row in these tables relates a user_id to a role_id and a classification_id. This is preferable because the same role/classification can be assigned to multiple unique users. This obviates the need to have multiple rows with the same role/classification but with different foreign keys pointing to the user.

The roles and classifications tables are relatively simple, an id column and name column, the name being a VARCHAR type. I have a Spring app where the UserRepository extends JpaRepository, and the User model contains javax.persistence annotations. Here it is:

@Entity
@Table(name = "users")
public class User {
    @Id
    private String id;
    
    // ... other fields here
    
    @OneToOne
    @JoinTable(
        name = "user_classifications",
        joinColumns = @JoinColumn(
            name = "user_id",
            referencedColumnName = "id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "classification_id",
            referencedColumnName = "id"
        ))
    private Classification classification;

    @OneToOne
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(
            name = "user_id",
            referencedColumnName = "id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "role_id",
            referencedColumnName = "id"
        ))
    private Role role;
    
    // ... the rest of the  model
}

@Entity
@Table(name = "roles")
public class Role {
    @Id
    private String id;

    @Column(name = "role_name")
    private String name;
}

@Entity
@Table(name = "classifications")
public class Classification {
    @Id
    private String id;

    @Column(name = "classification_name")
    private String name;
}

Initially, the role and classification fields were String types. Jpa did not like that though as it was expecting to map to an object that mirrored the respective table. So to make Jpa happy, I created additional POJOs for Role and Classification. And that works, I can see the objects being returned from Jpa, including the id and name fields. The problem is that my front end is expecting String types. I could modify the front end to "inspect" the returned models and only keep the relevant String data (the name). I have to imagine that I am missing something here and simply setting the String value from the object is possible. I feel like I am asking the wrong questions when searching for an answer on Google. I'm hoping by explaining my situation here I'll get better results. Your help is highly appreciated.

CodePudding user response:

This does not really answer your question but it would also solve your problem. Using entities in your, I assume, REST API is definitely not suggested. You should keep your core model defined as it makes more sense according to your business case. You should then map this core model based on the entities to DTOs that organize the model to the way it suits best the needs of someone consuming your API.

Having said that, you should try to decouple your entities from the model you make available in your API. This would allow you to change your core model and keep your API model untouched. Or the other way around. Hence, I strongly suggest you creating an API model that suits your consumers needs and mapping your entities to this model.

CodePudding user response:

As suggested by João Dias, you should be using DTOs and I think this is a perfect use case for Blaze-Persistence Entity Views.

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

@EntityView(User.class)
public interface UserDto {
    @IdMapping
    String getId();
    @Mapping("role.name")
    String getRoleName();
    @Mapping("classification.name")
    String getClassificationName();
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

UserDto a = entityViewManager.find(entityManager, UserDto.class, id);

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<UserDto> findAll(Pageable pageable);

The best part is, it will only fetch the state that is actually necessary!

  • Related