Search code examples
javaspring-bootclassenumsentity

Access Entity fields at dev-time


Is there a way to access entity fields without using string variables?

Let me explain, why this makes sense.

I have this User entity:

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;

    public User(String username) {
        this.username = username;
    }
}

This class results in also being a database table "user" having two fields "id" and "username".

To get all users I would typically use a UserRepository I defined for this, resulting in this command:

List<User> users = this.userRepository.findAll();

All is good until to this point. Now, we enter the area I am troubled with. I would like to sort the users before putting them into the List variable. For that I would enhance the above command to this:

Sort sort = Sort.by(Sort.Direction.ASC, "username");
List<User> users = this.userRepository.findAll();

This works perfectly fine when compiled, but what happens if I changed the structure in the User entity, i.e. changing "username" to "usernamezzz". Now I have to remember all the places in my application where I sort the query result using a string named "username". Terrible! And error prone! Easy to forget one of those places, because compilation does not see any errors. What I would like to have is something like User.username and then get not the username value, but the field name "username" instead. My research for a functionality providing me with that has yet been unsuccessful. Currently, I have a solution that works, but it is also a bit ugly. What I did was to create a parallel enum to my entity class User. It looks like this:

public enum UserFields {
    id,
    username
}

The code part that performs the sorting now looks like this:

Sort sort = Sort.by(Sort.Direction.ASC, UserFields.username.name());
return this.userRepository.findAll(sort);

This way, when I change the structure of the entity User, I will also change the structure of the enum UserFields. By doing so, I will get errors everywhere in my code, where I access the field name of the enum UserFields, and now I can correct the code everywhere. This works well, but it is ugly, due to having to write all entity classes twice with enums.

Is there a better way to access fields, without using string names?


Solution

  • You don't need to manually create classes that contain meta information about your entities. Hibernate can do this automatically using the annotation processor hibernate-jpamodelgen. You just need to include this annotation processor in your project builder:

    dependencies {
      annotationProcessor 'org.hibernate:hibernate-jpamodelgen:6.6.1.Final'
    }
    

    Then, before compiling your classes, the annotation processor generates a metamodel of your entities.

    For example, if you have a Person entity:

    @Entity
    @Table(name = "person")
    public class Person {
    
        @Id
        private Long id;
    
        public String name;
    }
    

    Then the annotation processor generates a Person_ class that contains constants with the names of the Pesron entity fields:

    @StaticMetamodel(Person.class)
    @Generated("org.hibernate.processor.HibernateProcessor")
    public abstract class Person_ {
    
        public static final String NAME = "name";
        public static final String ID = "id";
    }
    

    These constants are convenient to use in Criteria API or JPQL queries.

    You might find it convenient to include this annotation processor in your IDE: enter image description here

    For more information, see the article "Criteria Queries Using JPA Metamodel"