Hibernate search not updating the index for a view, when view is updated on database - elasticsearch

In my project we are using Lucene and Hibernate Search/ORM 5.9.2. It works perfectly fine if a table is updated on database, same changes are reflected on ES indexes. But the index for a database view is not updated. Values are present under the database but not under the indexes.
What corresponding strategy/logic do I need to implement this through Hibernate search?
For any of the code referencing my implementation could be found under implemented code under the Java files.
Further, Lets assume I've 2 classes A & B and C is the view for A+B. And I'm storing C in my elastic indexes. Once A & B are updated under the Database, is it possible C will get updated? As I'm using Hibernate search+lucene annotations.
When a table is updated under database through Hibernate entities, Hibernate search is updating the same under the indexes. But this behavior is not happening for views as they are updated on the SQL Server side. Could this be achieved through Lucene/Hibernate search?

Hibernate Search is operating at the entity level. So it does not see the changes you make directly in your database.
For Hibernate Search to manage your index, you need to rely on entities.
There's really no option for it to do things differently as this is what it has been designed for.
If the changes are made to entities, you can implement your own listener layer to deal with C or make C an entity and implement a trigger dropping the update queries on it if your database can do it.
If the changes are made directly to the database, then you're out of luck with Hibernate Search. Or you have to trigger some service in your app, every time you make a change to the database to trigger the indexing.
BTW, do you need this view at the database level? Because if you only need it at the Elasticsearch level, you can make C a proper entity having a #OneToOne to A and B and index the content of A and B using #IndexedEmbedded and #ContainedIn on the other side. That would solve your issue with native Hibernate Search.
I think I would try something like:
#Entity
#Indexed
public class C {
#Id
#GeneratedValue
private Long id;
#OneToOne(mappedBy = "a")
// you might need a prefix if some fields are common between a and b
#IndexedEmbedded
private A a;
#OneToOne(mappedBy = "b")
// you might need a prefix if some fields are common between a and b
#IndexedEmbedded
private B b;
}
Then in A (same for B):
#Entity
#Indexed
public class A {
// the existing content of your A class
#OneToOne(cascade = CascadeType.ALL)
#ContainedIn
private C c;
public setC(C c) {
if ( c != null ) {
c.setA( this );
}
this.c = c;
}
}
Then when first creating A and B, you also need to create a C and inject it in A and B:
C c = new C();
a.setC( c );
b.setC( c );
em.persist( a );
em.persist( c );
Once this is done, whenever you update A or B, C should be reindexed thanks to the #ContainedIn annotation.

Related

How to specify ignore case in JPA #JoinColumn when I am using two tables entity

I have two entities. Account entity and B_Account.
In Account entity I am joining table like below.
#ManyToOne(fetch=eger)
#JoinColumn(name = "a_type")
private B_Account b_Account;
Now the issue is a_type attribute value in Account is "ganesh" and in B_Account is "GANESH".
This is the reason I am not getting the data when I used findBy(B_Account) using repository method.
How can I join the tables above so that it can ignore the small and uppercase?
You can try #JoinFormula That's a Hibernate proprietary annotation that doesn't exist in JPA.
#ManyToOne(fetch=eger)
#JoinFormula(name = "lower(b_account) = a_type")
private B_Account b_Account;
I cannot test it so please try it out.
Documentation: https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#associations-JoinFormula

Spring Data / Hibernate save entity with Postgres using Insert on Conflict Update Some fields

I have a domain object in Spring which I am saving using JpaRepository.save method and using Sequence generator from Postgres to generate id automatically.
#SequenceGenerator(initialValue = 1, name = "device_metric_gen", sequenceName = "device_metric_seq")
public class DeviceMetric extends BaseTimeModel {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "device_metric_gen")
#Column(nullable = false, updatable = false)
private Long id;
///// extra fields
My use-case requires to do an upsert instead of normal save operation (which I am aware will update if the id is present). I want to update an existing row if a combination of three columns (assume a composite unique) is present or else create a new row.
This is something similar to this:
INSERT INTO customers (name, email)
VALUES
(
'Microsoft',
'hotline#microsoft.com'
)
ON CONFLICT (name)
DO
UPDATE
SET email = EXCLUDED.email || ';' || customers.email;
One way of achieving the same in Spring-data that I can think of is:
Write a custom save operation in the service layer that
Does a get for the three-column and if a row is present
Set the same id in current object and do a repository.save
If no row present, do a normal repository.save
Problem with the above approach is that every insert now does a select and then save which makes two database calls whereas the same can be achieved by postgres insert on conflict feature with just one db call.
Any pointers on how to implement this in Spring Data?
One way is to write a native query insert into values (all fields here). The object in question has around 25 fields so I am looking for an another better way to achieve the same.
As #JBNizet mentioned, you answered your own question by suggesting reading for the data and then updating if found and inserting otherwise. Here's how you could do it using spring data and Optional.
Define a findByField1AndField2AndField3 method on your DeviceMetricRepository.
public interface DeviceMetricRepository extends JpaRepository<DeviceMetric, UUID> {
Optional<DeviceMetric> findByField1AndField2AndField3(String field1, String field2, String field3);
}
Use the repository in a service method.
#RequiredArgsConstructor
public class DeviceMetricService {
private final DeviceMetricRepository repo;
DeviceMetric save(String email, String phoneNumber) {
DeviceMetric deviceMetric = repo.findByField1AndField2AndField3("field1", "field", "field3")
.orElse(new DeviceMetric()); // create new object in a way that makes sense for you
deviceMetric.setEmail(email);
deviceMetric.setPhoneNumber(phoneNumber);
return repo.save(deviceMetric);
}
}
A word of advice on observability:
You mentioned that this is a high throughput use case in your system. Regardless of the approach taken, consider instrumenting timers around this save. This way you can measure the initial performance against any tunings you make in an objective way. Look at this an experiment and be prepared to pivot to other solutions as needed. If you are always reading these three columns together, ensure they are indexed. With these things in place, you may find that reading to determine update/insert is acceptable.
I would recommend using a named query to fetch a row based on your candidate keys. If a row is present, update it, otherwise create a new row. Both of these operations can be done using the save method.
#NamedQuery(name="getCustomerByNameAndEmail", query="select a from Customers a where a.name = :name and a.email = :email");
You can also use the #UniqueColumns() annotation on the entity to make sure that these columns always maintain uniqueness when grouped together.
Optional<Customers> customer = customerRepo.getCustomersByNameAndEmail(name, email);
Implement the above method in your repository. All it will do it call the query and pass the name and email as parameters. Make sure to return an Optional.empty() if there is no row present.
Customers c;
if (customer.isPresent()) {
c = customer.get();
c.setEmail("newemail#gmail.com");
c.setPhone("9420420420");
customerRepo.save(c);
} else {
c = new Customer(0, "name", "email", "5451515478");
customerRepo.save(c);
}
Pass the ID as 0 and JPA will insert a new row with the ID generated according to the sequence generator.
Although I never recommend using a number as an ID, if possible use a randomly generated UUID for the primary key, it will qurantee uniqueness and avoid any unexpected behaviour that may come with sequence generators.
With spring JPA it's pretty simple to implement this with clean java code.
Using Spring Data JPA's method T getOne(ID id), you're not querying the DB itself but you are using a reference to the DB object (proxy). Therefore when updating/saving the entity you are performing a one time operation.
To be able to modify the object Spring provides the #Transactional annotation which is a method level annotation that declares that the method starts a transaction and closes it only when the method itself ends its runtime.
You'd have to:
Start a jpa transaction
get the Db reference through getOne
modify the DB reference
save it on the database
close the transaction
Not having much visibility of your actual code I'm gonna abstract it as much as possible:
#Transactional
public void saveOrUpdate(DeviceMetric metric) {
DeviceMetric deviceMetric = metricRepository.getOne(metric.getId());
//modify it
deviceMetric.setName("Hello World!");
metricRepository.save(metric);
}
The tricky part is to not think the getOne as a SELECT from the DB. The database never gets called until the 'save' method.

Upgrade of spring data neo4j 3.x to 4.x Relationship Operations

In Spring data neo4j 3.x To create relation ship between two nodes and relationship contains set of properties earlier used to achieve this by apis
create :
n4jOperations.createRelationshipBetween(Object start, Object end, Class<R> relationshipEntityClass, String relationshipType, boolean allowDuplicates);
delete:
n4jOperations.deleteRelationshipBetween(Object start, Object end, String type);
get:
n4jOperations.getRelationshipBetween( from, to, relationshipClass, relationshipType );
But after migration i didnt't find above apis
as per docs says
#NodeEntity
public class Student {
private String name;
#Relationship(type = "ENROLLED")
private Set<Enrollment> enrollments;
}
By repo.save(Student);
//Relation creation was possible but new api's how can i achieve below use cases
1.How can avoid duplicate relation creation?
2.get Relation ship between two nodes ?
2.delete relation ship between two nodes ?
SDN 4 does not provide low-level graph-operations like setting nodes and relationships directly.
Relationships in the graph are modelled and manipulated using object references in your domain classes. They come in two flavours: implicit and explicit. Implicit relationships are described by simple references between two node entities, e.g. Customer and Address:
class Customer {
#Relationship(type="LIVES_AT")
Address address; // implied (:Customer)-[:LIVES_AT]->(:Address)
...
}
Explicit relationships are modelled using RelationshipEntity objects, and are allowed to have properties (but don't have to). They are still accessed as references in your domain model.
class Person {
#Relationship(type="RATED")
List<Rating> ratings
}
class Movie {
}
#RelationshipEntity(type="RATED")
class Rating {
#StartNode Person person;
#EndNode Movie movie;
int stars;
}
Note: If you don't need properties on a particular relationship, you don't need to use a RelationshipEntity.
To answer your specific questions:
1) SDN 4.0 doesn't create duplicate relationships. No matter how many times you persist a specific object reference, it will represented by only one relationship in the graph.
2) Hopefully that is clear now!
3) Setting an object reference to null and saving the parent object will remove the relationship. Or, if the reference is part of Collection, remove it from the collection. You must ensure that the object references are removed from both sides. For example if A holds a reference to B and B holds a reference to A, you must remove A's reference to B as well as B's reference to A.

JDBC: select entities with Many to one relation

I have the two entity classes with bi-directional Many-to-one relation.
class A {
#Column(name="ID")
long Id;
}
class B {
#ManyToOne
#JoinColumn(name="A_ID")
A a;
}
The entities are well-coded with additional data fields and getters and setters. And now I want to construct a query string to fetch data from table B, where B's "A_ID" column is equal to A's "ID".
I tried something like this:
"select b.data1, b.data2 from B b, A a WHERE b.a.Id=a.Id"
But it does not work. What is the correct way to construct such a query? And if A and B are in a uni directional relation, would there be any difference?
Thanks in advance.
You don't need to join the tables, the whole idea behind #ManyToOne and #OneToMany is to do away with the need for most joins.
I refer you to a tutorial on JPA, like http://en.wikibooks.org/wiki/Java_Persistence/ManyToOne and http://en.wikibooks.org/wiki/Java_Persistence/OneToMany.
Now, without seeing your actual db definitions it's a bit difficult to guess the actual structure of your program and database, but it should be something like this:
class A {
#Id
#Column(name="ID")
long Id;
#OneToMany(mappedBy="a")
List<B> bees;
}
class B {
#ManyToOne
#JoinColumn(name="A_ID") // Note that A_ID is a column in the B table!!!
A a;
}
With the example above you could just select any list of B's you need, and JPA will automatically fetch the associated A for each found B. You don't need to do anything to be able to access it, b.a.Id will just work.
As we also have the OneToMany relationship, every A can have multiple B's associated with it. So, for any select that fetches a set of A's, each returned A's bees field will give access to the proper list of B objects, without the need to pull the B able into the query.

Sum with one to many Spring Data JPA

I am trying to get a sum with a one to many relationship, illustrated by the following relationship (only parent shown):
#Entity
#Table(name = "Parent")
public class Parent implements Serializable {
private static final long serialVersionUID = -7348332185233715983L;
#Id
#Basic(optional = false)
#Column(name = "PARENT_ID")
private Long parentId;
#OneToMany
#JoinColumn(name="CHILDREN", referencedColumnName="PARENT_ID")
private List<Child> children;
#Formula("(select sum(select height from children))")
private BicDecimal totalHeight
}
It is pretty straight forward with no restrictions and even with static restrictions. I am having trouble when the children list is restricted dynamically though.
In my case, I am using spring data and jpa. I am using specifications to restrict the children and am getting the appropriate list of children, but obviously the sum is still for unrestricted children because there is no where clause in the #Formula tag.
I do not want to iterate over the list in java for performance reasons and because the results are paginated. Also, the sum is not of the paginated results, but of all results.
I am new to Spring Data/JPA. Historically, I could build this query dynamically or use hibernate criteria. I am OK running a completley separate query to make this calculation. it is not required that I use the #Formula annotation as there is only 1 aggregation per call. In a hibernate framework, I could just state the select clause as "sum(field)" and build the criteria. In the Spring Data/JPA framework, I can build the specifications fine which covers the criteria, but I have no idea how to manipulate the select part of the query since it seems tied so tightly to the entity.
Using the #Query annotation on the repository works as its own query if I know which fields I need to restrict on, but often the fields are null and need to be ignored for the query. There are 8 possible fields, leaving me with 256 possible combinations (2^8). That is too many methods for this in the repository.
Any ideas outside of switching frameworks?
Posting an answer to this old question since I had a somewhat similar problem recently.
I decided to go with a custom repository with a method that does the aggregation based on any Specification passed into it. Specifications can be combined to compose dynamic criteria (see org.springframework.data.jpa.domain.Specifications)
So my repository to above Child height problem would look like below:
package something
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
#Repository
public class ChildHeightRepository {
#PersistenceContext
EntityManager entityManager;
public Long getTotalHeight(Specification<Child> spec) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(Long.class);
Root root = query.from(Child.class);
return ((Long) entityManager.createQuery(
query.where(spec.toPredicate(root, query, cb))
.select(cb.sum(root.get("height")))).getSingleResult());
}
}
Have you tried in JPQL
select sum(d.height) from Parent a join a.children d
If you dont want to ignore nulls
select sum(d.height) from Parent a left join a.children d
I think other question you have is how to filter depending on the properties . I mean if you need to have a where statement with several combinations.
Why you don't try to use a List and adding to the list all the predicates you want to apply depending on the combinations you want to have. Example
Create query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Double.class);
Root<Parent> root = cq.from(Parent.class)
Join<Parent, Child> join = cq.join(("children"));
cq.select(cb.<Double>sum(join.get("height")));
Create the list of predicates
List<Predicates> listOfpredicates = new ArrayList<Predicates>();
if(property1 != null && !"".equals(property1){
PatameterExpression<String> p = cb.parameter(String.class, "valueOfproperty1")
listOfpredicates.add(cb.equal(join.get("property1"),p);
}
Then add to the CriteriaQuery
if(listOfPredicates.size() == 1)
cq.where(listOfPredicates.get(0))
else
cq.where(cb.and(listOfPredicates.toArray(new Predicate[0])));
Finally execute the query.
TypedQuery<Double> q = em.createQuery(cq);
q.getResultList();
This will create dynamically your query with any combination.
6 years late but it still took me a while to get it to work, here is how I would do it for your mapping:
#Formula("(select sum(children.height) from children_table children inner join Parent p on children.parent_id=p.id where children.parent_id=parent_id)")
private BicDecimal totalHeight
Stuff you need to take care of:
add () to the beginning and end of your formula otherwise the syntax of the sql wont be translated correctly.
the formula query is a native SQL query to my understanding and not a JPQL some one might want to correct me on this?.
properties are those of the tables and not what you name your properties in Java so the column names and table names have to actually be the table names.

Resources