Search in key value Entity - jpa

Salam,
I have a problem with (key, value) entity searching.
I don't figure out how to write the jpql query to search.. this is my problem
I have document entity, mapped to oneToMany "MetaData" entity
#Entity
public class Document implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="id_document")
private Integer idDocument;
#Column(name="date_enregistrement")
private Timestamp dateEnregistrement;
// other columns
//bi-directional many-to-one association to MetaData
#OneToMany(mappedBy="document")
private List<MetaData> metaData;
/*
getters and setters
*/
}
and MetaData entity
#Entity
#Table(name="meta_data")
public class MetaData implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="id_meta_data")
private Integer idMetaData;
#Column(name="key")
private String key;
#Column(name="value")
private String value;
//bi-directional many-to-one association to Document
#ManyToOne
#JoinColumn(name="id_document")
private Document document;
/*
getters and setters
*/
}
What i want to do is to search for documents by providing some metadata as parameters.
examples:
Find documents where sender (key = sender) is Youssef (value = Youssef) And receiver (key) is Hamza (value)
it's possible that the client provides more than two parameters.
Thanks in advace

As key-value pairs can vary in number, I'll suggest to go with CriteraQuery for more flexibilty:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Document> query = criteriaBuilder.createQuery(Document.class);
Root<Document> root = query.from(Document.class);
Join<Document, MetaData> metadataJoin = root.join("metaData", JoinType.INNER);
List<Predicate> predicateList = new LinkedList<Predicate>();
//If a map contains all your key value pairs
for(Map.Entry<String, String> entry : keyValueMap.entrySet()){
predicateList.add(
criteriaBuilder.and(
criteriaBuilder.equal(metadataJoin.get("key"), entry.getKey()),
criteriaBuilder.equal(metadataJoin.get("value"), entry.getValue())
)
);
}
query.where(criteriaBuilder.or(
predicateList.toArray(new Predicate[predicateList.size()]))
)
query.groupBy(root.get("idDocument"));
query.having(criteriaBuilder.equal(criteriaBuilder.count(root.get("idDocument")), predicateList.size()));
List<Document> documentList = entityManager.createQuery(query).getResultList();

Related

Spring JPA query using specification and projection

I used spring jpa specification to build dynamically an entity query.
It's working perfect but the query returns all entity fields which makes the performance slower.
I want to fetch specific entity fields only and not fetching all entity fields and dependencies which I don't want and I will not use.
I search on the web, I tried some scenarios but without any lack.
Can anyone suggest any solution on this?
Thanks in advance
Here is what I have.I'm using spring boot 2.2.4
public class Concert {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#Column
private String code;
#Column
private double totalIncome;
#Column
private double totalExpenses;
#Column
private double totalBudget;
#ManyToOne(targetEntity = Orchestra.class, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "orchestra_id")
private Orchestra orchestra;
#ManyToOne(targetEntity = ConcertStatus.class, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "concert_status_id")
private ConcertStatus status;
/* other fields */
}
Specification:
public class ConcertSpecification implements Specification<Concert> {
#Override
public Predicate toPredicate(Root<Concert> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
//add add criteria to predicates
for (Criterion criteria : criteriaList) {
/* predicates builder here */
}
return builder.and(predicates.toArray(new Predicate[0]));
}
}
Repository:
public interface ConcertDao extends JpaRepository<Concert, Long>, JpaSpecificationExecutor<Concert>, PagingAndSortingRepository<Concert, Long> { }
ConcertService:
public interface ConcertService {
Page<Concert> findAll(#Nullable Specification<Concert> spec, Pageable pageable);
}
ConcertServiceImpl:
#Service(value = "concertService")
public class ConcertServiceImpl implements ConcertService {
public Page<Concert> findAll(#Nullable Specification<Concert> spec, Pageable pageable){
List<Concert> list = new ArrayList<>();
concertDao.findAll(spec).iterator().forEachRemaining(list::add);
return new PageImpl<Concert>(list);
}
}
Usage of projections with specifications are not supported and there is a PR for it that has been hanging for over five years.

Spring Data JPA order by value from OneToMany relation

I am trying to sort a result by nested collection element value. I have a very simple model:
#Entity
public class User {
#Id
#NotNull
#Column(name = "userid")
private Long id;
#OneToMany(mappedBy = "user")
private Collection<Setting> settings = new HashSet<>();
// getters and setters
}
#Entity
public class Setting {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "userid")
private User user;
private String key;
private String value;
// getters and setters
}
public interface UserRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
I want to have a result returned sorted by the value of one setting.
Is it possible to order by user.settings.value where settings.name = 'SampleName' using Spring Data JPA with QueryDSL?
I've used JpaSpecificationExecutor. let's see findAll for example.
Page<T> findAll(#Nullable Specification<T> spec, Pageable pageable);
Before call this method you can create your specification dynamically (where condition) and Pageable object with dynamic Sort information.
For example
...
Specification<T> whereSpecifications = Specification.where(yourWhereSpeficiation);
Sort sortByProperty = Sort.by(Sort.Order.asc("property"));
PageRequest orderedPageRequest = PageRequest.of(1, 100, sortByProperty);
userRepository.findAll(whereSpecifications, PageRequest.of(page, limit, orderedPageRequest));

JPA #ManyToOne foreign key not generated?

I have two ManyToOne relationships in my application which are represented by Lists. For the relationship "ChapterSection - ManyToOne - Chapter, the foreign key is inserted in the table, when persisting the entity (in the table "ChapterSection" the foreign key for "Chapter" is stored). For the other relationship, which is "Chapter - ManyToOne - Document".
I use ddl.generation "drop-and-create-tables". In the Database I can see, that the column "Chapter.fk_document_iddocument" is marked as an indexed foreign key referenced to the document id. (I use EclipseLink and MySQL).
I don't see the difference between these two relationships and why one is working out but the other is not.
Document Entity:
#Entity
public class Document implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="iddocument")
private Long id;
#Column(name="document_name")
private String documentName;
#OneToMany(mappedBy="Document", cascade = CascadeType.PERSIST)
private List<Chapter> chapters;
#Enumerated(EnumType.STRING)
#Column(name="document_type")
private DocumentTypes documentType;
//...getters, setters and other generated methods
Chapter Entity:
#Entity
public class Chapter implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="idchapter")
private Long id;
#Column(name="chapter_order")
private int chapterOrder;
#Column(name="parent_chapter")
private Long parentChapter;
#Column(name="chapter_name")
private String chapterName;
#ManyToOne(optional=false)
#JoinColumn(name="fk_document_iddocument")
private Document document;
#OneToMany(mappedBy="Chapter", cascade=CascadeType.PERSIST)
List<ChapterSection> chapterSections;
//...getters, setters and other generated methods
ChapterSection Entity:
#Entity
public class ChapterSection implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="idchaptersection")
private Long idChapterSection;
#Column(name="section_name")
private String sectionName;
#Column(name="section_order")
private int sectionOrder;
#Column(name="content")
private String content;
#ManyToOne
#JoinColumn(name="fk_chapter_idchapter")
private Chapter chapter;
//...getters, setters and other generated methods
The method I create the document with:
public void createDocument() {
List <Chapter> chapters = new ArrayList<>();
for (int i = 0; i <= 4; i++) {
Chapter chapter = new Chapter();
chapter.setChapterOrder(i);
chapter.setChapterName("Chapter "+i);
List <ChapterSection> chapterSections = new ArrayList<>();
for (int j = 0; j <= 4; j++) {
ChapterSection chapterSection = new ChapterSection();
chapterSection.setChapter(chapter);
chapterSection.setSectionName("Chapter "+i+" Section");
chapterSection.setSectionOrder(j);
chapterSection.setContent("Kapitel "+i+ ", Section "+j+" Content!");
chapterSections.add(chapterSection);
}
chapter.setChapterSections(chapterSections);
chapters.add(chapter);
}
document.setDocumentName("My Doc");
document.setChapters(chapters);
document.setDocumentType("My Doc Type");
documentDAO.persistDocument(document);
}
The mappedBy element of the #OneToMany annotation is defined in the JPA spcification as follows:
The field or property that owns the relationship. Required unless the relationship is unidirectional.
According to this definition your mappedBy elements must be set as (the value should be field name but not the class name):
#OneToMany(mappedBy="document", cascade = CascadeType.PERSIST)
private List<Chapter> chapters;
#OneToMany(mappedBy="chapter", cascade=CascadeType.PERSIST)
List<ChapterSection> chapterSections;
In you createDocument() method you haven't created the relationship between the Document and the Chapter. So you should tie them as follows:
chapter.setDocument(document);

JPA Exception- Can not find constructor for <Class> with argument types "[class java.lang.String, class java.lang.String]" to fill data

I use jBoss Fuse 6.1.0 with blueprint DSL with openJPA. I use Container Managed transaction (JTA) and transaction managed by Aspects that handles Commit and Rollback as of now
I have following Classes that are JPA entities.
#Entity
#Table(name="CLIENT")
#NamedQuery(name="Client.findAll", query="SELECT c FROM Client c")
public class Client implements Serializable {
private static final long serialVersionUID = 1L;
//Had to add this for avoiding exception. And it works as expected
//Dummy constructor for JPA - Workaround
public Client(String s1, String s2){}
#Column(name="requestid", unique=true,nullable=false)
private String requestId;
#Id
#Column(name="clientid", unique=true, nullable=false, length=128)
private String clientId;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="REQUESTID", nullable=false)
private RoccoRequest roccoRequest;
//bi-directional One-To-Many association to ClientGroup
#OneToMany(mappedBy="client",fetch=FetchType.LAZY)
private List<ClientGroup> clientGroups;
....
,...
...
}
#Entity
#Embeddable
#Table(name="CLIENTGROUP")
#NamedQuery(name="ClientGroup.findAll", query="SELECT c FROM ClientGroup c")
public class ClientGroup implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ClientGroupPK id;
#Column(length=32)
private String type;
#Column(name="clientid", length=128)
private String clientId;
//bi-directional many-to-one association to Client
#ManyToOne(fetch=FetchType.EAGER)
#MapsId("clientid")
#JoinColumn(name="CLIENTID", nullable=true, insertable=false, updatable=false)
private Client client;
..
.
.
.
}
#Entity
#Table(name="ROCCOREQUEST")
#NamedQuery(name="RoccoRequest.CHECK_EXISISTING_CLIENT_DETAILS",
query="SELECT r FROM RoccoRequest r JOIN r.client c WHERE c.crmId = :crmId")
public class RoccoRequest implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="requestid", unique=true, nullable=false, length=128)
private String requestId;
#OneToOne(mappedBy="roccoRequest", fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.REMOVE})
private Client client;
..
..
..
CriteriaQuery<Client> criteriaQuery = criteriaBuilder.createQuery(Client.class);
Root<Client> clientRoot = criteriaQuery.from(Client.class);
//Join the Client table with the RoccoRequest table
final Join<Client, RoccoRequest> clientRoccoJoin = clientRoot.join(Client_.roccoRequest,JoinType.INNER);
final Path<String> _requestStatus = clientRoccoJoin.get(RoccoRequest_.statusCode);
final Path<String> _requestId = clientRoccoJoin.get(RoccoRequest_.requestId);
final Predicate _crmIdPredicate = criteriaBuilder.equal(clientRoot.get(Client_.crmId), CRMId);
criteriaQuery.multiselect(_requestId,_requestStatus);
criteriaQuery.where(_crmIdPredicate);
//Get list of details of existing requests for the client with the request type as ACO
clientDetails = entityManager.createQuery(criteriaQuery).getResultList();
if(null != clientDetails) for(Client clientDetail : clientDetails){
StatusBO statusDetails = new StatusBO();
statusDetails.setCode((clientDetail.getRoccoRequest().getStatusCode()));
PreInitiationBO preinitiateDetails = new PreInitiationBO();
preinitiateDetails.getCaseHeader().setRequestId(requestId);
preinitiateDetails.setStatus(statusDetails);
exisitngRequestInfo.add(preinitiateDetails);
}
I have did some Criteria fetching of the entities. But I'm getting an exception as follows:
Can not find constructor for "class com.xxx.xxx.model.Client" with
argument types "[class java.lang.String, class java.lang.String]" to
fill data.
Why does JPA expect an argument Constructor? It has anything to do with the association? I tried removing the OneToMany relationship but I still get the error.
Please note that I have added a 2 argument constructor that makes no sense to me. But it works if it's given. log root level has Debug enabled. It has very less information on exception.
Please help.
As JBNizet pointed out,
I was making a dumb mistake by adding multiselect with two Strings but was having a CrtieriaQuery of type Client.class.
This can either be solved by removing the multiselect(Not in my case) or by Making the CriteriaQuery and other types with Tuples.class instead of Client.class and loop through the Tuples and get as tuple.get(0) etc.
Problem resolved. Thanks #Neil and #JBNizet

Hibernate Search Tuple Queries

I have an entity Message with a one-to-many relation to an entity Header. How can I create a tuple based search query like
(message.headerKey="foo" and message.headerValue="123") and
(message.headerKey="bar" and message.headerValue="456")
My current logic would also match when I swap the header values in my search criteria
(message.headerKey="foo" and message.headerValue="456") and
(message.headerKey="bar" and message.headerValue="123")
How can I do a tuple based query using the Hibernate Search API?
This is my Message Entity:
#Entity
#Table(name="MESSAGE")
#Indexed
public class MessageEntity implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Long id;
#Column(name="message_timestamp")
private Date timestamp;
#Column(name="payload")
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
private String payload;
#OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "message")
#IndexedEmbedded
private List<HeaderEntity> headers;
// Getters and Setters
}
This is my Header Entity:
#Entity
#Table(name="HEADER")
public class HeaderEntity implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="header_key")
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
private String headerKey;
#Column(name="header_value")
Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
private String headerValue;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="message_id")
private MessageEntity message;
// Getters and Setters
}
This is my search logic:
public List<MessageEntity> search(Header[] headers) {
FullTextEntityManager fullTextEntityManager = org.hibernate.search.jpa.Search.getFullTextEntityManager(mgr);
QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(MessageEntity.class).get();
TermMatchingContext onFieldKey = qb.keyword().onField("headers.headerKey");
TermMatchingContext onFieldValue = qb.keyword().onField("headers.headerValue");
BooleanJunction<BooleanJunction> bool = qb.bool();
org.apache.lucene.search.Query query = null;
for (Header header : headers) {
bool.must(onFieldKey.matching(header.getKey()).createQuery());
bool.must(onFieldValue.matching(header.getValue()).createQuery());
}
query = bool.createQuery();
FullTextQuery persistenceQuery = fullTextEntityManager.createFullTextQuery(query, MessageEntity.class);
persistenceQuery.setMaxResults(10);
return persistenceQuery.getResultList();
}
Your approach will indeed not work. The problem is that Lucene is a flat data structure, in particular associations (embedded entities) are just "added" to the Lucene Document of the owning entity. In your case the MessageEntity document will contain two fields per headerKey respectively headerValue. Once with "foo" and "bar" as value and56" as values. once with "123" and "456" as values. There is no notion that two of these values are acutally a pair.
One potential solution is to create a unique field/value pair. Using a custom class bridge you could create a "keyValueField" containing header key and value as concatenated value. In your query you would then target this field using concatenated query parameters.