How do I search for a nested #Indexed key in Redis redis-om-spring? - spring-boot

I am trying to search for a nested #Indexed field using redis-om-spring
For some reason a find returns expected 1 entry for level 1, but returns unexpected 0 entries for level 2.
I am not sure if I can only search one level deep or if I am making a mistake.
#SpringBootApplication
#Configuration
#EnableRedisDocumentRepositories
#Slf4j
public class Application {
#Autowired
ProductRepository productRepository;
#Bean
CommandLineRunner loadTestData() {
return args -> {
productRepository.deleteAll();
productRepository.save(new Product(new MyKey(new MyId("A_level2Key1"),"A_level1Key1"),"FirstColour"));
productRepository.save(new Product(new MyKey(new MyId("B_level2Key1"),"B_level1Key1"),"SecondColour"));
var byMyKeyLevel1Key = productRepository.findByMyKeyLevel1Key("A_level1Key1");
System.out.println(byMyKeyLevel1Key.size());//returns expected 1 entry for level 1
var byMyKeyMyIdLevel2Key = productRepository.findByMyKeyMyIdLevel2Key("A_level2Key1");
System.out.println(byMyKeyMyIdLevel2Key.size());//returns unexpected 0 entries for level 2
};
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Product
import com.redis.om.spring.annotations.Document;
import com.redis.om.spring.annotations.Indexed;
import com.redis.om.spring.annotations.Searchable;
#Slf4j
#Data
#AllArgsConstructor
#NoArgsConstructor
#Document
public class Product {
#Indexed
#Id
private MyKey myKey;
#Searchable
#NonNull
private String colourDesc;
}
ProductRepository
public interface ProductRepository extends RedisDocumentRepository<Product, String> {
List<Product> findByMyKeyLevel1Key(String level1Key);
List<Product> findByMyKeyMyIdLevel2Key(String level2Key);
}
MyKey
import com.redis.om.spring.annotations.Indexed;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
public class MyKey {
#Indexed
MyId myId;
#Indexed
String level1Key;
}
Data in Redis:
FT.INFO "com.redis.om.documents.domain.ProductIdx"
From the logs, it generates searches:
FT.SEARCH "com.redis.om.documents.domain.ProductIdx" #myKey_level1Key:{A_level1Key1}
FT.SEARCH "com.redis.om.documents.domain.ProductIdx" #myKey_myId_level2Key:{A_level2Key1}
I start Redis with:
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest

The issue is likely in the naming of the repository methods. I wrote your example as a test in the project repo, this will be using release 0.7.0 - but it should work with 0.6.x-SNAPSHOTs:
The entity:
#Data #AllArgsConstructor #NoArgsConstructor #Document public class Product {
#Indexed #Id private MyKey myKey;
#Searchable #NonNull private String colourDesc;
}
The first-level key:
#Data #AllArgsConstructor #NoArgsConstructor public class MyKey {
#Indexed MyId myId;
#Indexed String level1Key;
#Override public String toString() {
return myId.toString();
}
}
The second-level key:
#Data #AllArgsConstructor #NoArgsConstructor public class MyId {
#Indexed
private String level2Key;
#Override public String toString() {
return level2Key;
}
}
The repo, here's where things probably went wrong for you...
public interface ProductRepository extends RedisDocumentRepository<Product, MyKey> {
List<Product> findByMyKey_Level1Key(String level1Key);
List<Product> findByMyKey_MyId_Level2Key(String level2Key);
}
and here's a snippet of the test class I used:
#Autowired ProductRepository productRepository;
#BeforeEach
void setup() {
productRepository.deleteAll();
productRepository.save(new Product(new MyKey(new MyId("A_level2Key1"),"A_level1Key1"),"FirstColour"));
productRepository.save(new Product(new MyKey(new MyId("B_level2Key1"),"B_level1Key1"),"SecondColour"));
}
#Test
void testFindNestedKeyValues() {
var byMyKeyLevel1Key = productRepository.findByMyKey_Level1Key("A_level1Key1");
assertThat(byMyKeyLevel1Key).map(Product::getMyKey).map(MyKey::getLevel1Key).containsExactly("A_level1Key1");
var byMyKeyMyIdLevel2Key = productRepository.findByMyKey_MyId_Level2Key("A_level2Key1");
assertThat(byMyKeyMyIdLevel2Key).map(Product::getMyKey).map(MyKey::getMyId).map(MyId::getLevel2Key).containsExactly("A_level2Key1");
}
Hope that helps! Thanks for trying Redis OM Spring!

Related

SpringBoot StackOverflow error when getting entity

I have following problem. I'm new to Spring. I have created 2 entities and now using postman I want to get all books but I keep getting StackOverflowError.
Here is book model
package com.example.demo;
import jakarta.persistence.*;
import java.util.List;
#Entity
public class BookEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String title;
#ManyToMany
private List<Author> author;
public BookEntity() {
}
public BookEntity(String title) {
this.title = title;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<Author> getAuthor() {
return author;
}
public void setAuthor(List<Author> author) {
this.author = author;
}
}
Author class model
package com.example.demo;
import jakarta.persistence.*;
import java.util.List;
#Entity
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#ManyToMany
private List<BookEntity> book;
public Author() {
}
public Author(String name) {
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<BookEntity> getBook() {
return book;
}
public void setBook(List<BookEntity> book) {
this.book = book;
}
}
repository for books
package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface BookRepository extends JpaRepository<BookEntity, Long> {
}
repository for author
package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {
}
controller for books
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
#RequestMapping("/books")
public class BookController {
private final AuthorRepository authorRepository;
private final BookRepository bookRepository;
public BookController(AuthorRepository authorRepository, BookRepository bookRepository) {
this.authorRepository = authorRepository;
this.bookRepository = bookRepository;
}
#GetMapping
List<BookEntity> getAllBooks() {
return bookRepository.findAll();
}
}
Can you please explain what is happening? I can't get any further. I'm stuck
Well this is a common issue. The problem is that you have Book and Author related as ManyToMany. So now whenever you reach for Books, they have an Author field, and when Jackson is trying to add Author it turns out that Author has Books which again have an Author.
Im am aware of 2 ways out of here. First one is DTO you should create a class to be displayed by you controller looking somewhat like this:
public class BookDTO {
private long bookId;
private String bookTitle;
private List<AuthorDTO> authors;
// constructors getters setters
}
situation is a bit complicated because of Many to many so yo need another DTO for authors
public class AuthorDTO {
private long authorId;
private String authorName;
//constructors getters setters
}
you could use a service layer to do all of the mapping. Then you should return BookDTO in your controller.
Another way out are annotations:
#ManyToMany
#JsonManagedReference
private List<Author> author;
and
#ManyToMany
#JsonBackReference
private List<BookEntity> book;
#JsoonManaged and back References will stop Jackson from digging into another entity.
Another thing is you should consider mappedBy in one of your Entities to prevent creating 2 tables.

How to configure integration test for the new Spring for GrapQL

I am currently learning Spring for GraphQL and am working on an application to do some basic CRUD operations, using the GraphQL API using Spring-boot with JPA and an H2 in-memory database for testing. Now I am trying to write some integration tests using the GraphlQlTester interface. There is a possibility to test importing just the service layer, storing the data in a collection at this layer. Now I want to be able to access the repository layer too to temporarily store and manipulate the test data in the h2 database. I have been researching the documentation online, but unfortunately, I have not found any possible way to approach this particular issue. Perhaps you may have an idea?
Entity Class
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = IDENTITY)
private long id;
private String street;
private String city;
private int postCode;
}
Controller Class (just the first method)
#Controller
#RequiredArgsConstructor
public class AddressController {
private final AddressService addressService;
private static final String ERROR_MESSAGE = "Address with id %d not found";
#QueryMapping
List<Address> findAllAddresses() {
return addressService.findAll();
}
Service Class (just the first method)
#Service
#RequiredArgsConstructor
public class AddressService {
private final String ERROR_MESSAGE = "Address with id %d not found";
private final AddressRepository addressRepository;
private final ContactInformationRepository contactInformationRepository;
public List<Address> findAll() {
return addressRepository.findAll();
}
Repository
public interface AddressRepository extends JpaRepository<Address, Long> {
}
Integration Test class
import com.soscarlos.dropit.entity.Address;
import com.soscarlos.dropit.repository.AddressRepository;
import com.soscarlos.dropit.service.AddressService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.context.annotation.Import;
import org.springframework.graphql.test.tester.GraphQlTester;
#GraphQlTest(AddressController.class)
#Import({AddressService.class, AddressRepository.class})
class AddressControllerTest {
#Autowired
GraphQlTester tester;
#Autowired
AddressService addressService;
#Autowired
AddressRepository addressRepository;
#Test
void findAllAddresses() {
// language=GraphQL
String document = """
query {
findAllAddresses {
id
street
city
postCode
}
}
""";
tester.document(document)
.execute()
.path("findAllAddresses")
.entityList(Address.class)
.hasSize(1);
}
I cannot load the application context because of this error basically
"Error creating bean with name 'com.soscarlos.dropit.repository.AddressRepository': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.soscarlos.dropit.repository.AddressRepository]: Specified class is an interface"

Spring boot MongoDb complex query

I have been learning myself MongoDB implementation in Spring Boot.
However, I came into a problem with complex queries.
I cannot find any right solution for how to implement complex queries to MongoDB from Spring boot.
I am querying the database with MongoRepository interface implementation.
Let's say that I have three collections:
Person - 1 Person can have many Pets.
Pet - 1 Pet can have 1 PetToy and 1 Person who owns him.
PetToy - 1 PetToy can belong to 1 Pet.
POJO classes are bellow
What do I want to achieve?
I want to make a query, which would be returned me a Person, whose Pet has a Toy (PetToy) with the name "Teddy".
I could not have found a way how to do it. Furthermore, is it the best practice to even use such complex queries, or is it better to write more of little ones in MongoDB?
POJOs:
#Document
#Data
#ToString
public class Person {
#Id
private String id;
private String firstname;
private String lastname;
private int age;
#DBRef
private Pet pet;
}
#Document
#Data
#ToString
public class Pet {
#Id
private String id;
private String name;
private int age;
#DBRef
private List<PetToy> toys;
}
#Document
#Data
#ToString
public class PetToy {
#Id
private String id;
private String name;
}
I have tried to use MongoRepositories; however, I was not able to make the complex query.
How can one write such a query to a MongoDB from Spring Boot?
Thank you very much in advance.
If you can use embedded attributes, the class model should be:
#Document
#Data
#Builder
public class Person {
#Id
private String id;
private String firstName;
private String lastName;
private int age;
private List<Pet> pets;
}
#Data
#Builder
public class Pet {
private String name;
private int age;
private List<PetToy> toys;
}
#Data
#Builder
public class PetToy {
private String name;
}
The repository with the method that achieves what you want:
public interface PersonRepository extends MongoRepository<Person, String> {
List<Person> getByPetsToysName(String name);
}
The getByPetsToysName method basically navigate between Person's attributes Person->pets->toys->name. More info here.
An example
#Configuration
#EnableMongoRepositories
public class TestMongo implements CommandLineRunner {
private final PersonRepository repository;
public TestMongo(PersonRepository repository) {
this.repository = repository;
}
#Override
public void run(String... args) throws Exception {
repository.save(Person.builder()
.firstName("John")
.lastName("Doe")
.age(20)
.pets(Stream.of(Pet.builder()
.name("Ursa")
.age(1)
.toys(Stream.of(PetToy.builder()
.name("Teddy")
.build())
.collect(Collectors.toList()))
.build())
.collect(Collectors.toList()))
.build());
repository.save(Person.builder()
.firstName("Phillip")
.lastName("Larson")
.age(21)
.pets(Stream.of(Pet.builder()
.name("Bella")
.age(5)
.toys(Stream.of(PetToy.builder()
.name("Lolo")
.build())
.collect(Collectors.toList()))
.build())
.collect(Collectors.toList()))
.build());
List<Person> persons = repository.getByPetsToysName("Teddy");
System.out.println(persons.size());
List<Person> persons1 = repository.getByPetsToysName("Lolo");
System.out.println(persons1.size());
}
}
Logs:
find using query: { "pets.toys.name" : "Teddy" } fields: Document{{}} for class: class Person in collection: person
If you want more complex queries you can to take a look at the Spring Data MongoDB docs.

Redis - Why details are saving both as HASH and SET using Spring Data Redis?

I am new to Redis and developing Spring Boot + Spring Data Redis example. I am using CrudRepository, Example and ExampleMatchers API to do the searching from the Redis Key value store DB.
Now when I simply run my code, I saw that persons data saved as SET and HASH as well. Is this correct ? What's the use of saving the Person details both as SET and HASH
Showing all my code
public enum Gender {
MALE, FEMALE {
#Override
public String toString() {
return "Superwoman";
}
}
}
Species.java
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Species {
#Indexed
private String name;
}
Person.java
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("persons")
public class Person {
#Id
private String id;
#Indexed
private String firstname;
private String lastname;
#Indexed
private Gender gender;
private List<String> nicknames;
#Indexed
private Integer age;
private Map<String, String> physicalAttributes;
#Reference
private Person relative;
private Species species;
}
PersonRepository.java
public interface PersonRepository extends CrudRepository<Person, String>, QueryByExampleExecutor<Person> {
}
RedisExampleDemoApplication.java
#SpringBootApplication
public class RedisExampleDemoApplication implements CommandLineRunner{
RedisMappingContext mappingContext = new RedisMappingContext();
ExampleQueryMapper mapper = new ExampleQueryMapper(mappingContext, new PathIndexResolver(mappingContext));
#Autowired
private PersonRepository personRepository;
public static void main(String[] args) {
SpringApplication.run(RedisExampleDemoApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
Person person = Person.builder().firstname("Walter").gender(Gender.MALE).age(50).build();
Person person1 = Person.builder().firstname("Savani").gender(Gender.FEMALE).age(35).build();
personRepository.save(person);
personRepository.save(person1);
// [firstname:Walter, gender:MALE, age:50]
RedisOperationChain operationChain = mapper.getMappedExample(Example.of(person, ExampleMatcher.matchingAny()));
System.out.println(operationChain.getOrSismember());
System.out.println("----------------------------------------------");
Person p = Person.builder().lastname("Foo").build();
RedisOperationChain roc = mapper.getMappedExample(Example.of(p));
System.out.println(" == "+roc.getOrSismember());
System.out.println("-- "+roc.getSismember());
}
}
May be it is late to answer now , the reason that SET is visible is because of the secondary Index. I.e in your example First name is annotated as Indexed. Redis consider this as secondary index which is default a SET.

Spring Data postgresql 10 insertion does not work

I am working on spring boot application with RestController, Service a Repository and an Entity.
My problem is when I call the web service to save my data in the data base, it seems it works fine and there is no exception thrown but when I check my data base I find that the table was created but I find no data saved. and here is what I get in the output(for each element in my list):
Hibernate:
insert
into
table_name
(columnOne, columnTwo)
values
(?, ?)
Here is my code:
RestController:
#RestController
#RequestMapping(path = "/api/")
public class myController {
#Autowired
private MyService myService;
#PostMapping(path="/inject/{year}")
public void myControllerMethod(#PathParam("year") Year year) {
this.myService.myServiceMethod(year);
}
}
Service:
#Service
public class MyService {
#Autowired
MyRepository myRepository;
public void myServiceMethod(Year year) {
List<MyEntity> myEntityList = this.parseMyEntityList(year);
this.myRepository.save(myEntityList)
}
}
Repository:
#Repository
public interface MyRepository extends CrudRepository<MyEntity, Long>, JpaSpecificationExecutor<InseeLibelle> {
}
Entity:
#Entity
#Table(name = "table_name", indexes = {
#Index(name = "columnOne_idx", columnList = "columnOne"),
#Index(name = "columneTwo_idx", columnList = "columnTwo"),
})
public class MyEntity{
#JsonIgnore
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long columnId;
#Column
private Integer columnOne;
#Column
private String columnTwo;
public Integer getColumnOne() {
return columnOne;
}
public void setColumnOne(Integer columnOne) {
this.columneOne = colmunOne;
}
public String getColumnTwo() {
return columnTwo;
}
public void setColumnTwo(String columnTwo) {
this.columnTwo = columnTwo;
}
}
I tried to add this line in the repository but it does not work too:
<S extends MyEntity> Iterable<S> save(Iterable<S> entities) ;
Perhaps the problem is with the pgAdmin (like my case), it does not show the data but they exist in the database, try findAll method in the repository or check them with select * directly.

Resources