I am getting started with Hibernate Search/Lucene using Spring Boot and Spring Data, but I am having an issue with the index not getting updated (Checked with Luke tool).
I have 3 classes in my domain. This is Datasheet, my root entity:
#Entity
#Indexed
public class Datasheet
{
#Id
#GeneratedValue()
private long m_id;
#Field(name="name")
private String m_name;
#Field(name="description")
private String m_description;
#IndexedEmbedded(prefix = "documents.")
#OneToMany(cascade = CascadeType.REMOVE)
private Set<DatasheetDocument> m_documents;
}
Then DatasheetDocument:
#Entity
public class DatasheetDocument
{
#Id
#GeneratedValue()
private long m_id;
private String m_originalFileName;
#Field(name="componentName")
private String m_componentName;
#IndexedEmbedded(prefix = "manufacturer.")
#ManyToOne
private Manufacturer m_manufacturer;
}
And finally Manufacturer:
#Entity
public class Manufacturer
{
#Id
#GeneratedValue()
private long m_id;
#Field(name="name", analyze = Analyze.NO)
private String m_name;
private String m_website;
}
When I explicitly call startAndWait() on the indexer (org.hibernate.search.MassIndexer), then everything is as expected in the index. It contains the fields name, description, documents.componentName and documents.manufacturer.name.
However, when I now do updates through my #RestController classes that call into Spring Data CrudRepository classes, the index only changes when changing a direct field of Datasheet (E.g. name or description). Changing something to the DatasheetDocument instances does not update the index. Any idea why this might be?
Note that I have tried to add backreferences to the parent. For DatasheetDocument:
#ManyToOne
#ContainedIn
private Datasheet m_datasheet;
And for Manufacturer:
#ManyToMany
#ContainedIn
private Set<DatasheetDocument> m_datasheetDocuments;
But that does not help.
I am using Spring boot 1.0.1 which includes Hibernate 4.3.1. I added Hibernate Search 4.5.1. I see that Lucense 3.6.2 gets added transitively as well.
You need the back references for sure. Without them and in particular without #ContainedIn there is no way for Search to know that it has to update the Datasheet index when the DatasheetDocument instance changes.
Have you added mappedBy to the one to many side?
#OneToMany(cascade = CascadeType.REMOVE, mappedBy="m_datasheet")
private Set<DatasheetDocument> m_documents;
Also, how to you update DatasheetDocument? Can you show the code? Either way, you will need to make the associations bi-directional to start with.
FullTextSession fullTextSession = Search.getFullTextSession(session);
fullTextSession.openSession()
Object customer = fullTextSession.load( Datasheet.class, datasheetDocument.getDatasheet.getId() );
fullTextSession.index(customer);
fullTextSession.flushIndex();
Related
I'm creating a delete api endpoint for my spring boot application. I tried using the delete() and deleteById() methods provided by the JpaRepository. However, whenever I try to delete a concert, using the ConcertEntity or the concertId, the venue entry associated is deleted from the Venues table. How do I prevent deleting reference entities/tables using the JpaRepository?
My current solution is to set the venue to null before deleting the concert entity. My concertRepositroy extends to JpaRepository.
Current Solution in Service Impl
public void deleteConcert(ConcertEntity e){
e.setVenue(null);
this.concertRepository.delete(e);
}
Concert Entity
#Entity
#Table(name = "CONCERTS")
public class ConcertEntity{
#Id
private UUID concertId;
#Column(name = "ARTIST")
String artist;
#Column(name = "VENUE_ID")
VenueEntity venue;
/*Getters && Setters here...*/
}
Use the proper annotation to define the relationship (#ManyToOne or #OneToOne)
#ManyToOne(optional = true)
#JoinColumn(name = "VENUE_ID")
private VenueEntity venue;
That should not trigger any cascade deletion by default, but you can add the cascade parameter to the #ManyToOne or #OneToOne annotation if you want to customize the behavior.
This question already has an answer here:
How to use OneToOne with Hibernate Formula
(1 answer)
Closed 7 years ago.
I am having a problem concerning hibernate annotation mapping configurations. Imagine the following two classes on a social media platform:
#Entity
class User {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany
private List<Post> posts; //lazy-loaded, because of big amounts
}
#Entity
class Post {
#Id
#GeneratedValue
private Long id;
//large amounts of data
private String text;
private Date date;
#ManyToOne
private User author;
}
The most interesting information in my Use Case is the most recent Post every User wrote. I could read in the Users and append a custom query just like this:
SELECT mx.userId,text,mx.date FROM
(SELECT user.id AS userId,max(date) AS date
FROM post,user
WHERE post.author=user.id
GROUP BY user.id) mx,
Post p
WHERE p.author=mx.userId
AND p.date=mx.date;
My Question now is, how I can embed the latest post in its mapped user as a field.
New User:
#Entity
class User {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany
private List<Post> posts;
//#Filter?
//#Query?
private Post latestPost;
}
I don't want to Join the data from my 'latest-post'-aggregation with my User-Objects in Java. This does not taste right. So I am searching for a solution to embed this aggregated field into my base-entity. Where can I specify a custom retrieval of User-Objects in Hibernate to add such an aggregated field?
You could imagine a similar problem with e.g. embedded sums of trade-amounts of a specific customer in a CRM.
I hope, i could illustrate my thoughts and problem. Thanks in advance!
JPA itself does not provide such an annotation.
But the Hibernate specific Where annotation does exactly what you want. The only disadvantage - it needs a collection to work, which you can workaround with a specific getter:
#OneToMany(mappedBy = "author")
#Where("date = (SELECT max(post.date) FROM post GROUP BY post.author)")
private Collection<Post> latestPost;
public Post getLatestPost() {
return latestPost.isEmpty() ? null : latestPost.iterator().next(0);
}
I have a bean structure as shown below. The problem that I am facing is while trying to persist XBean, I am able to save all the data (i.e. xName, pBean, qBean, rBean, kBeans are all visible in storage) but there is no entry for Y_BEAN.
I am pretty much new with JPA annotations so not really sure if what I have done is correct. The idea is to have multiple entries of XBean (i.e. as List) with one instance of YBean
XBean also will hold an instance of YBean as its parent so when I retrieve XBean I should get all the data. Is there something wrong with #ManyToOne annotation?
#Entity
#Table (name = "X_BEAN")
public class XBean implements XInterface {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private String xName;
#OneToOne(cascade=CascadeType.ALL)
private PBean pBean;
#ManyToOne
#JoinColumn(name="y_id")
private YBean yBean;
#OneToOne(cascade=CascadeType.ALL)
private qBean qBean;
#OneToOne(cascade=CascadeType.ALL)
private RBean rBean;
#OneToMany (mappedBy="xBean", cascade=CascadeType.ALL)
private List<KBean> kBeans;
// getter setters for each are below ...
}
and structure of YBean is like below
#Entity
#Table (name = "Y_BEAN")
public class YBean implements XInterface {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#OneToOne(cascade=CascadeType.ALL)
private ZBean zName;
#OneToOne(cascade=CascadeType.ALL)
private PBean pBean;
#OneToOne(cascade=CascadeType.ALL)
private RBean rBean;
#OneToMany (mappedBy="yBean", cascade=CascadeType.ALL)
private List<XBean> xBeans;
// getter setter for each are below ...
}
I am using Google App Engine's storage
You need cascade=CascadeType.PERSIST on your ManyToOne, to tell Hibernate to persist the YBean when it persists the XBean.
You should also think about whether you want the cascade attribute on the inverse OneToMany. With CascadeType.ALL, if you were to delete an instance of YBean, Hibernate will delete all associated XBeans (of which there may be zero, one, or many), because CascadeType.ALL means "apply persistence operations, including deletion, to any other entities accessible via this property or collection". If you didn't have CascadeType.ALL and you deleted a YBean that was referred to by one or more XBeans, then those XBeans would now referenced a non-existent YBean ID, so you'd probably need to do some cleanup in that case.
Both options are irrelevant if your business logic never deletes a YBean until it is not referred to by any XBeans, but if your business logic doesn't prevent the case, then you should cascade or not based on whether you want to get rid of the XBeans or whether you want to clean them up (but not delete them) to no longer refer to the YBean that's being deleted.
#ManyToOne
#JoinColumn(name="y_id")
private YBean yBean;
what is the column y_id ?
what is the definition of it?
you can try removing #JoinColumn(name="y_id") and let JPA handle it.
and also add fetch = FetchType.EAGER like this.
#OneToMany (mappedBy="yBean", cascade=CascadeType.ALL, fetch = FetchType.EAGER)
private List<XBean> xBeans;
I'm currently working on a project to persist data with JPA 2.1 and to search entities using hibernate search 4.5.0.final.
After mapping classes and indexing, the searching works fine.
However, when I changed the value description of classB from "someStr" to "anotherStr". The database was updated accordingly, but when I checked the index using Luke, classA.classB.description in the index wasn't updated, and the data cannot be searchable by keyword "anotherStr", but can be searchable by keyword "someStr".
After I reindex the whole database, it's updated finally.
According to Hibernate search website,
The short answer is that indexing is automatic: Hibernate Search will transparently index every entity persisted, updated or removed through Hibernate ORM. Its mission is to keep the index and your database in sync, allowing you to forget about this problem.
But it's not working in my case. I'm not sure if I missed some details or I need to handle it myself for this kind of issues.
I also tried to add annotation #Indexed on classB as suggested by this one, but it's still not solving my problem.
As far as I know, the solution would be to reindex the database periodically. But reindexing would disable the search functionality and that's not an option in most of the cases.
Could anyone give some suggestions? Thanks.
I have a class which embedded some other classes by using #IndexedEmbedded annotation. Here is a simplified version of my class mapping.
Class A
#Entity(name = "classA")
#Indexed
public class classA extends Model {
private int id;
private String name;
private ClassB place;
...
some constructors
...
#Id
#GeneratedValue
#DocumentId
public int getId() {
return id;
}
#Column(name = "name")
#Field(analyze = Analyze.NO, store = Store.YES) // only used for sorting
public String getName() {
return name;
}
#IndexedEmbedded
#ManyToOne
#JoinColumn(name = "place_id")
public ClassB getPlace() {
return place;
}
...
}
Class B
#Entity(name = "classB")
public class classB extends Model {
private int id;
private String description;
...
some constructors
...
#Id
#GeneratedValue
public int getId() {
return id;
}
#Fields({
#Field,
#Field(name = "description_sort", analyze = Analyze.NO, store = Store.YES)
})
#ContainedIn
#Column(name = "description")
public String getDescription() {
return description;
}
...
}
And the indexing methods is as follows:
fullTextEntityManager.createIndexer()
.purgeAllOnStart(true)
.optimizeAfterPurge(true)
.optimizeOnFinish(true)
.batchSizeToLoadObjects(25)
.threadsToLoadObjects(8)
.startAndWait();
You placed ContainedIn annotation incorrectly. According the Hibernate Search documentation:
Be careful. Because the data is denormalized in the Lucene index when using the #IndexedEmbedded technique, Hibernate Search needs to be aware of any change in the Place object and any change in the Address object to keep the index up to date. To make sure the Place Lucene document is updated when it's Address changes, you need to mark the other side of the bidirectional relationship with #ContainedIn.
In your example, you need to:
Make the relationship between classes bidirectional
Mark the relationship in ClassB as ContainedIn
In your case:
ClassB {
private Set<ClassA> linkedObjects;
....
#OneToMany(mappedBy="place")
#ContainedIn
public Set<ClassA> getLinkedObjects() {
return linkedObjects;
}
....
}
I had a similar problem but already with correct annotations. In my case, I have added forced flush both to the database and to index and refreshed it afterward:
myEm.flush();
Search.getFullTextEntityManager(myEm).flushToIndexes();
myEm.refresh(updatedObject);
hmmm, add #ContainedIn doesn't work for me.
I put the sample project here
https://github.com/yhjhoo/princeSSH
Update department object is not able to update person index
I have the following mapping:
#Entity
public class Satellite implements Serializable, Comparable<Satellite> {
#NotNull #Id
private long id;
.....
#OrderColumn
#OneToMany(mappedBy = "satellite", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<DataModel> dataModel;
}
and a child entity:
#Entity #IdClass(value=DataModelPK.class)
public class DataModel implements Serializable, Comparable<DataModel> {
private static final long serialVersionUID = -3416403014857250990L;
#Id
private int orbit; // related to reference orbit file
private int dataPerOrbit; // in Gbit
#ManyToOne #Id
private Satellite satellite;
}
originally, DataModel was an embeddable entity, but for a better control over the primary key and the underlying structure of the db, I switched to a more traditional model.
The point is, during the loading of the entity now it generate a stack overflow!! I think there is some cyclic loading between those two entities and it got stuck!
I'm thinking to revert everything back to what it was, but I wish to understand why it gives me this error.
You have #IdClass for DataModel specified to be DataModelPK.class but your #Id annotation is on an int field.
This is a problem, it may be causing you stackoverflow but I am not certain.
Update I now see the second #Id annotation so I stand corrected, I will investigate furtuer.