Reputation: 11573
Is it possible to write a Service
that provides a method that selects entities by a parent field but returns a child class that gets passed in by a parameter (or generic on the method)?
Assuming the Parent
has a String field called flag and two childs Child1
and Child2
, the ParentService
should be usable like this:
Child1 child1 = parentService.findFlag(Child1.class, "de");
Child2 child2 = parentService.findFlag(Child2.class, "de");
or
Child1 child1 = parentService.findFlag<Child1>("de");
Child2 child2 = parentService.findFlag<Child2>("de");
Detailed example:
Because it is quite hard to explain, this code should explain the scenario from above:
Java Classes:
Parameter
- abstract parent class having a common field for all childs
UiParameter
extends Parameter BatchParameter
extends ParameterParameterService
- provides a save(Parameter)
method that can be used for UiParameter
and BatchParameter
This method is used by other services to save all kinds of childrenNow it would be handy to add a find(UiParameter.class, commonFieldSelector)
or find(BatchParameter.class, commonFieldSelector)
method to the ParameterService
.
Currently there are 4 boilerblate classes (UiParameterService, UiParameterRepository, BatchParameterService, BatchParameterRepository) with just 1 method that looks almost similar.
@Data
@EqualsAndHashCode(callSuper = true)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue("PARAMETER")
public abstract class Parameter extends AbstractEntity
{
// all children will share that
@ManyToOne
private Application commonField;
}
@Data
@Entity
@EqualsAndHashCode(callSuper = true)
public class UiParameter extends Parameter
{
private String foo;
}
@Data
@Entity
@EqualsAndHashCode(callSuper = true)
public class BatchParameter extends Parameter
{
private int size;
}
public interface ParameterRepository extends JpaRepository<Parameter, Long>
{
// That works great, but it returns all kind of children...
List<Parameter> findByCommonField(final Application commonField);
}
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ParameterService
{
private final ParameterRepository parameterRepository;
public ... findByCommonField...(...)
{
return ... magic ...;
}
}
The usual Service/Repository for all Childs approach leads in our case into boilerplate classes having just a findFlag
method that gets copied from Child1 to ChildN.
Changing the Domain (inheritance strategy) will be a problem.
Spring-Solution based on Maciejs answer:
public interface ParameterRepository extends JpaRepository<Parameter, Long>
{
@Query("SELECT e FROM Parameter e WHERE TYPE(e) = ?1 AND e.commonField = ?2")
<E extends Parameter> List<E> findByTopic(final Class<E> e, final Application commonField);
}
Upvotes: 3
Views: 2878
Reputation: 12122
There's TYPE
operator in JPQL, you can use it as following:
SELECT e
FROM EntityClass
WHERE TYPE(e) = EntityClassSubclass
You can use this query and then write generic method which casts results to desired type.
i.e.
private static <T extends SuperClass> List<T> queryForClass(Class<T> myClass) {
Query q = em.createQuery("SELECT p FROM SuperClass p WHERE TYPE(p) = :myClass");
q.setParameter("myClass", myClass);
List<? extends SuperClass> resultList = q.getResultList();
return (List<T>) resultList;
}
Upvotes: 2