Today I made another step towards migration end.
The project is compiled and indexing process starts.
At some point I get an exception:
org.hibernate.AssertionFailure: force initializing collection loading
The full indexing log is here.
Another issue I have is with my Provider class I mentioned before.
Provider’s name property is embedded immutable class LocalizedField which holds a Collections::unmodifiableMap where key is Locale and value is translation. This map has its own converter which converts to JSON while storing to repository and converts to Map when reading from repository.
The reason why I use JSON and not let Hibernate do it is to avoid creating additional tables for translations (which would happen because of Map) and also to be able to use json_search that gives me ability to implement autocomplete. I would do it with Lucene if there were option for wildcard search. My whole Map is converted to single field:
{
"hr_HR" : "Mrkvica - Ulica grada Mainza",
"en_US" : "Mrkvica - The street of city of Mainz"
}
LocalizedField.java
@NoArgsConstructor @ToString @EqualsAndHashCode
public class LocalizedField implements Serializable {
private static final long serialVersionUID = -7002190313077107236L;
@Convert( converter = LocaleStringMapConverter.class )
private Map<Locale, String> data;
public LocalizedField( final LocalizedField src ) {
this.data = src.entrySet().stream()
.map( entry -> {
if( entry.getValue() == null ) { entry.setValue(""); }
return entry;
} )
.collect( Collectors.collectingAndThen(
Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ),
Collections::unmodifiableMap
) );
}
public LocalizedField( final LocalizedFieldDTO src ) {
this.data = src.getData().entrySet().stream()
.map( entry -> {
if( entry.getValue() == null ) { entry.setValue(""); }
return entry;
} )
.collect( Collectors.collectingAndThen(
Collectors.toMap(
entry -> Locale.forLanguageTag( entry.getKey() ),
Map.Entry::getValue
),
Collections::unmodifiableMap
) );
}
public String get( final Locale locale ) {
return this.data.get( locale );
}
public Set<Map.Entry<Locale, String>> entrySet() {
return this.data.entrySet();
}
}
To display sorted providers in admin part of application I use transient property sortableTitle to avoid reading and parsing all the data just to provide a sort.
Provider.java
public class Provider {
@Embedded
@AttributeOverride(name = "data",
column = @Column(name = "name", columnDefinition = "TEXT"))
@FullTextField(projectable = Projectable.NO, searchable = Searchable.YES,
analyzer = "customAnalyzer",
valueBridge = @ValueBridgeRef(type = LocalizedFieldBridge.class))
private LocalizedField name;
@Override @Transient
@IndexingDependency(derivedFrom = {
@ObjectPath(@PropertyValue(propertyName = "name")),
@ObjectPath({
@PropertyValue(propertyName = "parent"),
@PropertyValue(propertyName = "name")
})
})
@KeywordField(name = "sortableTitle", projectable = Projectable.NO,
searchable = Searchable.NO, sortable = Sortable.YES)
public String getSortableTitle() {
final String result = this.name.get(LocaleUtil.SORT_LOCALE);
if (this.parent == null) {
return TextUtil.getFoldedString(result);
}
// FIXME exception is thrown during reindex process
if (this.parent.name == null) {
throw new IllegalStateException(
"Provider parent is NOT NULL, but its name IS NULL; " +
"child provider: " + this.name.get(LocaleUtil.SORT_LOCALE)
);
}
final String parentName = this.parent.name.get(LocaleUtil.SORT_LOCALE);
return TextUtil.getFoldedString(parentName + '.' + result);
}
During indexing process IllegalStateException is thrown from my method getSortableTitle(). When I put a breakpoint on a row with throw statement, I can find parent Provider being initialized partially - its name is null. I guess the problem is that I use @Converter to populate LocalizedField name and this converter is perhaps not triggered during parent Provider initialization. It could be something else, but so far I don’t know. When using SELECT statement from my SQL tool, I can see both names are present - for child Provider and parent Provider.
Any ideas? Except putting all relations to eager because it would destroy any chance for my application to be fast and responsive.
