index not updating after external entity changes - java

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

Related

Spring Data JDBC Lookup Tables Cascading Deletes

I have a structure similar to below. I need to only retrieve the lookup data but not have it deleted/saved/updated when a parent/child is deleted/saved/updated. The data in the lookup table is static. I'm using Sprint Data JDBC with Java 11 with Postgres for the database. I understand this is a contrived example but I am not allowed to post the actual code.
#Data
#Table("parent")
public class ParentDTO {
#Id
private long parentId;
private Date createdAt;
#MappedCollection(idColumn="parent_id", keyColumn="parent_id")
private Set<ChildDTO> children;
String name;
}
#Data
#Table("child")
public class ChildDTO {
#Id
private long childId;
private Date createdAt;
private long parentId;
String name;
#MappedCollection(idColumn="lookup_id", keyColumn="lookup_id")
private LookupDTO lookupDTO;
}
#Data
#Table("lookup")
public class LookupDTO {
#Id
private long lookupId;
String name;
private Date createdAt;
}
Cascade is not an optional feature in JPA/Spring-Data that can be entirely removed. The entity relations will have a default cascade option, since it is a reflection of the underlying database schema.
In your example, if the ParentDTO is deleted, how can the ChildDTO that has a foreign key (ChildDTO.parentId) exist in the database? The Spring-Data JPA just reflects this design.
If you want to make sure nobody deletes the ParentDTO or other entities, then you can do several things:
Here are some ideas:
Change the Database access to these tables to be read-only for your JDBC user id. If they are static, this is how it should be.
Make the Repository read-only. Here is a good discussion on that - Creating a read-only repository with SpringData
Force your entity to be read-only. The top answer here is the best - How to make an Entity read-only? (Implement an EntityListener, or just remove all setter methods from your entities)

Java Spring Hibernate JPA PostgreSQL Avoid saving duplicate rows / records

I'm very green when it comes to databases. I feel like this is probably a pretty common database problem, I can't seem to find the correct search terms to find my answer.
My issue is "duplicate" rows in a table. I'm trying to save restaurant food menus in a database, and for the most part its working alright. I have a object called RestaurantWeek, which contains a list of RestaurantDay objects and each day contains a list of RestaurantCourse objects. They get saved as such in the database: image. "weeks_days" and "days_courses" tables are the link between the "weeks", "days" and "courses" tables.
Now the problem comes when many days can have the same "course". Almost every single day has "Salad" as a course, so what ends up happening is I have 12 identical rows in my "courses" table, the only exception being the id column: image. So now the question is, how can I tell JPA or Hibernate to use the existing "Salad" row in my "courses" table instead of inserting a new one every time? Is it possible to do this by adding some specific annotation to my objects or their properties?
I have tried setting the "name" property on "RestaurantCourse" to unique with #Column(unique=true) but then I get errors about hibernate trying to save multiple courses with the same name (since name must be unique). I have tried grabbing the "courses" table when saving data and using the same id multiple times, but then I get errors about hibernate trying to save multiple courses with the same id (since id must be unique).
Is it even possible to fix this "easily", such as with few specific annotation I'm in the unknown about? Do I need to change something else about how my data is saved to the database, such as the classes, the annotations, or the way I'm trying to save?
Here are my classes.
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name="weeks")
public class RestaurantWeek {
#Id
private long id;
private Date saveDate;
private String weekName;
#OneToMany(cascade = CascadeType.ALL)
private List<RestaurantDay> days;
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name="days")
public class RestaurantDay {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String day;
#OneToMany(cascade = CascadeType.ALL)
private List<RestaurantCourse> courses;
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#TypeDef(name = "list-array", typeClass = ListArrayType.class)
#Entity
#Table(name = "courses")
public class RestaurantCourse {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true)
private String name;
private String price;
private String type;
#Type(type = "list-array")
#Column(name = "tags", columnDefinition = "text[]")
private List<String> tags;
}
And what I'm using to save:
#Repository
public interface RestaurantMenuRepository extends JpaRepository<RestaurantWeek, Long> {
}
public class RestaurantMenuServiceImpl implements RestaurantMenuService {
#Autowired
private RestaurantMenuRepository restaurantMenuRepository;
#Override
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) {
return this.restaurantMenuRepository.save(restaurantWeek);
}
}
Thanks in advance.
Yes is posible, you must use existing entity. But use in this method
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) parameter RestaurantWeek is not correct try put this method some dto class with need field to create entity class, additionally pass the parameter to available identify courses entity find which you can doing relationship and add to days entity.
No pass all parameter every time!
Alright, finally found the correct search terms and found the answer. Resolution was a combination of serob's answer and a bunch of googling.
In RestaurantDay I changed #OneToMany to #ManyToMany.
I created repository interfaces for RestaurantDay and RestaurantCourse.
When saving the course, I save the courses first, then the days, and finally the week, while grabbing all the new ids.
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) {
for (RestaurantDay day : restaurantWeek.getDays()) {
for (RestaurantCourse course : day.getCourses()) {
RestaurantCourse dbCourse = this.restaurantCourseRepository.findCourseByName(course.getName());
if (dbCourse == null) {
course.setId(this.restaurantCourseRepository.save(course).getId());
}
else {
course.setId(dbCourse.getId());
}
}
this.restaurantDayRepository.save(day);
}
return this.restaurantMenuRepository.saveAndFlush(restaurantWeek);
}
Try #NaturalId, this would make your name an Id for the Course entity:
https://vladmihalcea.com/the-best-way-to-map-a-naturalid-business-key-with-jpa-and-hibernate/

Data JPA - Remove entities from repository

I have an existing java application in which I use now spring-boot and data jpa to save some data in a database. In one class Order which I convert now to an #Entity I have a member which is a List<Position>. Following is the code of the reduced classes
#Entity
public class Order
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private List<Position> positions;
//some other members follow here...
}
#Entity
public class Position
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
//some members follow here...
}
So what I have done is the following, I added the annotation #Transient to my list in Order and add inPosition a reference to an Order:
#Entity
public class Order
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Transient
private List<Position> positions;
//some other members follow here...
}
#Entity
public class Position
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#ManyToOne
private Order order;
//some members follow here...
}
Now when I want to save an Order object, then I save first the Order in the corresponding repository and then go through the list of Positions and set in ervery the reference to Order and then save the Position object to its corresponding repository. If I want to fetch an Order then I fetch first the Order and then fetch the Positions in the correspoding repository with findByOrder(..).
So far this works. Now I'm facing the problem, if the application modifies in the Order the list with the Positions and I have to update the database with the new Order object, then I find no smooth solution to delete the Positions in the repository which were removed from the list by the application (as I have no longer a reference to the removed ones). I could delete first all Positions of that Order and then save the existing ones again.
So my questions is maybe if there is a better way to remove the Positions in the repository which were removed by the application. But maybe it would be an XY question, cause my approach how to save the Position-List is the reason why I am facing this problem. I appreciate any hints concering this.
You're not doing it right.
First, it's not clear why you're making the #OneToMany side #Transient.
Best is to use cascade features of JPA.
In your example, if you put:
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval=true)
private List<Position> positions;
All operations on Order will cascade on Positions aswell, so you don't need to explicitly manage them.
See these examples with Hibernate

How to store string lists in database without a join table?

I want to store a List<String> in a postgres DB.
#ElementCollection
private List<String> products;
Hibernate will therefore create a join table. Is it possible to prevent this?
One workaround would be to create an explicit class with bidirectional mapping as follows:
#Entity
public class MainEntity {
#OneToMany(mappedBy = "main")
private List<Product> products;
}
#Entity
public class Product {
#Id
private long id;
#ManyToOne
private MainEntity main;
private String text;
}
But I feel this is a bit over the top for just storing a string list, isn't it?
If you don't find anything better, try this:
Mark your List with #Transient so that it is never persisted directly.
Prepare additional field for persisting your list, of a type that is "persistable" directly by JPA (concatenated, delimetered String seems to be quite natural).
Use methods annotated with #PostLoad and #PrePersist to move data between those two fields, converting from List to String and back.
i'm not sure but could you remove :
#ManyToOne
private MainEntity main;
in class product.
I think it might works properly without this.
Do you want to handle your list from MainEntity or from Product?

Lucene index not updated with Hibernate Search and Spring Data

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();

Categories

Resources