I am trying to upgrade to Hibernate Search 6 since it seems quite easier to work with, but I find it difficult to configure the testing in Java. Anybody knows about any tutorials or anything? Maybee some known bugs with Spring boot 2?
I use Spring boot and @SpringBootTest and the autowired repository to store the testing data, i.e repository.save, but the indexes seems to be empty when I test, i.e nothing is found . I have updated my Hibernate search configuration values (spring.jpa.properties.hibernate.search.backend.indexes.default.directory.type) (so it is using the new version). I hopefully think I am missing some magic config so that Search will index my data.
1: Have tried to flush to index with
SearchSession searchSession = Search.session( entityManager );
searchSession.workspace().flush();
2: Have tried to confgure Hiberante search in test to write
spring.jpa.properties.hibernate.search.backend.indexes.default.directory.type = local-heap
spring.jpa.properties.hibernate.search.automatic_indexing
spring.jpa.properties.hibernate.search.schema_management.strategy = drop-and-create-and-drop
spring.jpa.properties.hibernate.search.synchronization.strategy = sync
Spring boot 2.4.5
Hibernate ORM 2.4.30.FINAL
Lucene search 6.0.3.Final
Are you using the indexes.default
intentionally?
From the docs Hibernate Search 7.0.0.Final: Reference Documentation
Index properties
(…)
With the root hibernate.search.backend.indexes.<index name>
, they set the value for a specific index, overriding the defaults (if any). The backend and index names must match the names defined in the mapping. For ORM entities, the default index name is the name of the indexed class, without the package: org.mycompany.Book
will have Book
as its default index name. Index names can be customized in the mapping.
You can look at hibernate-search/integrationtest/showcase/library at main · hibernate/hibernate-search · GitHub for many Tests it currently uses Spring 2.4.0
I’ve also added a very minimal Test to my example project here: Files · master · Peter Müller / spring-hibernate-search-6-demo · GitLab
@SpringBootTest(classes = DemoApplication.class)
@ActiveProfiles("test")
class PersonSearchServiceTest {
@Autowired
PersonRepository personRepository;
@Autowired
PersonSearchService personSearchService;
@Test
void search() {
Person person = new Person();
person.setName("Hans");
Person savedPerson = personRepository.save(person);
Page<Person> result = personSearchService.search(PageRequest.of(0, 10), "hans");
Assertions.assertEquals(1, result.getTotalElements(), "The one person should be found");
Person foundPerson = result.getContent().get(0);
Assertions.assertEquals(savedPerson.getId(), foundPerson.getId(), "The person should have the correct ID");
Assertions.assertEquals("Hans", foundPerson.getName(), "The person name should be correct");
}
}
with
# File: application.yaml
spring:
jpa.properties:
hibernate.search:
backend.directory.root: ./search-index
jackson:
serialization.indent_output: true
# File: application-test.yml ( `-test` so that it gets used as the Spring "test" active profile)
spring:
jpa.properties:
hibernate.search:
backend:
directory.type: local-heap
1 Like
Thanks for good reply. Yep I also read a bit more about the configuration and stopped using default in property name… I am using Spring boot updated to latest (2.4.5), I tried to print the index size in KB and the indexes seemed to be empty. I verified the entities are stored in the database.
@Service
public class PhotoSearch {
@PersistenceContext
private EntityManager entityManager;
@Autowired
PhotoRepository photoRepository;
@Transactional(readOnly = true)
public List<Photo> findOnName(String searchString) {
SearchSession searchSession = Search.session( entityManager );
SearchResult<Photo> result = searchSession.search( Photo.class )
.where( f -> f.match()
.fields( "name" )
.matching( searchString ) )
.fetchAll();
System.out.println("Found in db: " + photoRepository.findByName(searchString));
System.out.println("Found in index: " + result.hits().size());
List<Photo> hits = result.hits();
return hits;
}
public void indexAll() {
SearchSession searchSession = Search.session( entityManager );
MassIndexer indexer = searchSession.massIndexer( Photo.class );
try {
indexer.startAndWait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
I do the above to verify that I do correctly, i.e not wrong search.
I saw that in the referenced projects there were some other dependencies so I tried to include “hibernate-jpamodelgen” . Did not help
I tried to downgrade to Java 8 also but it did not help (I am using Java 11). My spring boot project is kind of clean. But I have noticed these outputs during startup:
23:36:58.603 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [no.skobba.web.holeinvoid.hibernatesearch.PhotoSearchTest]: class path resource [no/skobba/web/holeinvoid/hibernatesearch/PhotoSearchTest-context.xml] does not exist
23:36:58.604 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [no.skobba.web.holeinvoid.hibernatesearch.PhotoSearchTest]: class path resource [no/skobba/web/holeinvoid/hibernatesearch/PhotoSearchTestContext.groovy] does not exist
23:36:58.604 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [no.skobba.web.holeinvoid.hibernatesearch.PhotoSearchTest]: no resource found for suffixes {-context.xml, Context.groovy}.
23:36:58.780 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [no.skobba.web.holeinvoid.hibernatesearch.PhotoSearchTest]: using defaults.
I am using the old way of .properties file, could it be something there? These so be correct new:
application-test.properties
spring.jpa.properties.hibernate.search.backend.directory.type=local-heap
application.properties
spring.jpa.properties.hibernate.search.backend.directory.type=local-filesystem
spring.jpa.properties.hibernate.search.backend.directory.root=hibernateindex/
I tried to print the index size in KB
By using …directory.type=local-heap during the tests you tell Lucene put the index in RAM and not create a local folder for the index.
I think its more a Sprint Boot Test related problem than Hibernate Search, can you share a reduced failing test with it’s class Annotations and the failing Assertion/Error? Do all other Spring JPA-related tests work?
I saw that in the referenced projects there were some other dependencies so I tried to include “hibernate-jpamodelgen” . Did not help
You do not need jpamodelgen, it just generates Type-Safe MyEntity_
classes for SQL Queries with the JPA Criteria API. (and i used the helper classes for index field names which is slightly dodgy practice)
1 Like
Hi,
Good advice from @Peter_Muller , indeed the indexes.default
no longer makes sense in Hibernate Search 6 (it used to in some earlier betas, but the syntax was removed).
Assuming you save your data inside your test (and not using some SQL file to initialize the DB), indexing should happen automatically. I believe your problem is mainly in your properties.
Elasticsearch is a near-real-time engine: by default, indexed data is not visible immediately, but after about 1s. That’s to improve performance of indexing.
In order to change this behavior, you need to explicitly tell Hibernate Search that you want automatic indexing to be synchronous. That will perform poorly, but it’s useful for tests.
It seems you attempted to do so, but you mistyped the properties. This:
spring.jpa.properties.hibernate.search.synchronization.strategy = sync
Should be this:
spring.jpa.properties.hibernate.search.automatic_indexing.synchronization.strategy = sync
You can find more information about synchronization of automatic indexing in this section of the documentation.
As to tutorials, @Peter_Muller wrote a good one for Spring. There is also the official getting started guide, but it’s not specific to Spring. You can also find a list of known tutorials/articles about Hibernate Search on this page
2 Likes
Ah… I misread your post, sorry. You are using Lucene, not Elasticsearch. If so, unless you configured something specific, automatic indexing should already synchronous.
You should still fix your configuration properties, but most likely that won’t solve the problem.
If you are calling repository.save(photo)
directly from your test to persist your entities, then your problem is that you didn’t open a transaction. I bet this question details a problem very similar to yours, and the answer will probably solve your problem.
1 Like
Thanks for all the help, I really apricate it and I have learnt a lot about how Lucene works. I tried the Transaction as described in the linked question but it did not work, but some more messing on my branch for Hibernate 6 search and when I annotated my search method in the Search service with: @Transactional(readOnly = true), using org.springframework.transaction.annotation.Transactional it works, I tried it previously but I seemingly did something else wrong back then. But when I configure my test to run in one transaction with rollback after test it fails (i.e annotate the test class with @Transactional.). But maybe that is not possible now? I found a workaround which is to annotate my test classes with this:
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
Which gives each test an clean database
Most likely my original problem was that I had my test wrapped in a transaction with rollback after test so that I could have independent dataset for each test. And if you annotate the test class with Transactional then the Transactional annotation on the search method has no effect.
1 Like
Yes, you need to commit your changes in order for them to be indexed.
There are ways to control that manually, but frankly I wouldn’t recommand that for tests, where you want the behavior to be as similar as possible to the real thing.
If your test change the data, then indeed @DirtiesContext
is the way to go.