Чтение рекурсивной структуры обычно выполняется с использованием того, что SQL называет рекурсивным CTE .JPA не поддерживает это из коробки, потому что не все RDBMS поддерживают это.Если вы знаете, что ваша СУБД поддерживает это, вы можете использовать следующий SQL для этого:
WITH RECURSIVE nodes(id, parent_id) AS (
SELECT id, parent_id FROM location l where id = ?
UNION ALL
SELECT l.id, l.parent_id FROM nodes n JOIN location l ON n.parent_id = l.id
)
SELECT id, parent_id FROM nodes
С этим вы получите список определенных и всех родительских идентификаторов местоположения, а также их соответствующих родителейкоторый плоский.Вам придется внести в это структуру.
List<Object[]> result = //get the result of the query
Map<Integer, LocationDto> locationMap = new HashMap<>();
result.forEach(r -> locationMap.put(result.get(0), new LocationDto(result[0], result[1])));
locationMap.values().forEach(l -> l.setParent(locaitonMap.get(l.getParentId())));
Если вы не хотите использовать простой SQL из-за проблем переносимости или просто потому, что вы не хотите отказаться от своей абстракции, выможет использовать Blaze-Persistence , который работает поверх JPA и добавляет поддержку CTE.Ваш запрос с Blaze-Persistence будет выглядеть так:
List<LocationCte> result = criteriaBuilderFactory.create(entityManager, LocationCte.class)
.withRecursive(LocationCte.class)
.from(Location.class, "l")
.bind("id").select("l.id")
.bind("parent").select("l.parent.id")
.where("id").eq(initialId)
.unionAll()
.from(Location.class, "l")
.innerJoinOn(LocationCte.class, "cte")
.on("cte.parent").eqExpression("l.id)
.end()
.bind("id").select("l.id")
.bind("parent").select("l.parent.id")
.end()
.from(LocationCte.class)
.getResultList();
Вам также понадобится этот специальный класс сущностей
@CTE
@Entity
public class LocationCte {
@Id Integer id;
Integer parent;
}