2015-01-29 6 views
1

У меня возникла проблема с реализацией логической логики с помощью Hibernate Search Filter. Есть люди, которые могут быть частью групп. Каждая группа имеет статус из каталога статуса.Фильтр Hibernate Search (Lucene) по коллекциям

Мне нужно отфильтровать всех пользователей, которые находятся в группе 1, и иметь статус 2. Для этого я использую логический запрос с Occur.MUST для обоих предложений, но в отфильтрованном результате включены люди, у которых есть список grops и один из них равен 1, а один из статусов группы 2, например:

person | group | status 
105  (1)  3 
105  2  3 
105  3  (2) 

188  (1)  3 
188  7  (2) 

197  (1)  4 
197  8  5 
197  9  (2) 

пользователям 105, 188 и 197 не должны быть включены в отфильтрованный результат. Каков правильный способ добиться этого?

Фильтр:

BooleanQuery bq = new BooleanQuery(); 
TermQuery tqGroup = new TermQuery(new Term("groupPersons.id.groupId", "1")); 
TermQuery tqStatus = new TermQuery(new Term("groupPersons.status.id", "2")); 
bq.add(tqGroup, BooleanClause.Occur.MUST); 
bq.add(tqStatus, BooleanClause.Occur.MUST); 
filter = new QueryWrapperFilter(bq); 

Person лицо:

... 
private List<GroupPerson> groupPersons = new ArrayList<GroupPerson>(0); 

@IndexedEmbedded 
@OneToMany(fetch = FetchType.LAZY, mappedBy = "person") 
public List<GroupPerson> getGroupPersons() { 
    return this.groupPersons; 
} 

GroupPerson лицо:

... 

@EmbeddedId 
@AttributeOverrides({ 
     @AttributeOverride(name = "groupId", column = @Column(name = "group_id", nullable = false)), 
     @AttributeOverride(name = "personId", column = @Column(name = "person_id", nullable = false)) }) 
@NotNull 
@DocumentId 
@FieldBridge(impl = GroupPersonIdBridge.class) 
public GroupPersonId getId() { 
    return this.id; 
} 

... 

@ManyToOne(fetch = FetchType.LAZY) 
@JoinColumn(name = "status_id",nullable = false) 
@IndexedEmbedded 
@NotNull 
public Status getStatus() { 
    return this.Status; 
} 

OrganizationPersonIdBridge:

public Object get(String name, Document document) { 
    GroupPersonId id = new GroupPersonId(); 
    Field field = document.getField(name + ".groupId"); 
    id.setGroupId(Long.parseLong(field.stringValue())); 
    field = document.getField(name + ".personId"); 
    id.setPersonId(Long.parseLong(field.stringValue())); 
    return id; 
    } 

    public String objectToString(Object object) { 
    GroupPersonId id = (GroupPersonId) object; 
    StringBuilder sb = new StringBuilder(); 
    sb.append(id.getGroupId()) 
    .append(" ") 
    .append(id.getPersonId()); 
    return sb.toString(); 
    } 


    public void set(String name,Object value,Document document,LuceneOptions luceneOptions) { 
    GroupPersonId id = (GroupPersonId)value; 
    Store store = luceneOptions.getStore(); 
    Index index = luceneOptions.getIndex(); 
    TermVector termVector = luceneOptions.getTermVector(); 
    Float boost = luceneOptions.getBoost(); 
    //store each property in a unique field 
    Field field = new Field(name + ".groupId", id.getGroupId() + "", store, index, termVector); 
    field.setBoost(boost); 
    document.add(field); 

    field = new Field(name + ".personId", id.getPersonId() + "", store, index, termVector); 
    field.setBoost(boost); 
    document.add(field); 
    //store the unique string representation in the named field 
    field = new Field(name, 
    objectToString(id), 
    store, index, termVector); 
    field.setBoost(boost); 
    document.add(field); 
    } 

Версия поиска в спящем режиме - 4.5.1.Final

ответ

2

Проблема в том, что Lucene Document не имеет ассоциаций. Когда вы используете @IndexedEmbedded, вы эффективно сглаживаете все ассоциации в один Lucene Document (это то, что добавляется к индексу Lucene и извлекается во время поиска). A Document может иметь поле с таким же именем, добавленным несколько раз. Берем пример: Document для Person с идентификатором 105 будет содержать следующее имя поля для пар значений поля:

+-------------------------+-------------+ 
|  field name  | field value | 
+-------------------------+-------------+ 
| groupPersons.id.groupId |   1 | 
| groupPersons.id.groupId |   2 | 
| groupPersons.id.groupId |   3 | 
| groupPersons.status.id |   3 | 
| groupPersons.status.id |   3 | 
| groupPersons.status.id |   2 | 
+-------------------------+-------------+ 

Если вы теперь посмотрите на ваш запрос, вы понимаете, почему человек 105 является матч. Оба логических запроса совпадают.

Как вы можете решить эту проблему? Вам нужно убедиться, что у вас есть что-то уникальное для поиска. Один из способов сделать это - проиндексировать группу и статус в одно поле - с использованием настраиваемого моста. Затем вы можете написать запрос, который просто нацеливается на это поле.

+0

Прочитав больше о том, почему это конкретное поведение происходит, я пришел к такому же выводу, как вы, Харди. Я использовал другое решение, передающее список как параметр для фильтра. Но мне больше нравится решение, которое вы предлагаете. Благодаря! – Tvori

0

Для тех, кто имущих того же использует случай, здесь решение, используя classBridge:

public class CustomClassBridge implements FieldBridge, Serializable { 
public final static String SEPARATOR = "-"; 

@Override 
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) { 
    GroupPerson gp = (GroupPerson)value; 
    String fieldValue = gp.getId().getGroupId() + SEPARATOR + gp.getStatus().getId(); 
    Field field = new Field(name, fieldValue, luceneOptions.getStore(), luceneOptions.getIndex(), luceneOptions.getTermVector()); 
    field.setBoost(luceneOptions.getBoost()); 
    document.add(field); 
} 
} 

Добавить аннотацию GroupPerson объекта на уровне класса:

@ClassBridge(name="groupStatus",index=Index.YES, analyze=Analyze.NO, store=Store.YES, impl = CustomClassBridge.class) 

И, наконец, в фильтре:

TermQuery tq = new TermQuery(new Term("groupPersons.groupStatus", 1 + CustomClassBridge.SEPARATOR + 2)); 
Смежные вопросы