diff --git a/documentation/src/main/asciidoc/reference/mapper-orm-bridge-bridgeelement-dependencies.asciidoc b/documentation/src/main/asciidoc/reference/mapper-orm-bridge-bridgeelement-dependencies.asciidoc index 2d42fb6b335..1aa1c4b33c0 100644 --- a/documentation/src/main/asciidoc/reference/mapper-orm-bridge-bridgeelement-dependencies.asciidoc +++ b/documentation/src/main/asciidoc/reference/mapper-orm-bridge-bridgeelement-dependencies.asciidoc @@ -1,5 +1,175 @@ [[mapper-orm-bridge-bridgedelement-dependencies]] = Declaring dependencies to bridged elements -include::todo-placeholder.asciidoc[] -// TODO HSEARCH-3710 \ No newline at end of file +== Basics + +In order to keep the index synchronized, +Hibernate Search needs to be aware of all the entity properties that are used to produce indexed documents, +so that it can trigger reindexing when they change. + +When using a <> or a <>, +the bridge itself decides which entity properties to access during indexing. +Thus, it needs to let Hibernate Search know of its "dependencies" (the entity properties it may access). + +This is done through a dedicated DSL, accessible from the `bind(...)` method of <> +and <>. + +Below is an example of a type binder that expects to be applied to the `ScientificPaper` type, +and declares a dependency to the paper author's last name and first name. + +.Declaring dependencies in a bridge +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/AuthorFullNameBinder.java[tags=binder] +---- +<1> Start the declaration of dependencies. +<2> Declare that the bridge will acess the paper's `author` property, +then the author's `firstName` property. +<3> Declare that the bridge will acess the paper's `author` property, +then the author's `lastName` property. +==== + +The above should be enough to get started, but if you want to know more, +here are a few facts about declaring dependencies. + +Paths are relative to the bridged element:: +For example: +* for a type bridge on type `ScientificPaper`, path `author` will refer to the value of property `author` on `ScientificPaper` instances. +* for a property bridge on the property `author` of `ScientificPaper`, +path `name` will refer to the value of property `name` on `Author` instances. +Every component of given paths will be considered as a dependency:: +You do not need to declare every sub-path. ++ +For example, if the path `myProperty.someOtherProperty` is declared as used, +Hibernate Search will automatically assume that `myProperty` is also used. +Only mutable properties need to be declared:: +If a property never, ever changes after the entity is first persisted, +then it will never trigger reindexing and Hibernate Search +does not need to know about the dependency. ++ +If your bridge only relies on immutable properties, +see <>. +Associations included in dependency paths need to have an inverse side:: +If you declare a dependency that crosses entity boundaries through an association, +and that association has no inverse side in the other entity, an exception will be thrown. ++ +For example, when you declare a dependency to path `author.lastName`, +Hibernate Search infers that whenever the last name of an author changes, +its books need to be reindexed. +Thus when it detects an author's last name changed, Hibernate Search will need to retrieve the books to reindex them. +That's why the `author` association in entity `ScientificPaper` needs to have an inverse side in entity `Author`, +e.g. a `books` association. ++ +See <> for more information about these constraints and how to address non-trivial models. + +[[mapper-orm-bridge-bridgedelement-dependencies-containers]] +== Traversing non-default containers (map keys, ...) + +include::components/incubating-warning.asciidoc[] + +When a path element refers to a property of a container type (`List`, `Map`, `Optional`, ...), +the path will be implicitly resolved to elements of that container. +For example `someMap.otherObject` will resolve to the `otherObject` property +of the _values_ (not the keys) of `someMap`. + +If the default resolution is not what you need, +you can explicitly control how to traverse containers by passing `PojoModelPath` objects +instead of just strings: + +.Declaring dependencies in a bridge with explicit container extractors +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/Book.java[tags=include;!getters-setters] +---- +<1> Apply a custom bridge to the `ScientificPaper` entity. +<2> This (rather complex) map is the one we'll access in the custom bridge. + +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/BookEditionsForSaleTypeBinder.java[tags=include] +---- +<1> Start building a `PojoModelPath`. +<2> Append the `priceByEdition` property (a `Map`) to the path. +<3> Explicitly mention that the bridge will access _keys_ from the `priceByEdition` map -- the paper editions. +Without this, Hibernate Search would have assumed that _values_ are accessed. +<4> Append the `label` property to the path. This is the `label` property in paper editions. +<5> Create the path and pass it to `.use(...)` to declare the dependency. +<6> This is the actual code that accesses the paths as declared above. +==== + +For property binders applied to a container property, +you can control how to traverse the property itself +by passing a container extractor path as the first argument to `use(...)`: + +.Declaring dependencies in a bridge with explicit container extractors for the bridged property +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/Book.java[tags=include;!getters-setters] +---- +<1> Apply a custom bridge to the `pricesByEdition` property of the `ScientificPaper` entity. + +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/BookEditionsForSalePropertyBinder.java[tags=include] +---- +<1> Explicitly mention that the bridge will access _keys_ from the `priceByEdition` property -- the paper editions. +Without this, Hibernate Search would have assumed that _values_ are accessed. +<2> Declare a dependency to the `label` property in paper editions. +<3> This is the actual code that accesses the paths as declared above. +==== + +[[mapper-orm-bridge-bridgedelement-dependencies-useRootOnly]] +== `useRootOnly()`: declaring no dependency at all + +If your bridge only accesses immutable properties, +then it's safe to declare that its only dependency is to the root object. + +To do so, call `.dependencies().useRootOnly()`. + +[NOTE] +==== +Without this call, Hibernate Search will suspect an oversight and will throw an exception on startup. +==== + +[[mapper-orm-bridge-bridgedelement-dependencies-fromOtherEntity]] +== `fromOtherEntity(...)`: declaring dependencies using the inverse path + +include::components/incubating-warning.asciidoc[] + +It is not always possible to represent the dependency as a path +from the bridged element to the values accessed by the bridge. + +In particular, when the bridge relies on other components (queries, services) to retrieve another entity, +there may not even be a path from the bridge element to that entity. +In this case, if there is an _inverse_ path from the other entity to the bridged element, +and the bridged element is an entity, +you can simply declare the dependency from the other entity, as shown below. + +.Declaring dependencies in a bridge using the inverse path +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/ScientificPaper.java[tags=include;!getters-setters] +---- +<1> Apply a custom bridge to the `ScientificPaper` type. + +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/ScientificPapersReferencedByBinder.java[tags=include] +---- +<1> Declare that this bridge relies on other entities of type `ScientificPaper`, +and that those other entities reference the indexed entity through their `references` property. +<2> Declare which parts of the other entities are actually used by the bridge. +<3> The bridge retrieves the other entity through a query, +but then uses exclusively the parts that were declared previously. +==== + +[WARNING] +==== +Currently, dependencies declared this way will be ignored when the "other entity" gets deleted. + +See https://hibernate.atlassian.net/browse/HSEARCH-3567[HSEARCH-3567] to track progress on solving this problem. +==== diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/DependenciesFromOtherEntityIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/DependenciesFromOtherEntityIT.java new file mode 100644 index 00000000000..468708a6e15 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/DependenciesFromOtherEntityIT.java @@ -0,0 +1,132 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.fromotherentity; + + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.persistence.EntityManagerFactory; + +import org.hibernate.search.documentation.testsupport.BackendConfigurations; +import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.automaticindexing.session.AutomaticIndexingSynchronizationStrategyNames; +import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; +import org.hibernate.search.mapper.orm.session.SearchSession; +import org.hibernate.search.util.impl.integrationtest.common.rule.BackendConfiguration; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmSetupHelper; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DependenciesFromOtherEntityIT { + @Parameterized.Parameters(name = "{0}") + public static Object[] backendConfigurations() { + return BackendConfigurations.simple().toArray(); + } + + @Rule + public OrmSetupHelper setupHelper; + + private EntityManagerFactory entityManagerFactory; + + public DependenciesFromOtherEntityIT(BackendConfiguration backendConfiguration) { + this.setupHelper = OrmSetupHelper.withSingleBackend( backendConfiguration ); + } + + @Before + public void setup() { + entityManagerFactory = setupHelper.start() + .withProperty( + HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, + AutomaticIndexingSynchronizationStrategyNames.SYNC + ) + .setup( ScientificPaper.class ); + } + + @Test + public void smoke() { + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + ScientificPaper paper1 = new ScientificPaper( 1 ); + paper1.setTitle( "Fundamental Ideas of the General Theory of Relativity and the Application of this Theory in Astronomy" ); + ScientificPaper paper2 = new ScientificPaper( 2 ); + paper2.setTitle( "On the General Theory of Relativity" ); + paper2.getReferences().add( paper1 ); + ScientificPaper paper3 = new ScientificPaper( 3 ); + paper3.setTitle( "Explanation of the Perihelion Motion of Mercury from the General Theory of Relativity" ); + paper3.getReferences().add( paper1 ); + paper3.getReferences().add( paper2 ); + + entityManager.persist( paper1 ); + entityManager.persist( paper2 ); + entityManager.persist( paper3 ); + } ); + + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + SearchSession searchSession = Search.session( entityManager ); + + assertThat( searchSession.search( ScientificPaper.class ) + .where( f -> f.match().field( "referencedBy" ) + .matching( "Field Equations Gravitation" ) ) + .fetchHits( 20 ) + ) + .hasSize( 0 ); + assertThat( searchSession.search( ScientificPaper.class ) + .where( f -> f.match().field( "referencedBy" ) + .matching( "Perihelion Motion Mercury" ) ) + .fetchHits( 20 ) + ) + .hasSize( 2 ); + assertThat( searchSession.search( ScientificPaper.class ) + .where( f -> f.match().field( "referencedBy" ) + .matching( "Fundamental Ideas" ) ) + .fetchHits( 20 ) + ) + .hasSize( 0 ); + } ); + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + ScientificPaper paper1 = entityManager.find( ScientificPaper.class, 1 ); + ScientificPaper paper2 = entityManager.find( ScientificPaper.class, 2 ); + ScientificPaper paper3 = entityManager.find( ScientificPaper.class, 3 ); + ScientificPaper paper4 = new ScientificPaper( 4 ); + paper4.setTitle( "The Field Equations of Gravitation" ); + paper4.getReferences().add( paper1 ); + paper4.getReferences().add( paper2 ); + paper4.getReferences().add( paper3 ); + + entityManager.persist( paper4 ); + } ); + + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + SearchSession searchSession = Search.session( entityManager ); + + assertThat( searchSession.search( ScientificPaper.class ) + .where( f -> f.match().field( "referencedBy" ) + .matching( "Field Equations Gravitation" ) ) + .fetchHits( 20 ) + ) + .hasSize( 3 ); + assertThat( searchSession.search( ScientificPaper.class ) + .where( f -> f.match().field( "referencedBy" ) + .matching( "Perihelion Motion Mercury" ) ) + .fetchHits( 20 ) + ) + .hasSize( 2 ); + assertThat( searchSession.search( ScientificPaper.class ) + .where( f -> f.match().field( "referencedBy" ) + .matching( "Fundamental Ideas" ) ) + .fetchHits( 20 ) + ) + .hasSize( 0 ); + } ); + } + +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/ScientificPaper.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/ScientificPaper.java new file mode 100644 index 00000000000..3cad865f808 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/ScientificPaper.java @@ -0,0 +1,61 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.fromotherentity; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; + +import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.TypeBinderRef; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.TypeBinding; + +//tag::include[] +@Entity +@Indexed +@TypeBinding(binder = @TypeBinderRef(type = ScientificPapersReferencedByBinder.class)) // <1> +public class ScientificPaper { + + @Id + private Integer id; + + private String title; + + @ManyToMany + private List references = new ArrayList<>(); + + public ScientificPaper() { + } + + // Getters and setters + // ... + + //tag::getters-setters[] + public ScientificPaper(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List getReferences() { + return references; + } + //end::getters-setters[] +} +//end::include[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/ScientificPapersReferencedByBinder.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/ScientificPapersReferencedByBinder.java new file mode 100644 index 00000000000..544409cc2d5 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/fromotherentity/ScientificPapersReferencedByBinder.java @@ -0,0 +1,65 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.fromotherentity; + +import java.util.List; + +import org.hibernate.Session; +import org.hibernate.query.Query; +import org.hibernate.search.engine.backend.document.DocumentElement; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.mapper.orm.HibernateOrmExtension; +import org.hibernate.search.mapper.pojo.bridge.TypeBridge; +import org.hibernate.search.mapper.pojo.bridge.binding.TypeBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.TypeBinder; +import org.hibernate.search.mapper.pojo.bridge.runtime.TypeBridgeWriteContext; + +//tag::include[] +public class ScientificPapersReferencedByBinder implements TypeBinder { + + @Override + public void bind(TypeBindingContext context) { + context.dependencies() + .fromOtherEntity( ScientificPaper.class, "references" ) // <1> + .use( "title" ); // <2> + + IndexFieldReference papersReferencingThisOneField = context.indexSchemaElement() + .field( "referencedBy", f -> f.asString().analyzer( "english" ) ) + .multiValued() + .toReference(); + + context.bridge( new Bridge( papersReferencingThisOneField ) ); + } + + private static class Bridge implements TypeBridge { + + private final IndexFieldReference referencedByField; + + private Bridge(IndexFieldReference referencedByField) { // <2> + this.referencedByField = referencedByField; + } + + @Override + public void write(DocumentElement target, Object bridgedElement, TypeBridgeWriteContext context) { + ScientificPaper paper = (ScientificPaper) bridgedElement; + + for ( String referencingPaperTitle : findReferencingPaperTitles( context, paper ) ) { // <3> + target.addValue( referencedByField, referencingPaperTitle ); + } + } + + private List findReferencingPaperTitles(TypeBridgeWriteContext context, ScientificPaper paper) { + Session session = context.extension( HibernateOrmExtension.get() ).session(); + Query query = session.createQuery( + "select p.title from ScientificPaper p where :this member of p.references", + String.class ); + query.setParameter( "this", paper ); + return query.list(); + } + } +} +//end::include[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/Book.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/Book.java new file mode 100644 index 00000000000..71178835682 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/Book.java @@ -0,0 +1,92 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.property; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.MapKeyJoinColumn; +import javax.persistence.OrderBy; + +import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.PropertyBinderRef; +import org.hibernate.search.mapper.pojo.extractor.builtin.BuiltinContainerExtractors; +import org.hibernate.search.mapper.pojo.extractor.mapping.annotation.ContainerExtraction; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AssociationInverseSide; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyBinding; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue; + +// Note: this example is too complex for its own good, but I can't think of a much simpler one. +//tag::include[] +@Entity +@Indexed +public class Book { + + @Id + @GeneratedValue + private Integer id; + + @FullTextField(analyzer = "name") + private String title; + + @ElementCollection + @JoinTable( + name = "book_editionbyprice", + joinColumns = @JoinColumn(name = "book_id") + ) + @MapKeyJoinColumn(name = "edition_id") + @Column(name = "price") + @OrderBy("edition_id asc") + @AssociationInverseSide( + extraction = @ContainerExtraction(BuiltinContainerExtractors.MAP_KEY), + inversePath = @ObjectPath( @PropertyValue( propertyName = "book" ) ) + ) + @PropertyBinding(binder = @PropertyBinderRef(type = BookEditionsForSalePropertyBinder.class)) // <1> + private Map priceByEdition = new LinkedHashMap<>(); + + public Book() { + } + + // Getters and setters + // ... + + //tag::getters-setters[] + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Map getPriceByEdition() { + return priceByEdition; + } + + public void setPriceByEdition(Map priceByEdition) { + this.priceByEdition = priceByEdition; + } + //end::getters-setters[] +} +//end::include[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/BookEdition.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/BookEdition.java new file mode 100644 index 00000000000..74dbcb173d3 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/BookEdition.java @@ -0,0 +1,52 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.property; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity +public class BookEdition { + + @Id + @GeneratedValue + private Integer id; + + @ManyToOne + private Book book; + + private String label; + + public BookEdition() { + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Book getBook() { + return book; + } + + public void setBook(Book book) { + this.book = book; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/BookEditionsForSalePropertyBinder.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/BookEditionsForSalePropertyBinder.java new file mode 100644 index 00000000000..9e91028d342 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/BookEditionsForSalePropertyBinder.java @@ -0,0 +1,56 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.property; + +import java.util.Map; + +import org.hibernate.search.engine.backend.document.DocumentElement; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.mapper.pojo.bridge.PropertyBridge; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder; +import org.hibernate.search.mapper.pojo.bridge.runtime.PropertyBridgeWriteContext; +import org.hibernate.search.mapper.pojo.extractor.builtin.BuiltinContainerExtractors; +import org.hibernate.search.mapper.pojo.extractor.mapping.programmatic.ContainerExtractorPath; + +//tag::include[] +public class BookEditionsForSalePropertyBinder implements PropertyBinder { + + @Override + public void bind(PropertyBindingContext context) { + context.dependencies() + .use( ContainerExtractorPath.explicitExtractor( BuiltinContainerExtractors.MAP_KEY ), // <1> + "label" ); // <2> + + IndexFieldReference editionsForSaleField = context.indexSchemaElement() + .field( "editionsForSale", f -> f.asString().analyzer( "english" ) ) + .multiValued() + .toReference(); + + context.bridge( new Bridge( editionsForSaleField ) ); + } + + private static class Bridge implements PropertyBridge { + + private final IndexFieldReference editionsForSaleField; + + private Bridge(IndexFieldReference editionsForSaleField) { + this.editionsForSaleField = editionsForSaleField; + } + + @Override + @SuppressWarnings("unchecked") + public void write(DocumentElement target, Object bridgedElement, PropertyBridgeWriteContext context) { + Map priceByEdition = (Map) bridgedElement; + + for ( BookEdition edition : priceByEdition.keySet() ) { // <3> + target.addValue( editionsForSaleField, edition.getLabel() ); + } + } + } +} +//end::include[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/DependenciesContainersPropertyIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/DependenciesContainersPropertyIT.java new file mode 100644 index 00000000000..315d7f59da9 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/property/DependenciesContainersPropertyIT.java @@ -0,0 +1,91 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.property; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.util.List; +import javax.persistence.EntityManagerFactory; + +import org.hibernate.search.documentation.testsupport.BackendConfigurations; +import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.automaticindexing.session.AutomaticIndexingSynchronizationStrategyNames; +import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; +import org.hibernate.search.mapper.orm.session.SearchSession; +import org.hibernate.search.util.impl.integrationtest.common.rule.BackendConfiguration; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmSetupHelper; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DependenciesContainersPropertyIT { + + @Parameterized.Parameters(name = "{0}") + public static Object[] backendConfigurations() { + return BackendConfigurations.simple().toArray(); + } + + @Rule + public OrmSetupHelper setupHelper; + + private EntityManagerFactory entityManagerFactory; + + public DependenciesContainersPropertyIT(BackendConfiguration backendConfiguration) { + this.setupHelper = OrmSetupHelper.withSingleBackend( backendConfiguration ); + } + + @Before + public void setup() { + entityManagerFactory = setupHelper.start() + .withProperty( + HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, + AutomaticIndexingSynchronizationStrategyNames.SYNC + ) + .setup( Book.class, BookEdition.class ); + } + + @Test + public void smoke() { + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setTitle( "The Caves Of Steel" ); + + BookEdition edition1 = new BookEdition(); + edition1.setLabel( "Mass Market Paperback, 12th Edition" ); + edition1.setBook( book ); + book.getPriceByEdition().put( edition1, new BigDecimal( "25.99" ) ); + + BookEdition edition2 = new BookEdition(); + edition2.setLabel( "Kindle Edition" ); + edition2.setBook( book ); + book.getPriceByEdition().put( edition2, new BigDecimal( "15.99" ) ); + + entityManager.persist( edition1 ); + entityManager.persist( edition2 ); + entityManager.persist( book ); + } ); + + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + SearchSession searchSession = Search.session( entityManager ); + + List result = searchSession.search( Book.class ) + .where( f -> f.bool() + .must( f.match().field( "editionsForSale" ).matching( "paperback" ) ) + .must( f.match().field( "editionsForSale" ).matching( "kindle" ) ) + ) + .fetchHits( 20 ); + assertThat( result ).hasSize( 1 ); + } ); + } + +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/Book.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/Book.java new file mode 100644 index 00000000000..703a9c1d9aa --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/Book.java @@ -0,0 +1,92 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.simple; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.MapKeyJoinColumn; +import javax.persistence.OrderBy; + +import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.TypeBinderRef; +import org.hibernate.search.mapper.pojo.extractor.builtin.BuiltinContainerExtractors; +import org.hibernate.search.mapper.pojo.extractor.mapping.annotation.ContainerExtraction; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AssociationInverseSide; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.TypeBinding; + +// Note: this example is too complex for its own good, but I can't think of a much simpler one. +//tag::include[] +@Entity +@Indexed +@TypeBinding(binder = @TypeBinderRef(type = BookEditionsForSaleTypeBinder.class)) // <1> +public class Book { + + @Id + @GeneratedValue + private Integer id; + + @FullTextField(analyzer = "name") + private String title; + + @ElementCollection + @JoinTable( + name = "book_editionbyprice", + joinColumns = @JoinColumn(name = "book_id") + ) + @MapKeyJoinColumn(name = "edition_id") + @Column(name = "price") + @OrderBy("edition_id asc") + @AssociationInverseSide( + extraction = @ContainerExtraction(BuiltinContainerExtractors.MAP_KEY), + inversePath = @ObjectPath( @PropertyValue( propertyName = "book" ) ) + ) + private Map priceByEdition = new LinkedHashMap<>(); // <2> + + public Book() { + } + + // Getters and setters + // ... + + //tag::getters-setters[] + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Map getPriceByEdition() { + return priceByEdition; + } + + public void setPriceByEdition(Map priceByEdition) { + this.priceByEdition = priceByEdition; + } + //end::getters-setters[] +} +//end::include[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/BookEdition.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/BookEdition.java new file mode 100644 index 00000000000..024b40fc405 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/BookEdition.java @@ -0,0 +1,52 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.simple; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity +public class BookEdition { + + @Id + @GeneratedValue + private Integer id; + + @ManyToOne + private Book book; + + private String label; + + public BookEdition() { + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Book getBook() { + return book; + } + + public void setBook(Book book) { + this.book = book; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/BookEditionsForSaleTypeBinder.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/BookEditionsForSaleTypeBinder.java new file mode 100644 index 00000000000..1d503aa8411 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/BookEditionsForSaleTypeBinder.java @@ -0,0 +1,56 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.simple; + +import org.hibernate.search.engine.backend.document.DocumentElement; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.mapper.pojo.bridge.TypeBridge; +import org.hibernate.search.mapper.pojo.bridge.binding.TypeBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.TypeBinder; +import org.hibernate.search.mapper.pojo.bridge.runtime.TypeBridgeWriteContext; +import org.hibernate.search.mapper.pojo.extractor.builtin.BuiltinContainerExtractors; +import org.hibernate.search.mapper.pojo.model.path.PojoModelPath; + +//tag::include[] +public class BookEditionsForSaleTypeBinder implements TypeBinder { + + @Override + public void bind(TypeBindingContext context) { + context.dependencies() + .use( PojoModelPath.builder() // <1> + .property( "priceByEdition" ) // <2> + .value( BuiltinContainerExtractors.MAP_KEY ) // <3> + .property( "label" ) // <4> + .toValuePath() ); // <5> + + IndexFieldReference editionsForSaleField = context.indexSchemaElement() + .field( "editionsForSale", f -> f.asString().analyzer( "english" ) ) + .multiValued() + .toReference(); + + context.bridge( new Bridge( editionsForSaleField ) ); + } + + private static class Bridge implements TypeBridge { + + private final IndexFieldReference editionsForSaleField; + + private Bridge(IndexFieldReference editionsForSaleField) { + this.editionsForSaleField = editionsForSaleField; + } + + @Override + public void write(DocumentElement target, Object bridgedElement, TypeBridgeWriteContext context) { + Book book = (Book) bridgedElement; + + for ( BookEdition edition : book.getPriceByEdition().keySet() ) { // <6> + target.addValue( editionsForSaleField, edition.getLabel() ); + } + } + } +} +//end::include[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/DependenciesContainersSimpleIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/DependenciesContainersSimpleIT.java new file mode 100644 index 00000000000..ce5b7e5b2dd --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/containers/simple/DependenciesContainersSimpleIT.java @@ -0,0 +1,91 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.containers.simple; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.util.List; +import javax.persistence.EntityManagerFactory; + +import org.hibernate.search.documentation.testsupport.BackendConfigurations; +import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.automaticindexing.session.AutomaticIndexingSynchronizationStrategyNames; +import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; +import org.hibernate.search.mapper.orm.session.SearchSession; +import org.hibernate.search.util.impl.integrationtest.common.rule.BackendConfiguration; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmSetupHelper; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DependenciesContainersSimpleIT { + + @Parameterized.Parameters(name = "{0}") + public static Object[] backendConfigurations() { + return BackendConfigurations.simple().toArray(); + } + + @Rule + public OrmSetupHelper setupHelper; + + private EntityManagerFactory entityManagerFactory; + + public DependenciesContainersSimpleIT(BackendConfiguration backendConfiguration) { + this.setupHelper = OrmSetupHelper.withSingleBackend( backendConfiguration ); + } + + @Before + public void setup() { + entityManagerFactory = setupHelper.start() + .withProperty( + HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, + AutomaticIndexingSynchronizationStrategyNames.SYNC + ) + .setup( Book.class, BookEdition.class ); + } + + @Test + public void smoke() { + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + Book book = new Book(); + book.setTitle( "The Caves Of Steel" ); + + BookEdition edition1 = new BookEdition(); + edition1.setLabel( "Mass Market Paperback, 12th Edition" ); + edition1.setBook( book ); + book.getPriceByEdition().put( edition1, new BigDecimal( "25.99" ) ); + + BookEdition edition2 = new BookEdition(); + edition2.setLabel( "Kindle Edition" ); + edition2.setBook( book ); + book.getPriceByEdition().put( edition2, new BigDecimal( "15.99" ) ); + + entityManager.persist( edition1 ); + entityManager.persist( edition2 ); + entityManager.persist( book ); + } ); + + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + SearchSession searchSession = Search.session( entityManager ); + + List result = searchSession.search( Book.class ) + .where( f -> f.bool() + .must( f.match().field( "editionsForSale" ).matching( "paperback" ) ) + .must( f.match().field( "editionsForSale" ).matching( "kindle" ) ) + ) + .fetchHits( 20 ); + assertThat( result ).hasSize( 1 ); + } ); + } + +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/Author.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/Author.java new file mode 100644 index 00000000000..9f210c90730 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/Author.java @@ -0,0 +1,54 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.simple; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +@Entity +public class Author { + + @Id + @GeneratedValue + private Integer id; + + private String firstName; + + private String lastName; + + @OneToMany(mappedBy = "author") + private List books = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public List getBooks() { + return books; + } + +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/AuthorFullNameBinder.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/AuthorFullNameBinder.java new file mode 100644 index 00000000000..b333706e8f3 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/AuthorFullNameBinder.java @@ -0,0 +1,55 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.simple; + +import org.hibernate.search.engine.backend.document.DocumentElement; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.mapper.pojo.bridge.TypeBridge; +import org.hibernate.search.mapper.pojo.bridge.binding.TypeBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.TypeBinder; +import org.hibernate.search.mapper.pojo.bridge.runtime.TypeBridgeWriteContext; + +//tag::binder[] +public class AuthorFullNameBinder implements TypeBinder { + + @Override + public void bind(TypeBindingContext context) { + context.dependencies() // <1> + .use( "author.firstName" ) // <2> + .use( "author.lastName" ); // <3> + + IndexFieldReference authorFullNameField = context.indexSchemaElement() + .field( "authorFullName", f -> f.asString().analyzer( "name" ) ) + .toReference(); + + context.bridge( new Bridge( authorFullNameField ) ); + } + + private static class Bridge implements TypeBridge { + + // ... + //end::binder[] + + private final IndexFieldReference authorFullNameField; + + private Bridge(IndexFieldReference authorFullNameField) { // <2> + this.authorFullNameField = authorFullNameField; + } + + @Override + public void write(DocumentElement target, Object bridgedElement, TypeBridgeWriteContext context) { + Book book = (Book) bridgedElement; + Author author = book.getAuthor(); + + String fullName = author.getLastName() + " " + author.getFirstName(); + + target.addValue( this.authorFullNameField, fullName ); + } + //tag::binder[] + } +} +//end::binder[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/Book.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/Book.java new file mode 100644 index 00000000000..1b8880fbb34 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/Book.java @@ -0,0 +1,52 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.simple; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.TypeBinderRef; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.TypeBinding; + +@Entity +@Indexed +@TypeBinding(binder = @TypeBinderRef(type = AuthorFullNameBinder.class)) +public class Book { + + @Id + @GeneratedValue + private Integer id; + + private String title; + + @ManyToOne + private Author author; + + public Integer getId() { + return id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/DependenciesSimpleIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/DependenciesSimpleIT.java new file mode 100644 index 00000000000..1f63664739d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/dependencies/simple/DependenciesSimpleIT.java @@ -0,0 +1,82 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.dependencies.simple; + + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import javax.persistence.EntityManagerFactory; + +import org.hibernate.search.documentation.testsupport.BackendConfigurations; +import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.automaticindexing.session.AutomaticIndexingSynchronizationStrategyNames; +import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; +import org.hibernate.search.mapper.orm.session.SearchSession; +import org.hibernate.search.util.impl.integrationtest.common.rule.BackendConfiguration; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmSetupHelper; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DependenciesSimpleIT { + @Parameterized.Parameters(name = "{0}") + public static Object[] backendConfigurations() { + return BackendConfigurations.simple().toArray(); + } + + @Rule + public OrmSetupHelper setupHelper; + + private EntityManagerFactory entityManagerFactory; + + public DependenciesSimpleIT(BackendConfiguration backendConfiguration) { + this.setupHelper = OrmSetupHelper.withSingleBackend( backendConfiguration ); + } + + @Before + public void setup() { + entityManagerFactory = setupHelper.start() + .withProperty( + HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, + AutomaticIndexingSynchronizationStrategyNames.SYNC + ) + .setup( Book.class, Author.class ); + } + + @Test + public void smoke() { + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + Author author = new Author(); + author.setFirstName( "Isaac" ); + author.setLastName( "Asimov" ); + entityManager.persist( author ); + Book book = new Book(); + book.setTitle( "I, Robot" ); + book.setAuthor( author ); + author.getBooks().add( book ); + entityManager.persist( author ); + entityManager.persist( book ); + } ); + + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + SearchSession searchSession = Search.session( entityManager ); + + List result = searchSession.search( Book.class ) + .where( f -> f.match().field( "authorFullName" ).matching( "isaac asimov" ) ) + .fetchHits( 20 ); + + assertThat( result ).hasSize( 1 ); + } ); + } + +} diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoOtherEntityIndexingDependencyConfigurationContext.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoOtherEntityIndexingDependencyConfigurationContext.java index aca98d6f923..fe854925de8 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoOtherEntityIndexingDependencyConfigurationContext.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoOtherEntityIndexingDependencyConfigurationContext.java @@ -32,7 +32,7 @@ default PojoOtherEntityIndexingDependencyConfigurationContext use(String pathFro *

* Every component of this path will be considered as a dependency, * so it is not necessary to call this method for every subpath. - * In other words, if the path {@code "myProperty.someOtherPropety"} is declared as used, + * In other words, if the path {@code "myProperty.someOtherProperty"} is declared as used, * Hibernate Search will automatically assume that {@code "myProperty"} is also used. * * @param pathFromBridgedTypeToUsedValue The path from the entity type to the value used by the bridge. diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoPropertyIndexingDependencyConfigurationContext.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoPropertyIndexingDependencyConfigurationContext.java index 2876b1dd4c6..ba54b385708 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoPropertyIndexingDependencyConfigurationContext.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoPropertyIndexingDependencyConfigurationContext.java @@ -76,7 +76,7 @@ default PojoPropertyIndexingDependencyConfigurationContext use(ContainerExtracto *

* Every component of this path will be considered as a dependency, * so it is not necessary to call this method for every subpath. - * In other words, if the path {@code "myProperty.someOtherPropety"} is declared as used, + * In other words, if the path {@code "myProperty.someOtherProperty"} is declared as used, * Hibernate Search will automatically assume that {@code "myProperty"} is also used. * * @param extractorPathFromBridgedProperty A container extractor path from the bridged property. diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoTypeIndexingDependencyConfigurationContext.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoTypeIndexingDependencyConfigurationContext.java index 071ad27bf37..80a09417b71 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoTypeIndexingDependencyConfigurationContext.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/dependency/PojoTypeIndexingDependencyConfigurationContext.java @@ -42,14 +42,13 @@ default PojoTypeIndexingDependencyConfigurationContext use(String pathFromBridge *

* Every component of this path will be considered as a dependency, * so it is not necessary to call this method for every subpath. - * In other words, if the path {@code "myProperty.someOtherPropety"} is declared as used, + * In other words, if the path {@code "myProperty.someOtherProperty"} is declared as used, * Hibernate Search will automatically assume that {@code "myProperty"} is also used. * * @param pathFromBridgedTypeToUsedValue The path from the bridged type to the value used by the bridge. * @return {@code this}, for method chaining. * @throws org.hibernate.search.util.common.SearchException If the given path cannot be applied to the bridged type. */ - @Incubating PojoTypeIndexingDependencyConfigurationContext use(PojoModelPathValueNode pathFromBridgedTypeToUsedValue); /**