How to retrieve #IndexedEmbedded properties? - hibernate-search

FullTextQuery.setProjection("id", "author") ignored author's id, name property. How can I retrieve these properties?
#MappedSuperclass
class BaseContent{
#IndexedEmbedded(prefix = "author.", includePaths = {"id", "name"}) #ManyToOne
Author author;
}
#Entity #Indexed
class Content extends BaseContent{
#Id #DocumentId
Integer id;
}
#Entity
class Author{
#Id
Integer id;
#Field(store = Store.YES)
String name;
}
EDIT:
Is this query correct?.
FullTextQuery ftq = fullTextSession.createFullTextQuery(luceneQuery, Content.class);
ftq.setProjection("id", "author.id", "author.name");
ftq.setResultTransformer(new AliasToBeanResultTransformer(Content.class));
List<Content> result = ftq.list();

Use the author. prefix.
fullTextQuery.setProjection("author.id", "author.name")
EDIT: Did you try inspecting the results without your transformer? It should return a List<Object[]> with the content of the projections. If it does, then it's the transformer that isn't working. I very much doubt that AliasToBeanResultTransformer is able to handle composite aliases such as "author.name". You'll probably need you own transformer.
Also note that:
If you just want to get the Content entity, and getting it from the database doesn't bother you, just remove the projection and result transformer: Hibernate Search will get a List<Content> from the database for you.
If what you're trying to do is to avoid loading anything from the database, then you're on the right path. But as I said, you'll probably need a custom transformer.

Related

Can I mark more than one field as #indexed in single document in MongoDb?

I am using #indexed annotation in my document for the person while using spring boot and MongoDb.
I have already marked firstName to be used for indexing.
Can I mark other fields as well for index?
The document is as mentioned below:
#Document
public class Person {
#Id private String id;
#Indexed(name = "first_name_index", direction = IndexDirection.DESCENDING)
private String firstName;
private String secondName;
private LocalDateTime dateOfBirth
}
Is it a good practice to mark more than one field as indexed?
Yes. You can index multiple fields as #Indexed if your queries are orthogonal.
You need to support findByFirstName and findByDateOfBirth queries.
you add #Indexed annotation to firstName
you add #Indexed annotation to dateOfBirth
You need to support findByFirstName and findByFirstNameAndDateOfBirth queries.
you add #CompoundIndex(def = "{'findByFirstName': 1, 'dateOfBirth': 1}") to public class Person.

How to use multiple foreign keys in JPA?

I'm starting a project to know more in detail JPA.
Context:
At the end of his internship, the student has a report to make and a presentation in front of his professor to do about the internship.
I've a database, which is called "grade_management". It must contains a "student", "presentation", "report", "professor" and a "mark" (there are several rating criteria such as expression, quality of powerpoint ...) table. But now it's empty, since I want to make it throught JPA.
I've a "Presentation" class. Which countain this:
#Entity
public class Presentation implements Serializable {
#Id
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
private mark_id;
private int professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
}
But the Presentation table contain 2 foreign key: professor_id and mark_id.
My question is: How can I indicate that both of them are foreign key ?
I'm sorry if I'm not clear, don't hesitation to ask question.
Cordially
You shouldn't reference other entities by their ID, but by a direct reference to the entity.
Something like that :
#ManyToOne
#JoinColumn(name = "mark_id", referencedColumnName = "id")
private Mark mark; // supposed here that mark_id if link to entity `Mark`
#ManyToOne
#JoinColumn(name = "professor_id", referencedColumnName = "id") // suppose "id" is the column name of the PK inside the table Professor.
private Professor professor; // supposed here that professor_id if link to entity `Professor`
This code is supposing that you use an unidirectional relation.
For bidirectional you have to define this in the other side (Mark/Professor type)
#OneToMany(mappedBy = "professor")
private Presentation presentation;
From your explanation, it looks like you have a Database named grade_management and in that database you have "student", "presentation", "report", "professor" and a "mark" tables (i.e: which are #Entity by themselves defined in their separate respective classes )
I'm not sure whether you have defined them or not. If not then you have to define them first and then use the refactored code mentioned below.
So, you will have many-to-one relation mapping. You can annotate your foreign keys belonging to different tables using #ManyToOne annotation to indicate relation type and #JoinColumn annotation to indicate that this entity has a foreign key to the referenced table.
You can redefine your Presentation class show below:
#Entity
#Table(name = "Presentation")
public class Presentation implements Serializable {
#Id
#Column(name="presentation_id")
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
#ManyToOne
#JoinColumn(name = "mark_id")
private Mark mark_id;
#ManyToOne
#JoinColumn(name = "professor_id")
private Professor professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
//getter and setter
}
Also, if you need more information to read upon for yourself you can always checkout this Hibernate Documentation that explains everything you'll need to know.

Does hibernate search - 6.0 version support projection on embedded entities?

I am developing a search api where I need to return in response the resource with only the fields/properties asked in request. The fields can be of sub elements as well. E.g - book.author.name where book is the parent resource and author a sub resource under it, may be with a many to one relationship.
I have learned in earlier versions of hibernate (5.x.x) projections is not supported on embedded entities.
So wanted to know if this feature is added in 6.0
When there is a single author, yes, you can do a projection on the author (but you could already in Search 5, though in a less convenient way):
#Entity
#Indexed
class Book {
#Id
private Long id;
#GenericField
private String title;
#ManyToOne
#IndexedEmbedded
private Author author;
// ...getters and setters...
}
#Entity
class Author {
#Id
private Long id;
#GenericField(projectable = Projectable.YES)
private String firstName;
#GenericField(projectable = Projectable.YES)
private String lastName;
#OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
// ...getters and setters...
}
class MyProjectedAuthor {
public final String firstName;
public final String lastName;
public MyProjectedAuthor(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
SearchSession searchSession = Search.session(entityManager);
List<MyProjectedAuthor> projectedAuthors = searchSession.search(Book.class)
.asProjection(f -> f.composite(
MyProjectedAuthor::new,
f.field("author.firstName", String.class),
f.field("author.lastName", String.class),
))
.predicate(f -> f.matchAll())
.fetchHits(20);
Multi-valued projections (e.g. if you have multiple authors per book) are not supported yet, but we will be working on it before the 6.0.0 release: https://hibernate.atlassian.net/browse/HSEARCH-3391
If you were talking about loading the authors from the database instead of projections, then there is no such built-in feature yet. We'll be looking into it when we address HSEARCH-3071, but I can't tell how long it will take.
As a workaround, for single-valued associations, you can implement loading manually:
#Entity
#Indexed
class Book {
#Id
private Long id;
#GenericField
private String title;
#ManyToOne
#IndexedEmbedded
private Author author;
// ...getters and setters...
}
#Entity
class Author {
#Id
#GenericField(projectable = Projectable.YES)
private Long id;
private String firstName;
private String lastName;
#OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
// ...getters and setters...
}
SearchSession searchSession = Search.session(entityManager);
List<Author> authors = searchSession.search(Book.class)
.asProjection(f -> f.composite(
authorId -> entityManager.getReference(Author.class, authorId),
f.field("author.id", Long.class)
))
.predicate(f -> f.matchAll())
.fetchHits(20);
// Or, for more efficient loading:
SearchSession searchSession = Search.session(entityManager);
List<Long> authorIds = searchSession.search(Book.class)
.asProjection(f -> f.field("author.id", Long.class))
.predicate(f -> f.matchAll())
.fetchHits(20);
List<Author> authors = entityManager.unwrap(Session.class).byMultipleIds(Author.class)
.withBatchSize(20)
.multiLoad(authorIds);
EDIT: According to your comment, your problem was related to many fields, not just the author. Essentially, you are concerned about loading as few associations as possible.
The most common solution to this problem in Hibernate ORM is to set all your associations' fetch mode to lazy (which is what you should do by default, anyway).
Then when searching, do not even think about loading: just ask Hibernate Search to retrieve the entities you need. Associations will not be loaded at that point.
Then, when you serialize your entities to JSON, only the associations you actually use will be loaded. If you correctly set the default batch fetch size (with hibernate.default_batch_fetch_size, here, performance should be comparable to what you'll achieve with more complicated solutions, at a fraction of the development time.
If you really want to fetch some of the associations eagerly, the easiest solution would probably be to leverage JPA's entity graphs: they tell Hibernate ORM which associations to load exactly when it loads the Book entity.
There's no built-in functionnality for that in Hibernate Search 6 yet, but you can do it manually:
#Entity
#Indexed
class Book {
#Id
private Long id;
#GenericField
private String title;
#ManyToOne // No need for an #IndexedEmbedded with this solution, at least not for loading
private Author author;
// ...getters and setters...
}
#Entity
class Author {
#Id // No need for an indexed ID with this solution, at least not for loading
private Long id;
private String firstName;
private String lastName;
#OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
// ...getters and setters...
}
SearchSession searchSession = Search.session(entityManager);
List<Long> bookIds = searchSession.search(Book.class)
.asProjection(f -> f.composite(ref -> (Long)ref.getId(), f.entityReference()))
.predicate(f -> f.matchAll())
.fetchHits(20);
// Note: there are many ways to build a graph, some less verbose than this one.
// See https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#fetching-strategies-dynamic-fetching-entity-graph
javax.persistence.EntityGraph<Book> graph = entityManager.createEntityGraph( Book.class );
graph.addAttributeNode( "author" );
// Ask for the author association to be loaded eagerly
graph.addSubgraph( "author" ).addAttributeNode( "name" );
List<Book> booksWithOnlySomeAssociationsFetched = entityManager.unwrap(Session.class).byMultipleIds(Book.class)
.with(graph, org.hibernate.graph.GraphSemantic.FETCH)
.withBatchSize(20)
.multiLoad(bookIds);
Note that, even with this solution, you should probably set the fetch mode to lazy in the mapping (#OnyToMany, ...) for as many associations as possible, because Hibernate ORM doesn't allow making an eager association lazy though a fetch graph.

spring data jpa fine granular auditing, custom audit

I have requirement where I need to insert user name and group name to which the user belongs (both available in SecurityContext) in the same table.
class Entity
{
#createdBy
String username
#createdBy
String groupname
other fields ...
}
As per requirement. I cant solve this issue by making a user class and referencing it through a foreign key.
With current implementation of AuditingHandler both fields are getting the same value. How do I make sure they get respective values.
Can this be achieved using current implementation ?
If not thn how can I provide custom implementation of AuditingHandler ?
You could make a separate embeddable class and annotate it with #CreatedBy in your parent class. One way is to define a bean implementing AuditorAware, then you can make it return custom object, containing your two required fields. For example, your parent class would look like this (note the listener annotation):
#Entity
#EntityListeners(AuditingEntityListener.class)
public class AuditedEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
#Embedded
#CreatedBy
private AuditorDetails createdBy;
// setters and getters
}
where AuditorDetails is:
#Embeddable
public class AuditorDetails {
private String username;
private String groupname;
// setters and getters
}
and finally, your AuditorAware bean:
#Component
class AuditorAwareImpl implements AuditorAware<AuditorDetails> {
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setUsername("someUser")
.setGroupname("someGroup");
}
}
AuditingHandler fetches your custom AuditorDetails from your AuditorAware bean (it must be single bean implementing it) and sets it in your auditable entity.

JPA Query Many To One nullable relationship

I have the following entities and would like to seek help on how to query for selected attributes from both side of the relationship. Here is my model. Assume all tables are properly created in the db. JPA provider I am using is Hibernate.
#Entity
public class Book{
#Id
private long id;
#Column(nullable = false)
private String ISBNCode;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = false)
private Person<Author> author;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
private Person<Borrower> borrower;
}
#Inheritance
#DiscriminatorColumn(name = "personType")
public abstract class Person<T>{
#Id
private long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Info information;
}
#Entity
#DiscriminatorValue(PersonType.Author)
public class Author extends Person<Author> {
private long copiesSold;
}
#Entity
#DiscriminatorValue(PersonType.Borrower)
public class Borrower extends Person<Borrower> {
.....
}
#Entity
public class Info {
#Id
private long id;
#Column(nullable=false)
private String firstName;
#Column(nullable=false)
private String lastName;
......;
}
As you can see, the book table has a many to one relation to Person that is not nullable and Person that is nullable.
I have a requirement to show, the following in a tabular format -
ISBNCode - First Name - Last Name - Person Type
How can I write a JPA query that will allow me to select only attributes that I would want. I would want to get the attributes ISBN Code from Book, and then first and last names from the Info object that is related to Person Object that in turn is related to the Book object. I would not want to get all information from Info object, interested only selected information e.g first and last name in this case.
Please note that the relation between the Borrower and Book is marked with optional=true, meaning there may be a book that may not have been yet borrowed by someone (obviously it has an author).
Example to search for books by the author "Marc":
Criteria JPA Standard
CriteriaQuery<Book> criteria = builder.createQuery( Book.class );
Root<Book> personRoot = criteria.from( Book.class );
Predicate predicate = builder.conjunction();
List<Expression<Boolean>> expressions = predicate.getExpressions();
Path<Object> firtsName = personRoot.get("author").get("information").get("firstName");
expressions.add(builder.equal(firtsName, "Marc"));
criteria.where( predicate );
criteria.select(personRoot);
List<Book> books = em.createQuery( criteria ).getResultList();
Criteria JPA Hibernate
List<Book> books = (List<Book>)sess.createCriteria(Book.class).add( Restrictions.eq("author.information.firstName", "Marc") ).list();
We recommend using hibernate criterias for convenience and possibilities.
Regards,