问题
I have the following class definitions and for class UserAdAccount, it uses both the adAccountId and userId as its composite primary key. I need to use this composite primary key as the document id for indexing.
After reading the Hibernate Search 5.11.5.Final: Reference Guide(This is the version I am using), I found the following: The more powerful TwoWayFieldBridge interface allows you to store more than one field into the index, which can be useful for composite properties, but is more complex to implement.
I am not sure how to proceed using the TwoWayFieldBridge to address my need here. Any help is appreciated!
@Entity (name = "JHI_USER_AD_ACCOUNT")
@Indexed
@IdClass(UserAdAccountId.class)
@Getter
@Setter
public class UserAdAccount implements SearchableEntity, Serializable {
//newly added id field with two way string bridge
@DocumentId
@Column(name = "ID")
@FieldBridge(impl = UserAdAccoutPrimaryKeyBridge.class)
private UserAdAccountId id;
@Id
@Column(name = "AD_ACCOUNT_ID")
@GenericGenerator( name = "native", strategy = "native")
@Field
private Long adAccountId;
@Id
@Column(name = "USER_ID")
@GenericGenerator( name = "native", strategy = "native")
@Field
private Long userId;
@ManyToOne
@JoinColumn(name = "USER_ID", referencedColumnName = "ID", updatable = false, insertable = false)
@JsonIgnore
private User user;
@ManyToOne
@JoinColumn(name = "AD_ACCOUNT_ID", referencedColumnName = "ID", updatable = false, insertable = false)
@IndexedEmbedded(includePaths = "name")
private AdAccount adAccount;
}
@Getter
@Setter
@EqualsAndHashCode(of = {"userId", "adAccountId"})
public class UserAdAccountId implements Serializable {
private Long userId;
private Long adAccountId;
}
public class UserAdAccoutPrimaryKeyBridge implements TwoWayStringBridge {
@Override
public String objectToString(Object object) {
UserAdAccountId userAdAccountId = (UserAdAccountId) object;
StringBuilder buffer = new StringBuilder();
buffer.append(userAdAccountId.getUserId()).append("-").append(userAdAccountId.getAdAccountId());
return buffer.toString();
}
@Override
public Object stringToObject(String value) {
String[] components = value.split("-");
UserAdAccountId userAdAccountId = new UserAdAccountId();
userAdAccountId.setUserId(Long.parseLong(components[0]));
userAdAccountId.setAdAccountId(Long.parseLong(components[1]));
return userAdAccountId;
}
}
回答1:
From the documentation:
It is important for the two-way process to be idempotent, i.e.: [...]
- for TwoWayFieldBridge: for a given document, the object returned by get() must be the same as the one that was originally passed to set().
Also, in order for TwoWayFieldBridge implementations to work correctly when used on a document identifier, the void set(String name, Object value, Document document, LuceneOptions luceneOptions) method must add a field to the document following these conventions:
- the field name must be the name provided in the name parameter
- the field value must be encoded as a String
- the field value must be unique to the given value of the value parameter
- the field value must match what the objectToString method would return for the given value parameter.
You can find an example implementation for a composite primary key on github:
public class PersonPKBridge implements TwoWayFieldBridge, MetadataProvidingFieldBridge {
private static final String FIRST_NAME_SUFFIX = "_content.firstName";
private static final String LAST_NAME_SUFFIX = "_content.lastName";
@Override
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
builder.field( name + FIRST_NAME_SUFFIX, FieldType.STRING )
.field( name + LAST_NAME_SUFFIX, FieldType.STRING );
}
@Override
public Object get(String name, Document document) {
PersonPK id = new PersonPK();
IndexableField field = document.getField( name + FIRST_NAME_SUFFIX );
id.setFirstName( field.stringValue() );
field = document.getField( name + LAST_NAME_SUFFIX );
id.setLastName( field.stringValue() );
return id;
}
@Override
public String objectToString(Object object) {
PersonPK id = (PersonPK) object;
StringBuilder sb = new StringBuilder();
sb.append( id.getFirstName() ).append( " " ).append( id.getLastName() );
return sb.toString();
}
@Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
PersonPK id = (PersonPK) value;
//store each property in a unique field
luceneOptions.addFieldToDocument( name + FIRST_NAME_SUFFIX, id.getFirstName(), document );
luceneOptions.addFieldToDocument( name + LAST_NAME_SUFFIX, id.getLastName(), document );
//store the unique string representation in the named field
luceneOptions.addFieldToDocument( name, objectToString( id ), document );
}
}
Alternatively, if you're certain the components of your ID don't contain a certain string, you will be able to use that string as a separator for the indexed form of your ID, and you'll be able to convert that indexed form back to the original ID. This means you can use the simpler TwoWayStringBridge
and rely on concatenation/splitting:
public class PersonPKBridge implements TwoWayStringBridge {
@Override
public String objectToString(Object object) {
PersonPK id = (PersonPK) object;
StringBuilder sb = new StringBuilder();
sb.append( id.getFirstName() ).append( " " ).append( id.getLastName() );
return sb.toString();
}
@Override
public Object stringToObject(String value) {
String[] components = value.split(" ");
PersonPK id = new PersonPK();
id.setFirstName( components[0] );
id.setLastName( components[1] );
return id;
}
}
来源:https://stackoverflow.com/questions/63182446/how-to-index-composite-primary-keys-in-hibernate-search