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?
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:
For more information, see the article "Criteria Queries Using JPA Metamodel"