SpringData and Elasticsearch - java.lang.StackOverflowError while using ignoreFields - elasticsearch

I am using simple relationship (spring data elasticsearch) but springboot is throwing StackOverflowError
#Document(indexName = "users", type = "user")
public class User {
#Id
private String id;
#Field(type= FieldType.Nested,ignoreFields={"users"})
private Set<Group> groups = new HashSet<Group>();
}
#Document(indexName = "groups", type = "group")
public class Group {
#Id
String id;
#Field(type = FieldType.Nested, ignoreFields ={"groups"})
private Set<User> users = new HashSet<User>();
}
public interface UserRepository extends ElasticsearchRepository<User, String>{
}
public interface GroupRepository extends ElasticsearchRepository<Group, String> {
}
Any Idea what is problem?
Code is from https://github.com/spring-projects/spring-data-elasticsearch/tree/master/src/test/java/org/springframework/data/elasticsearch/entities
Thanks
Rajan

As you said: spring-boot is throwing StackOverflowError.
It's because of no safeguards or detecting circular dependencies in spring-boot.
Look at spring-boot source: MappingBuilder.java, there's loop that will never break when you have class A that have a #Field annotation referencing class B, that have #Field annotation referencing class A.

Related

Mapstruct - How to convert a DTO String parameter to an Entity object?

I'm new to Mapstruct and I'm trying to understand it properly.
What I want to achieve is converting from a DTO String parameter (carModel) to his Entity, retrieve using Service and Repository.
The problem is that Mapper class generated by Mapstruct is trying to inject the Service class with #Autowired annotation, but it's not working. The service is null.
Here's my #Mapper class:
#Mapper(componentModel = "spring", uses = CarModelService.class)
public interface KitMapper extends EntityMapper<KitDTO, Kit> {
KitMapper INSTANCE = Mappers.getMapper(KitMapper.class);
#Mapping(source = "weight", target = "systemWeight")
#Mapping(source = "carModel", target = "carModel")
Kit toEntity(KitDTO kitDTO);
}
public interface EntityMapper<D, E> {
E toEntity(D dto);
List<E> toEntity(List<D> dtoList);
}
The #Service class:
#Service
#Transactional
public class CarModelService {
private final CarModelRepository carModelRepository;
#Transactional(readOnly = true)
public CarModel findByName(String name) {
return carModelRepository.findByName(name).orElse(null);
}
}
The #Repository class:
#Repository
public interface CarModelRepository extends JpaRepository<CarModel, Long> {
Optional<CarModel> findByName(String carModelName);
}
The DTO and Entity classes:
public class KitDTO {
private String id;
private String carModel; // e.g. "Ferrari Monza"
....
}
#Entity
#Table(name = "kit")
public class Kit implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#ManyToOne
private CarModel carModel;
...
}
#Entity
#Table(name = "car_model")
public class CarModel implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
...
}
The build work properly but the application stop when I try to use the Mapper. It says that carModelService is null.
Here's the mapper generated implementation class:
#Component
public class KitMapperImpl implements KitMapper {
#Autowired // <-- this seems not working
private CarModelService carModelService;
#Override
public Kit toEntity(KitDTO kitDTO) {
if ( kitDTO == null ) {
return null;
}
Kit kit = new Kit();
kit.setSystemWeight( String.valueOf( kitDTO.getWeight() ) );
kit.carModel( carModelService.findByName(kitDTO.getCarModel()) ); // <-- carModelService is null!
// other setters
return kit;
}
}
I've tried many things, using Decorator, #Context, expression, inject the #Mapper class into the #Service class.
I've found many questions but actually no one helped me:
Mapstruct - How can I inject a spring dependency in the Generated Mapper class
#Service Class Not Autowired in org.mapstruct.#Mapper Class
MapStruct mapper not initialized with autowired when debug
Any help would be appreciated! Thanks in advance!
Found the solution!
Instead of calling directly the Mapper method toEntity() from the #RestController class, I injected the mapper in the CarModelService class and created a method that call the mapper.
In this way the flow is:
Controller --> Service --> Mapper
#Service
#Transactional
public class KitService {
private final KitRepository kitRepository;
private final KitSearchRepository kitSearchRepository;
private final KitMapper kitMapper; // <-- mapper declaration
public KitService(KitRepository kitRepository, KitSearchRepository kitSearchRepository, KitMapper kitMapper) {
this.kitRepository = kitRepository;
this.kitSearchRepository = kitSearchRepository;
this.kitMapper = kitMapper; // <-- mapper initilization
}
// here the method which calls mapper
public Kit convertDTOToEntity(KitDTO kitDTO) {
return kitMapper.toEntity(kitDTO);
}
In this way, the generated class by Mapstruct doesn't give error on the CarModelService.
Seems like this approach is the only way to achieve this, create a king of "bridge" between services and mappers.
(You can use also the #Autowired annotation instead of constructor)
Can you please share the error message?
From the information that you shared, I can see the carModel in KitDto is String and in Entity is CarModel class. Not sure how mapstruct's auto generated implementation class implemented this: kit.carModel( carModelService.findByName(kitDTO.getCarModel()) );.
But I would like to share another approach,Don't know this is a best practice or not. In this approach you can create a abstarct class of mapper, in which you can #Autowired repository can manually add those mapping.
I shared the snippet for it. Hopefully this will help you.
#Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public abstract class ProductMapper {
#Autowired
private CarModelService carModelService;
public abstract Kit convertDTOToEntity(KitDTO kitDTO);
public Kit toEntity(KitDTO kitDTO);
{
Kit kit = convertDTOToEntity(kitDTO);
kit.setCarModel(carModelService.findByName(kitDTO.getCarModel()));
return kit;
}
}
Curious about the other approaches, will follow this thread. We can discuss the best practices

Can't access a property of a Embedded class via JPA

#Entity
#EntityListeners(AuditingEntityListener.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "TIPO_CONTRATO", discriminatorType = DiscriminatorType.STRING)
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Contrato extends AuditorEntity implements Serializable, Clonable {
#Column(name = "CIF_NIF")
#JsonView(Views.Buscador.class)
#JsonProperty("cifNif")
private String cifNif;
#Column(name = "NOMBRE_SOCIEDAD_PERSONA")
#JsonView(Views.Buscador.class)
private String nombreSociedadPersona;
}
And i have this Embeddable class called CuentaBancaria from Contrato table:
#Embeddable
public class CuentaBancaria implements Serializable {
private static final long serialVersionUID = 6835775213299596371L;
#Column(name = "TITULAR_CUENTA")
#JsonView(Views.Completo.class)
private String titularCuenta;
}
In ContratoRepository i'm trying doing a JPA Query finding the "titularCuenta" field of Cuenta Bancaria finding by the cifNif field of Contrato. But it's not working. What can i do to solve this?
#Query(value="SELECT c.CuentaBancaria.titularCuenta FROM Contrato c WHERE c.cifNif= ?1 AND c.nombreSociedadPersona IS NOT NULL AND ROWNUM = 1")
public String getNombreLegalCliente(String cifNif);
The error which is throwing:
Caused by: org.hibernate.QueryException: could not resolve property:
CuentaBancaria of: com.xxxx.Contrato
You're missing CuentaBancaria field in Contrato class. That's why JQL complains.
Add the field in the class with #Embedded annotation:
public class Contrato extends AuditorEntity implements Serializable, Clonable {
#Embedded
private CuentaBancaria cuentaBancaria;
}
And fix the JQL expression to:
#Query(value="SELECT c.cuentaBancaria.titularCuenta FROM Contrato c WHERE c.cifNif= ?1 AND c.nombreSociedadPersona IS NOT NULL AND ROWNUM = 1")
public String getNombreLegalCliente(String cifNif);
Yes, since your class [ CuentaBancaria ] is annotated with #Embeddable, it needs to be embedded in the parent class in this case [ Contrato ] with #Embedded.
Then, harnessing Spring Data JPA query Lookup strategies, you can access property fields of your embedded class with ease or you could still go by the #Query() approach
Query lookup Strategy from Spring documentation
Sample demo code with your problem with a minimal implementation:
Entity-Class
--------------
#Entity
public class Contrato{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long contratoId;
#Column(name = "CIF_NIF")
private String cifNif;
#Column(name = "NOMBRE_SOCIEDAD_PERSONA")
private String nombreSociedadPersona;
//we call the embeddable class in this parent class with #Embedded annotation
#Embedded
private CuentaBancaria cuentaBancaria
}
Embeddable-Class
-----------------
#Embeddable
public class CuentaBancaria{
#Column(name = "TITULAR_CUENTA")
private String titularCuenta;
}
Now in your ContratoRepository class, we could have
#Repository
public interface ContratoRepository extends CrudRepository<Contrato, Long> {
Optional<Contrato> findByCuentaBancariaTitularCuenta(String cifNif);
}
which interprets to JPQL snippet:
c.cuentaBancaria.titularCuenta FROM Contrato c WHERE c.cifNif= ?1
NOTE: Notice the query method name matches the exact names in the classes and their corresponding fields, preceded by findBy

How can I add a tenant condition to Spring Data JPA Default and Dervied Queries

I have a Springboot Application with Repositories having Spring Data JPA Queries like findOne, findAll and also derived ones like findByID or findByName etc.
What I want to achieve is multitenancy. All entities have an "account_id" column which holds the tenant.
How do I add a filter like "account_id" to all the queries metioned above without using derived queries that contains those name slike findIdAndAccountid (which would be findone)
#Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
Category findByName(String name);
}
Here's the corresponding entity
#Entity
#Table(name = "unit")
#Data
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
I know most people use schemas as tenant separation but that's impossible for me. Is there a way (I didn't find one) to add such a tenant filter condition on those queries without writing NamedQueries or using DerivedQueries. An elegeant solution like annotate the repository or entity or maybe the queries that all queries should add the additional filter "account_id"?
You can add Where clause on your Entity classes (Didnt had time to test )
#Entity
#Table(name = "unit")
#Data
#Where(clause = "account_id= :account_id")
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
Update and Solution
1. Create a Filter & FilterDef on the entity like so
#FilterDef(name="accountFilter", parameters=#ParamDef( name="accountId", type="long" ) )
#Filters( {
#Filter(name="accountFilter", condition=":accountId = account_id")
} )
public class Category {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
enable filtering in the controller by autowiring entitymanager, writing a method to enable the filter and activate the filter in #ModelAttribute for each request
#RestController
#RequestMapping(path = "/categories",produces = MediaType.APPLICATION_JSON_VALUE )
public class CategoryController {
private final CategoryRepository repository;
#Autowired
private EntityManager entityManager;
CategoryController(CategoryRepository repository) {
this.repository = repository;
}
private void activateFilter() {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("accountFilter");
filter.setParameter("accountId", Long.valueOf(TenantContext.getCurrentTenant()));
}
#ModelAttribute
public void initFilter() {
activateFilter();
}
... your rest methods here
}

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.

Spring Data Neo4J #Indexed(unique = true) not working

I'm new to Neo4J and I have, probably an easy question.
There're NodeEntitys in my application, a property (name) is annotated with #Indexed(unique = true) to achieve the uniqueness like I do in JPA with #Column(unique = true).
My problem is, that when I persist an entity with a name that already exists in my graph, it works fine anyway.
But I expected some kind of exception here...?!
Here' s an overview over basic my code:
#NodeEntity
public abstract class BaseEntity implements Identifiable
{
#GraphId
private Long entityId;
...
}
public class Role extends BaseEntity
{
#Indexed(unique = true)
private String name;
...
}
public interface RoleRepository extends GraphRepository<Role>
{
Role findByName(String name);
}
#Service
public class RoleServiceImpl extends BaseEntityServiceImpl<Role> implements
{
private RoleRepository repository;
#Override
#Transactional
public T save(final T entity) {
return getRepository().save(entity);
}
}
And this is my test:
#Test
public void testNameUniqueIndex() {
final List<Role> roles = Lists.newLinkedList(service.findAll());
final String existingName = roles.get(0).getName();
Role newRole = new Role.Builder(existingName).build();
newRole = service.save(newRole);
}
That's the point where I expect something to go wrong!
How can I ensure the uniqueness of a property, without checking it for myself??
P.S.: I'm using neo4j 1.8.M07, spring-data-neo4j 2.1.0.BUILD-SNAPSHOT and Spring 3.1.2.RELEASE.
I walked into the same trap... as long as you create new entities, you will not see the exception - the last save()-action wins the battle.
Unfortunately, the DataIntegrityViolationException will be raised only in case of update an existing entity!
A detailed description of that behaviour can be found here:
http://static.springsource.org/spring-data/data-graph/snapshot-site/reference/html/#d5e1035
If you are using SDN 3.2.0+ use the failOnDuplicate attribute:
public class Role extends BaseEntity
{
#Indexed(unique = true, failOnDuplicate = true)
private String name;
...
}

Resources