Spring Data Elasticsearch's ElasticsearchTemplate vs ElasticsearchRepository - elasticsearch

I am in reference to Spring Data Elasticsearch's
org.springframework.data.elasticsearch.repository.ElasticsearchRepository
org.springframework.data.elasticsearch.core.ElasticsearchTemplate
It seems they are two different APIs that achieve the same goal but I am not sure what the differences are between those two types and more importantly when to use which.
Can someone please provide advice and guidance?

ElasticsearchRepository is intended to be used as a repository for your domain classes, as it's typed. It extends Spring interfaces for repositories so it can used as one of them. You'll feel very comfortable with it if you are used to Spring repositories.
All you need to start indexing your objects to Elasticsearch is to add the #Document annotation to them and create a Repository interface extending ElasticsearchRepository.
The indexable class:
#Document(
indexName = "customers",
type = "customer",
shards = 1,
replicas = 0,
refreshInterval = "-1"
)
public class Customer {
#Id
private Long id;
private String name;
public Customer() {
}
public Customer(String name) {
this.name = name;
}
//Getters and setters omited
}
The repostitory:
public interface CustomerRepository
extends ElasticsearchRepository<Customer, Long>{
}
With this you can, out of the box, make CRUD operations, index, search and other common operations.
ElasticsearchTemplate, by other hand, is an elasticsearch client for working with your indexes, and it's not typed or related to your domain classes. It's more powerful since you can do many tasks not available to the repository implementation, like deleting an index or making aggregated searchs.

Related

Cyclic dependency with JPA/Hibernate and Jackson [duplicate]

This question already has answers here:
Infinite Recursion with Jackson JSON and Hibernate JPA issue
(29 answers)
Closed 3 months ago.
I have a Spring Boot application using JPA/Hibernate in its persistence layer. The application has read-only access to a database and basically has three entities Article, Category, and Field, which have the following relationships.
Article (*) -> (1) Category (*) <-> (1) Field
That is, an Article has a Category, and a Category always belongs to a single Field, however, multiple Category instances can belong to the same Field.
The application provides two REST endpoints, which give a single Article and a single Field by their IDs, respectively. Of course, this cannot work when using Jackson for serialization due to the cyclic dependency Category <-> Field.
What I want is when I retrieve an Article, it should give me its Category including the category's Field, but not all the other Category instances that belong to the this same Field. On the other hand, when I retrieve a Field, it should give me the Field including all Category instances that belong to this Field.
How can I achieve this?
Edit:
I basically have a similar question as Jackson infinite loops many-to-one one-to-many relation
You can use interface-based projections, to only retrieve needed properties, since Spring Data allows modeling dedicated return types, to more selectively retrieve partial views of the managed aggregates.
Let's assume the entities are declared as shown below. For simplicity, only the id attribute is defined alongside association-mapping attributes.
#Entity
public class Article {
#Id
private Long id;
#ManyToOne
private Category category;
}
#Entity
public class Category {
#Id
private Long id;
#OneToMany
private Set<Article> articles;
#ManyToOne
private Field field;
}
#Entity
public class Field {
#Id
private Long id;
#OneToMany
private Set<Category> categories;
}
For the first endpoint where the Article is fetched by id, the projections should be declared as follows:
public interface ArticleDto {
Long getId();
CategoryDto1 getCategory();
interface CategoryDto1 {
Long getId();
FieldDto1 getField();
}
interface FieldDto1 {
Long getId();
}
}
The important bit here is that the properties defined here exactly
match properties in the aggregate root.
Then, the additional query method should be defined in ArticleRepository:
interface ArticleRepository extends JpaRepository<Article, Long> {
Optional<ArticleDto> findDtoById(Long id);
}
The query execution engine creates proxy instances of that interface
at runtime for each element returned and forwards calls to the exposed
methods to the target object.
Declare additional projections to retrieve properties needed for the second case:
public interface FieldDto2 {
Long getId();
Set<CategoryDto2> getCategories();
interface CategoryDto2 {
Long getId();
}
}
Lastly, define the following query method in FieldRepository:
interface FieldRepository extends JpaRepository<Field, Long> {
Optional<FieldDto2> findDtoById(Long id);
}
With this approach, the infinite recursion exception would never appear, as long as projections don't contain attributes causing recursion.

Designing one-to-one and one-to-many relationships in Spring Data R2DBC

I am exploring possible ideas when it comes to designing the one-to-one and one-to-many relationships while using Spring Data R2DBC.
As Spring Data R2DBC still do not support relationships natively there is still a need to handle those on our own (unlike Spring Data JDBC).
What I would imagine that when it comes to one-to-one mapping, the implementation could look like this:
#Table("account")
public class Account {
#Id
private Long id;
#Transient // one-to-one
private Address address;
}
#Table("address")
public class Address {
#Id
private Integer id;
}
while the database schema would be defined as follows:
--address
CREATE TABLE address
(
id SERIAL PRIMARY KEY
)
--account
CREATE TABLE account
(
id SERIAL PRIMARY KEY,
address_id INTEGER REFERENCES address(id)
)
As the Account object is my aggregate root what I would imagine is that I am supposed to load the Address object with it following the advice of Jens Schaduer:
An aggregate is a cluster of objects that form a unit, which should
always be consistent. Also, it should always get persisted (and
loaded) together.
source: Spring Data JDBC, References, and Aggregates
This leads me to thinking that in case of one-to-one relationships like this one I in fact should have my Account entity defined like this:
#Table("account")
public class Account {
#Id
private Long id;
#Transient // one-to-one
private Address address;
#Column("address_id")
private Integer addressId;
}
and later on to recreate the full Account aggregate entity with an Address I would write something like:
#Service
public class AccountServiceImpl implements AccountService {
private final AccountRepository accountRepository;
private final AddressRepository addressRepository;
public AccountServiceImpl(AccountRepository accountRepository,
AddressRepository addressRepository) {
this.accountRepository = accountRepository;
this.addressRepository = addressRepository;
}
#Override
public Mono<Account> loadAccount(Integer id) {
return accountRepository.getAccountById(id)
.flatMap(account ->
Mono.just(account)
.zipWith(addressRepository.getAddressByAccountId(account.getAddressId()))
.map(result -> {
result.getT1().setAddress(result.getT2());
return result.getT1();
})
);
}
}
If that is not the case, how else should I handle one-to-one relationships while using Spring Data R2DBC?
I think your approach is reasonable. There are just a couple of nitpicks:
Do you need the flatMap -> Mono.just ? Can't you just use map directly?
I wouldn't consider this a service, but a repository (it's just not implemented by Spring Data directly.
You might be able to that code in a after load callback.

Sort feature in association endpoint in Spring Data Rest

I have the following two resources, and their association;
#Table(name = "Item")
#Data
#Entity
public class Item {
#ManyToOne
#JoinColumn(name = "fk_wrapper")
private Wrapper wrapper;
#Id
#GeneratedValue
private String id;
private Integer someValue;
}
and;
#Table(name = "Wrapper")
#Data
#Entity
public class Wrapper {
#Id
#GeneratedValue
private String id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_wrapper")
private List<Item> items;
private String someField;
}
Then, first, I create a Wrapper;
POST http://localhost:8080/wrappers/
{
"someField": "asd"
}
http://localhost:8080/wrappers/1 created, then I create two Item's, linked to this Wrapper;
POST http://localhost:8080/items/
{
"someValue": "5",
"wrapper": "http://localhost:8080/wrappers/1"
}
&
POST http://localhost:8080/items/
{
"someValue": "7",
"wrapper": "http://localhost:8080/wrappers/1"
}
After all this, when I call the endpoint http://localhost:8080/wrappers/1/items, I get the list of these two items, as expected, but what the trouble is that, I cannot seem to have a sorting feature on this endpoint. I seem to be able to sort in http://localhost:8080/items endpoint, but while fetching with association, there doesn't seem to be a sorting feature. Is this lack of sorting is intended, or am I lacking some configuration?
P.S. when I create a custom search method, for example;
#RepositoryRestResource
public interface ItemRepository extends JpaRepository<Item, String> {
List<Item> findByWrapper_Id(#Param("id") String id, Sort sort);
}
Then I can use the sorting with http://localhost:8080/items/search/findByWrapper_Id endpoint, but too ugly imo, considering there is already an auto-generated endpoint.
Spring Data Rest doesn't support sorting on the associations.
You seem to have already found the best way to do what you need, according to the Spring Data Rest team - create a query for fetching the data you need. That will indeed support both pagination and sorting.
The reason why it's not supported has to do with the time when the queries are made to fetch the main resource (before the association endpoints are built) and the facts that the association endpoint makes use of the the loaded entity associations directly and that for supporting sort, a new query would need to be made anyway.
More detailed information can be found here:
https://jira.spring.io/browse/DATAREST-725?focusedCommentId=122244&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-122244
Cheers!

Spring Data Rest - sort by nested property

I have a database service using Spring Boot 1.5.1 and Spring Data Rest. I am storing my entities in a MySQL database, and accessing them over REST using Spring's PagingAndSortingRepository. I found this which states that sorting by nested parameters is supported, but I cannot find a way to sort by nested fields.
I have these classes:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
#ManyToOne
protected Address address;
#ManyToOne(targetEntity = Name.class, cascade = {
CascadeType.ALL
})
#JoinColumn(name = "NAME_PERSON_ID")
protected Name name;
#Id
protected Long id;
// Setter, getters, etc.
}
#Entity(name = "Name")
#Table(name = "NAME")
public class Name{
protected String firstName;
protected String lastName;
#Id
protected Long id;
// Setter, getters, etc.
}
For example, when using the method:
Page<Person> findByAddress_Id(#Param("id") String id, Pageable pageable);
And calling the URI http://localhost:8080/people/search/findByAddress_Id?id=1&sort=name_lastName,desc, the sort parameter is completely ignored by Spring.
The parameters sort=name.lastName and sort=nameLastName did not work either.
Am I forming the Rest request wrong, or missing some configuration?
Thank you!
The workaround I found is to create an extra read-only property for sorting purposes only. Building on the example above:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
// read only, for sorting purposes only
// #JsonIgnore // we can hide it from the clients, if needed
#RestResource(exported=false) // read only so we can map 2 fields to the same database column
#ManyToOne
#JoinColumn(name = "address_id", insertable = false, updatable = false)
private Address address;
// We still want the linkable association created to work as before so we manually override the relation and path
#RestResource(exported=true, rel="address", path="address")
#ManyToOne
private Address addressLink;
...
}
The drawback for the proposed workaround is that we now have to explicitly duplicate all the properties for which we want to support nested sorting.
LATER EDIT: another drawback is that we cannot hide the embedded property from the clients. In my original answer, I was suggesting we can add #JsonIgnore, but apparently that breaks the sort.
I debugged through that and it looks like the issue that Alan mentioned.
I found workaround that could help:
Create own controller, inject your repo and optionally projection factory (if you need projections). Implement get method to delegate call to your repository
#RestController
#RequestMapping("/people")
public class PeopleController {
#Autowired
PersonRepository repository;
//#Autowired
//PagedResourcesAssembler<MyDTO> resourceAssembler;
#GetMapping("/by-address/{addressId}")
public Page<Person> getByAddress(#PathVariable("addressId") Long addressId, Pageable page) {
// spring doesn't spoil your sort here ...
Page<Person> page = repository.findByAddress_Id(addressId, page)
// optionally, apply projection
// to return DTO/specifically loaded Entity objects ...
// return type would be then PagedResources<Resource<MyDTO>>
// return resourceAssembler.toResource(page.map(...))
return page;
}
}
This works for me with 2.6.8.RELEASE; the issue seems to be in all versions.
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At project mark association as #SortByLinkableAssociation:
#ManyToOne
#SortByLinkableAssociation
private Name name;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.
Please see https://stackoverflow.com/a/66135148/6673169 for possible workaround/hack, when we wanted sorting by linked entity.

Multiple Repositories for the Same Entity in Spring Data Rest

Is it possible to publish two different repositories for the same JPA entity with Spring Data Rest?
I gave the two repositories different paths and rel-names, but only one of the two is available as REST endpoint.
The point why I'm having two repositories is, that one of them is an excerpt, showing only the basic fields of an entity.
The terrible part is not only that you can only have 1 spring data rest repository (#RepositoryRestResource) per Entity but also that if you have a regular JPA #Repository (like CrudRepository or PagingAndSorting) it will also interact with the spring data rest one (as the key in the map is the Entity itself).
Lost quite a few hours debugging random load of one or the other. I guess that if this is a hard limitation of spring data rest at least an Exception could be thrown if the key of the map is already there when trying to override the value.
The answer seems to be: There is only one repository possible per entity.
I ended up using the #Subselect to create a second immutable entity and bound that to the second JpaRepsotory and setting it to #RestResource(exported = false), that also encourages a separation of concerns.
Employee Example
#Entity
#Table(name = "employee")
public class Employee {
#Id
Long id
String name
...
}
#RestResource
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
}
#Entity
#Immutable
#Subselect(value = 'select id, name, salary from employee')
public class VEmployeeSummary {
#Id
Long id
...
}
#RestResource(exported = false)
public interface VEmployeeRepository extends JpaRepository<VEmployeeSummary, Long> {
}
Context
Two packages in the monolithic application had different requirements. One needed to expose the entities for the UI in a PagingAndSortingRepository including CRUD functions. The other was for an aggregating backend report component without paging but with sorting.
I know I could have filtered the results from the PagingAndSorting Repository after requesting Pageable.unpaged() but I just wanted a Basic JPA repository which returned List for some filters.
So, this does not directly answer the question, but may help solve the underlying issue.
You can only have one repository per entity... however, you can have multiple entities per table; thus, having multiple repositories per table.
In a bit of code I wrote, I had to create two entities... one with an auto-generated id and another with a preset id, but both pointing to the same table:
#Entity
#Table("line_item")
public class LineItemWithAutoId {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
...
}
#Entity
#Table("line_item")
public class LineItemWithPredefinedId {
#Id
private String id;
...
}
Then, I had a repository for each:
public interface LineItemWithoutId extends Repository<LineItemWithAutoId,String> {
...
}
public interface LineItemWithId extends Repository<LineItemWithPredefinedId,String> {
...
}
For the posted issue, you could have two entities. One would be the full entity, with getters and setters for everything. The other, would be the entity, where there are setters for everything, but only getters for the fields you want to make public. Does this make sense?

Resources