2013-04-23 3 views
2

Мы должны запрашивать данные из базы данных, где нам нужно найти сущности, соответствующие списку пар значений ключа. Мы подумали, что было бы неплохо использовать Spring Data JPA, поскольку нам также нужна разбивка на страницы.метод запроса весны-данных со списком в качестве аргументов

таблицы мы создали, как показано ниже:

terminal(ID,NUMBER,NAME); 
terminal_properties(ID,KEY,VALUE,TERMINAL_FK); 

Можно определить метод запроса для извлечения всех терминалов со свойствами, содержащими данные пары ключ/значение?

Что-то вроде этого: Список < терминал > findByPropertiesKeyAndValue (Список < недвижимости >);

ответ

1

Я не выполнил код, но с учетом правильных операторов импорта это, по крайней мере, компилируется. В зависимости от определения вашего объекта некоторые свойства, возможно, потребуется адаптировать, и в любом случае вы должны получить представление о том, как подойти к этому.

Мои критерии запрос основан на следующих SQL:

SELECT * FROM TERMINAL 
    WHERE ID IN (
    SELECT TERMINAL_FK FROM TERMINAL_PROPERTIES 
     WHERE (KEY = 'key1' AND VALUE = 'value1') 
     OR (KEY = 'key2' AND VALUE = 'value2') 
     ... 
     GROUP BY TERMINAL_FK 
     HAVING COUNT(*) = 42 
) 

Где вы перечисляете каждое имя пары/значение и 42 просто представляет число пар имя/значение.

Так что я предполагаю, что вы определили хранилище, как это:

public interface TerminalRepository extends CrudRepository<Terminal, Long>, JpaSpecificationExecutor { 
} 

Это важно расширить JpaSpecificationExecutor для того, чтобы использовать критерии API.

Затем вы можете создать запрос критерии, как это:

public class TerminalService { 

    private static Specification<Terminal> hasProperties(final Map<String, String> properties) { 
    return new Specification<Terminal>() { 
     @Override 
     public Predicate toPredicate(Root<Terminal> root, CriteriaQuery<?> query, CriteriaBuilder builder) { 
     // SELECT TERMINAL_FK FROM TERMINAL_PROPERTIES 
     Subquery<TerminalProperty> subQuery = query.subquery(TerminalProperty.class); 
     Root propertyRoot = subQuery.from(TerminalProperty.class); 
     subQuery.select(propertyRoot.get("terminal.id")); 
     Predicate whereClause = null; 
     for (Map.Entry<String, String> entry : properties.entrySet()) { 
      // (KEY = 'key1' AND VALUE = 'value1') 
      Predicate predicate = builder.and(builder.equal(propertyRoot.get("key"), 
       entry.getKey()), builder.equal(propertyRoot.get("value"), entry.getValue())); 
      if (whereClause == null) { 
      whereClause = predicate; 
      } else { 
      // (...) OR (...) 
      whereClause = builder.or(whereClause, predicate); 
      } 
     } 
     subQuery.where(whereClause); 
     // GROUP BY TERMINAL_FK 
     subQuery.groupBy(propertyRoot.get("terminal.id")); 
     // HAVING COUNT(*) = 42 
     subQuery.having(builder.equal(builder.count(propertyRoot), properties.size())); 

     // WHERE ID IN (...) 
     return query.where(builder.in(root.get("id")).value(subQuery)).getRestriction(); 
     } 
    }; 
    } 

    @Autowired 
    private TerminalRepository terminalRepository; 

    public Iterable<Terminal> findTerminalsWith(Map<String, String> properties) { 
    // this works only because our repository implements JpaSpecificationExecutor 
    return terminalRepository.findAll(hasProperties(properties)); 
    } 
} 

Вы можете, очевидно, заменить Map<String, String> с Iterable<TerminalProperty>, хотя это было бы чувствовать себя странно, потому что они, как представляется, связаны с конкретным Terminal.

+0

Я надеялся избежать спецификации и строить запросы программно, но, очевидно, это невозможно. Большое спасибо за подробный ответ. – Ismail

+0

Добро пожаловать. И вы, безусловно, можете использовать API-критерии JPA напрямую, если хотите избежать использования «Спецификации», но вы хотели использовать Spring Data JPA? – skirsch