Spring Data Couchbase: projections with reactive repository - spring-data

Reactive Couchbase repository with a method using DTO projections fails with
CouchbaseQueryExecutionException: Query returning primitive got more values than expected: 5
Is DTO projection supported by reactive repositories?
I'm trying to implement a very basic DTO projection repository method with a reactive repository: I have two Couchbase #Documents, and an interface representing a projection. A query repository method projects a few attributes from the two documents using interface projection.
After some debugging I see that the failure occurs because of this check in org.springframework.data.repository.query.QueryMethod:
public boolean isQueryForEntity() {
return getDomainClass().isAssignableFrom(getReturnedObjectType());
}
called from org.springframework.data.couchbase.repository.query.ReactiveAbstractN1qlBasedQueryexecuteDependingOnType().
Apparently the document entity is not assignable from the projection interface which is expected.
Here're the prototype classes in question:
First document:
#Document
#Data
public class DocMetadata {
#Field
private String type;
#Id
#Field
private String id;
#Field
private String title;
}
Second document:
#Document
#Data
public class Doc {
#Field
private String type;
#Id
#Field
private String id;
#Field
private String metadataId;
#Field
private Status status;
#Field
private String releaseDate;
}
Projection interface:
public interface DashboardDocProjection {
String getId(); // from Doc.id
String getReleaseDate(); // from Doc.releaseDate
String getStatus(); // from Doc.status
String getTitle(); // from DocMetadata.title
}
And the repository:
#Repository
public interface CouchbaseDocRepository extends
ReactiveCouchbaseSortingRepository<Doc, String> {
#Query("<query to select Doc join DocMetadata projecting some attributes from both>")
Flux<DashboardDocProjection> getProjection();
}
As a workaround, I can make projections work with a separate #Document representing projected attributes and a separate repository for that document type, but it requires more boilerplate code and is not so nice in general. An example mapping looks like this:
#Query("SELECT META(`doc1`).id AS _ID, META(`doc1`).cas AS _CAS, "
+ “doc2.title as title, "
+ "`doc1`.id as versionId, "
+ "`doc1`.attribute2 as attribute2 "
+ "FROM #{#n1ql.bucket} `doc1` "
+ "JOIN #{#n1ql.bucket} doc2 ON KEYS […] "
+ "WHERE `doc1`._class = "\"<doc1.class>\" "
+ "AND `doc1`.attribute1 = $1 "
+ "AND `doc1`.attribute2 = $2 "
+ "ORDER BY `doc1`.id ASC "
+ "LIMIT $3 OFFSET $4;")
Flux<DashboardEntry> findAll(
String attribute1Value, String attribute2Value, Integer limit, Integer offset);

Related

CriteriaBuilder.or returns no values if entity mapping is empty

I have a spring project with two entities.
public class Parent {
private integer id;
private String name;
private List<Child> children
}
public class Child {
private integer id;
private integer parentId;
private String name;
}
I am attempting to use the Hibernate criteriaBuilder to construct a query that will return all parents who has a name, or has a child with a given name.
criteriaBuilder.or(
criteriaBuilder.like(parent.join("children").get("name"),
"%" + query.getValue() + "%"),
criteriaBuilder.like(parent.get("name"),
"%" + query.getValue() + "%"));
The query works with no problems if a parent has children but will return no results if the parent has no children.
I've tried adding additional an additional predicate that will only check the parent's name if they are childless.
I have completely run out of ideas, any help will be greatly appreciated.

Which return value should I use for the one-to-one table when using spring boot jpa?

We are working on a club management project.
There is a club president on the club table and the user ID is received as a foreign key.
I want to join two tables while using jpa and get the results
If you specify the mapped club table as a type, an error appears (image 1)
If you interface the resulting field, only null values are returned. (Image 2)
How shall I do it?
(Image1)
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.Object[]] to type [#org.springframework.data.jpa.repository.Query com.wodongso.wodongso.entity.Society] for value '{호남대학교, 왕유저13, 두 발의 자유1, 스포츠, 두 바퀴만 있다면 지원가능!!1, 허벅지 터지도록 활동합니다1, true}'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [#org.springframework.data.jpa.repository.Query com.wodongso.wodongso.entity.Society]
(Image2)
society entity
user entity
#Entity
#Data
public class User {
#Id
private String id;
private String name;
private String nickname;
private String password;
private String role;
private String contact;
#Column(name = "profile_url")
private String profileUrl;
private String region;
private String university;
private String major;
#Column(name = "class_of")
private Integer classOf;
private boolean enabled;
#Column(name = "created_at")
private Date createdAt;
}
SocietyRepository
#Repository
public interface SocietyRepository extends JpaRepository<Society, Integer> {
Page<Society> findByNameContaining(String searchKeyword, Pageable pageable);
#Query("SELECT s FROM Society s WHERE s.enabled = :isEnable")
Page<Society> findByEnabledPage(#Param("isEnable") boolean isEnable, Pageable pageable);
#Query("SELECT u.university, u.name, s.name, s.category, s.simpleDesc, s.detailDesc, s.enabled " +
"FROM Society s " +
"INNER join User u " +
"ON u.id = s.officerId " +
"WHERE u.university = :university")
List<Society> findAllByUniversity(#Param("university") String university);
}
Create a class which contains all the fields and add an all args constructor and than use the query:
#Query("SELECT new SocietyWithUser(u.university, u.name, s.name, s.category, s.simpleDesc, s.detailDesc, s.enabled) " +
"FROM Society s " +
"INNER join User u " +
"ON u.id = s.officerId " +
"WHERE u.university = :university")
List<SocietyWithUser> findAllByUniversity(#Param("university") String university);

Spring Data Repository - composite key & don't need to save entity

I'm working with a third party database that is read-only. I have the following Spring Data repository:
public interface FolderRepository extends Repository<Folder, Integer> {
#Query(value = "SELECT f.folderId, a.fileId, a.fileName, " //selecting other columns in my app
+ "FROM User.functionSys(:binData) f "
+ "LEFT JOIN User.vFile a "
+ "ON f.fileId = a.fileId "
+ "group by f.folderId, a.fileId, a.fileName",
nativeQuery = true)
List<Folder> getFolders(#Param("binData") byte[] binData);
}
Folder id and file id form an unique key. So, my folder entity looks like this:
#Entity
#Data
public class Folder implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private FolderFileKey id;
private String fileName;
// several other fields in my app
}
#Embeddable
#Data
class FolderFileKey implements Serializable {
private static final long serialVersionUID = 2L;
private Integer folderId;
private Integer fileId;
}
The problem is that I really want a list of File objects where each File object contains a list of Folders (the same file id may be in several different folders):
#Data
public class FileDto {
private Integer fileId;
private String fileName;
private List<Folder> folders;
}
#Data
public class FolderDto {
private Integer folderId;
private String folderName;
}
I know that I could write a service to transform a Folder entity into the FileDto and FolderDto but is there a way to use Spring Data projections or rewrite the entities to achieve the structure wanted in the Dtos?
Update
User.functionSys(:binData) is a table-valued function (so it returns a table).

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.

Using COUNT in JPQL Query

I have the following JPQL query:
List<DestinationInfo> destinations = em.createQuery("SELECT NEW com.realdolmen.patuva.dto.DestinationInfo(d.name, d.continent, MIN(t.departureDate), MIN(t.pricePerDay), COUNT(t.id))" +
" FROM Destination d, Trip t" +
" WHERE d.continent = :continent " +
" GROUP BY d.name, d.continent").setParameter("continent", searchedContinent).getResultList();
If I run this I get the error:
javax.ejb.EJBTransactionRolledbackException: org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [com.realdolmen.patuva.dto.DestinationsList]
If I leave out the COUNT(t.id) and remove that parameter from my DestinationInfo constructor it works fine. Why can't I map the COUNT(t.id) to my DestinationInfo DTO.
This is my DestinationInfo class:
public class DestinationInfo {
private String name;
private Continent continent;
private Date earliestDeparture;
private Integer totalNumTrips;
private BigDecimal lowestPrice;
public DestinationInfo(String name, Continent continent, Date earliestDeparture, BigDecimal lowestPrice, Integer totalNumTrips) {
this.name = name;
this.continent = continent;
this.earliestDeparture = earliestDeparture;
this.totalNumTrips = totalNumTrips;
this.lowestPrice = lowestPrice;
}
// getters and setters
}
Apparently COUNT(t.id) returns a number of type long. Changing the DestinationInfo class to the following makes it work:
public class DestinationInfo {
private String name;
private Continent continent;
private Date earliestDeparture;
private long totalNumTrips;
private BigDecimal lowestPrice;
public DestinationInfo(String name, Continent continent, Date earliestDeparture, BigDecimal lowestPrice, long totalNumTrips) {
this.name = name;
this.continent = continent;
this.earliestDeparture = earliestDeparture;
this.totalNumTrips = totalNumTrips;
this.lowestPrice = lowestPrice;
}
// getters and setters
}