JpaReposistory
The easiest option would be to write a JpaRepository<T, Id>
. This is still a custom repository. However, you do not have to write so much code. You mainly have to write a repository interface for each relevant class and annotate the findById(Long id)
method with a graph. The advantage is that if you edit your entity, the repository method will not need any changes because you define the entity graph within the entity class itself.
@Entity
@NamedEntityGraph(name = "Department.detail",
attributeNodes = @NamedAttributeNode("employees"))
public class Department {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(fetch = FetchType.LAZY)
private List<Employee> employees;
// ...
}
public interface DepartmentRepository extends JpaRepository<Department, Long> {
@EntityGraph(value = "Department.detail", type = EntityGraphType.LOAD)
List<Department> findById(Long id);
}
As Spring data ignores the @Fetch(Fetchmode.JOIN)
annotation or the information fetch = FetchType.EAGER
, you cannot influence the join how you want it to be within the entity itself.
JPQL Query Where You Need It
Another option can be considered as a bad software engineering style: You can call the database queries directly where you need them. This means that you execute the code which you would usually write in the repository.
public ClassWithQueryResults {
@PersistenceContext
private EntityManager entityManager;
public void methodWhereYouNeedYourResults() {
TypedQuery<Department> query = entityManager.createQuery(
"SELECT DISTINCT d FROM Department d LEFT JOIN d.employees e",
Department.class);
List<Department> departments = query.getResultList();
// ...
}
}
Repository With JPQL, Generics and Reflection
Taking the previously suggested idea, you can create a custom repository which is valid for all your entities. The first step would be to create an attribute in your entity class in which you store the attribute which should be fetched.
public class Department extends AbstractEntity {
public static void String ATTRIBUTE_TO_FETCH = "employees";
...
}
With some tweaking, this can be extended to an array/list of all the fields which should be fetched. As this attribute is directly in your entity classes, the chance for any mistakes and future effort is low. Obviously, this attribute should have the same name in all your entities.
The next step would be to create the repository. I provide an example with the findAll()
method. You have to pass it only the class name of the entities you want to have and the generics and reflection do the rest. (Consider what you want to do with the exceptions.)
public <T> List<T> findAll(Class<T> tClass)
throws NoSuchFieldException, IllegalAccessException {
String className = tClass.getSimpleName();
String attributeToFetch = (String)
tClass.getDeclaredField("ATTRIBUTE_TO_FETCH").get(null);
String queryString = String.format("SELECT DISTINCT p FROM %s p LEFT JOIN p.%s c",
className, attributeToFetch);
TypedQuery<T> query = entityManager.createQuery(queryString, tClass);
return query.getResultList();
}
Depending on how you want to implement this, the modification/generation of a query through simple manipulation of a String can offer the possibility of SQL injection attacks.