I got the following data structure
#Entity
public class Publication {
private Map<Integer, Author> authors;
// more stuff
}
#Entity
public class Author {
private String name;
// more stuff
}
I'm looking for a query dsl predicate which gives my all publication where any Author.name contains a certain String e.g. "Hans"
I tried:
QPublication publication = QPublication.publication;
QAuthor author = QAuthor.author;
publication.authors.containsValue(JPAExpressions.selectFrom(author).where(author.lastName.containsIgnoreCase("Hans")));
But this complains if there is more than one author containing "Hans" in the name. Is there somenting like publication.authors.anyValue().name.equalsIgrnoreCase("Hans") like there is for a Set?
I found a solution with a join and a custom repository implementation:
public class PublicationRepositoryImpl extends QueryDslRepositorySupport
implements PublicationRepositoryCustom {
#Override
public Page<Publication> findByAnyAuthorName(String name, PageRequest pageRequest) {
QPublication publication = QPublication.publication;
JPQLQuery<Publication> query = from(publication);
QAuthor author = QAuthor.author;
query.join(publication.authors, author);
query.where(author.name.containsIgnoreCase(name);
JPQLQuery<Publication> pagedQuery = getQuerydsl()
.applyPagination(pageRequest, query);
return PageableExecutionUtils.getPage(pagedQuery.fetch(), pageRequest,
() -> query.fetchCount());
}
I think you have missed the
BooleanBuilder
in your Repository - try this
QPublication publication = QPublication.publication;
QAuthor author = QAuthor.author;
BooleanBuilder where= new BooleanBuilder();
where.and(publication.author.name.containsIgnoreCase(textToSearch));
//pass the where condition to the desired Repo method
repo.findAll(where);
Related
I have a composite primary key made of planId and planDate, when the user gives me both this attributes I cant find a way to retrieve it from my Repo. Should findById work like this?
public Plans assignPlansToMeds(Long id, Long planId, Date planDate) {
Set<Meds> medsSet = null;
Meds meds = medsRepo.findById(id).get();
Plans plans = plansRepo.findById(planId, planDate).get();
medsSet = plans.getAssignedMeds();
medsSet.add(meds);
plans.setAssignedMeds(medsSet);
return plansRepo.save(plans);
}
My Primary Key:
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Embeddable
public class PlansPKId implements Serializable {
private long planId;
private Date planDate; // format: yyyy-mm-dd
}
Plans entity:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "plans")
public class Plans {
#EmbeddedId
private PlansPKId plansPKId;
#Column
private String planName;
#Column
private String weekday;
#ManyToMany
#JoinTable(name = "Plan_Meds", joinColumns = {
#JoinColumn(name = "planDate", referencedColumnName = "planDate"),
#JoinColumn(name = "planId", referencedColumnName = "planId") }, inverseJoinColumns = #JoinColumn(name = "id"))
private Set<Meds> assignedMeds = new HashSet<>();
}
where I ask for the planId and planDate:
#PutMapping("/medicine/{id}/assignToPlan/{planId}/date/{plandate}")
public Plans assignMedToPlan(#PathVariable Long id, #PathVariable Long planId, #PathVariable Date planDate){
return assignService.assignPlansToMeds(id, planId, planDate);
}
The Spring JpaRepository only allows one type as the ID-type, as can be seen in the javadoc. Therefore, findById will never accept two arguments.
You need to define your repository with your EmbeddedId-type as ID-type as follows:
#Repository
public interface PlansRepository extends JpaRepository<Plans, PlansPKId> {
}
You can then call the findById method as follows:
Plans plans = plansRepo.findById(new PlansPKId(planId, planDate))
.orElseThrow(PlansNotFoundException.idAndDate(planId, planDate));
If you dont want to create a new PlansPKId instance for every query, you could also define a repository method as follows and let Spring derive the query based on the method name:
Optional<Plans> findByPlansPKIdPlanIdAndPlansPKIdPlanDate(long planId, Date planDate);
If you don't like to have a cumbersome method name you could as well define a JPQL query and name the method as you like:
#Query("select p from Plans p where p.plansPKId.planId = :planId and p.plansPKId.planDate = :planDate")
Optional<Plans> findByCompositeId(#Param("planId) long planId, #Param("planDate") Date planDate);
On a side note, I strongly encourage you to use LocalDate, LocalDateTime or ZonedDateTime (depending on your needs) instead of the legacy Date class.
Moreover, you shouldn't call get() on an Optional without checking if it is present. I recently wrote an answer on SO describing how you can create an elegant error handling. If you stuck to my example, you had to create a NotFoundException and then create the PlansNotFoundException which extends NotFoundException. by this means, everytime when a PlansNotFoundException is thrown in thread started by a web request, the user would receive a 404 response and a useful message if you implement it like this:
public abstract class NotFoundException extends RuntimeException {
protected NotFoundException(final String object, final String identifierName, final Object identifier) {
super(String.format("No %s found with %s %s", object, identifierName, identifier));
}
protected NotFoundException(final String object, final Map<String, Object> identifiers) {
super(String.format("No %s found with %s", object,
identifiers.entrySet().stream()
.map(entry -> String.format("%s %s", entry.getKey(), entry.getValue()))
.collect(Collectors.joining(" and "))));
}
}
public class PlansNotFoundException extends NotFoundException {
private PlansNotFoundException(final Map<String, Object> identifiers) {
super("plans", identifiers);
}
public static Supplier<PlansNotFoundException> idAndDate(final long planId, final Date planDate) {
return () -> new PlansNotFoundException(Map.of("id", id, "date", date));
}
}
For the Meds case:
public class MedsNotFoundException extends NotFoundException {
private MedsNotFoundException(final String identifierName, final Object identifier) {
super("meds", identifierName, identifier);
}
public static Supplier<MedsNotFoundException> id(final long id) {
return () -> new MedsNotFoundException("id", id);
}
}
Meds meds = medsRepo.findById(id).orElseThrow(MedsNotFoundException.id(id));
hi I am trying to make a JPA custom query call using Examplematcher . Below is the code i am trying . If i get the stateId i need to use it in query condition otherwise i need pull all counties irrespective of state . I dont want to make two repo calls like to achieve it in a single repo call .
StateCountyId stateCountyId = new StateCountyId();
StateCounty stateCounty = new StateCounty();
if(null != stateId){
stateCountyId.setStateId(stateId);
}
stateCounty.setStateCountyId(stateCountyId);
return countiesRepository.findAllByStateCountyIdStateIdOrderByStateCountyIdCountyNameAsc
(Example.of(stateCounty, ExampleMatcher.matchingAll().withIgnoreCase()))
.stream().map(this::countyAsDropDown).collect(Collectors.toList());
#Repository
public interface CountiesRepository extends JpaRepository<StateCounty, StateCountyId> {
List<StateCounty> findAllByStateCountyIdStateIdOrderByStateCountyIdCountyNameAsc(Example
stateId);
}
#Entity
#Builder
public class StateCounty implements Serializable {
#EmbeddedId
StateCountyId stateCountyId;
#Column(name = "CODE_NBR")
private String codeNbr;
}
#Embeddable
#EqualsAndHashCode
public class StateCountyId implements Serializable {
#Column(name = "STATE_ID")
private String stateId;
#Column(name = "COUNTY_NAME")
private String countyName;
}
If i make the repo call with just string stateId like below its working , but if stateId is empty it will bring back empty result , i need all need to comeback .
countiesRepository.findAllByStateCountyIdStateIdOrderByStateCountyIdCountyNameAsc
(stateId)
.stream().map(this::countyAsDropDown).collect(Collectors.toList());
Use a Specification like this:
#Repository
public interface CountiesRepository extends JpaRepository<StateCounty, StateCountyId> {
List<StateCounty> findAllByStateCountyIdStateIdOrderByStateCountyIdCountyNameAsc(Specification<StateCounty> specification);
}
countiesRepository.findAllByStateCountyIdStateIdOrderByStateCountyIdCountyNameAsc(
(root, query, cb) -> {
if (stateId == null) return null;
return cb.equal(root.get("stateCountyId").get("stateId"), stateId);
}
)
.stream().map(this::countyAsDropDown).collect(Collectors.toList());
Expect the two entities Movie and Genre:
#Entity
public class Movie {
#Id
private long id;
private String name;
private ToMany<Genre> genres;
[...]
}
#Entity
public class Genre {
#Id
private long id;
private String name;
[...]
}
We all know how to create a relation and save it:
Movie movie = new Movie();
movie.setTitle("Star Wars");
movie.getGenres().add(new Genre("Sci-Fi");
box.put(movie);
but is there a possibility to query all Movie-objects with a specific Genre? Like
Box<Movie> box = boxStore.boxFor(Movie.class);
Query query = box.query()
.equal(Genre_.name, "Sci-Fi") // note that I want to query the Movie-Box with Genre-properties
.build();
List movies = query.find();
My goal is to find all movies with a specific genre in a simple way. Does anyone know how to do it or do I have to query all movies and filter the result on my own? Or do I have to adapt my entities in another way?
Update:
I prepared the correct marked answer below to a working example:
final Genre genreSciFi = genreBox.query().equal(Genre_.name, "Sci-Fi").build().findFirst();
List<Movie> filteredMovies = movieBox.query().filter(new QueryFilter<Movie>() {
#Override
public boolean keep(#NonNull Movie entity) {
return entity.getGenres().contains(genreSciFi);
}
}).build().find();
To make the contains-Method work correctly, override equals-Method in your Genre-Entity:
#Override
public boolean equals(Object obj) {
return obj instanceof Genre && ((Genre) obj).getId() == id && ((Genre) obj).getName().equals(name);
}
Unfortunately, this part of the API is not exposed in Java yet. We want to refactor the Query API very soon.
Until this is ready, you can workaround using query filtering. Example using Java/Kotlin-ish code for brevity:
Query query = movieBox.query().filter(movie -> {
return genres.contains(genre -> {
return "Sci-Fi".equals(genre.getName())
}
}).build()
(Will make it similar in Java with the next update.)
I'm still new to Java but I'm having problems with an application framework when I turn off Solr (To see changes made to database) I get an error because one of the classes is still using using an attribute that no longer exists.
Part 1: I have no idea why this was changed, and doesn't use a ManyToMany anymore, most likely a workaround for some limitation I guess.
public class CategoryImpl implements Category, Status, AdminMainEntity {
#OneToMany(targetEntity = CategoryProductXrefImpl.class, mappedBy = "categoryProductXref.category")
protected List<CategoryProductXref> allProductXrefs = new ArrayList<CategoryProductXref>(10);
Part 2:
public class CategoryProductXrefImpl implements CategoryProductXref {
#EmbeddedId
CategoryProductXrefPK categoryProductXref = new CategoryProductXrefPK();
public CategoryProductXrefPK getCategoryProductXref() {
return categoryProductXref;
}
public void setCategoryProductXref(CategoryProductXrefPK categoryProductXref) {
this.categoryProductXref = categoryProductXref;
}
public void setCategoryProductXref(CategoryProductXrefPK categoryProductXref) {
this.categoryProductXref = categoryProductXref;
}
public Category getCategory() {
return categoryProductXref.getCategory();
}
public void setCategory(Category category) {
categoryProductXref.setCategory(category);
}
public Product getProduct() {
return categoryProductXref.getProduct();
}
public void setProduct(Product product) {
categoryProductXref.setProduct(product);
}
Part 3:
#Embeddable
public class CategoryProductXrefPK implements Serializable {
#ManyToOne(targetEntity = CategoryImpl.class, optional=false)
#JoinColumn(name = "CATEGORY_ID")
protected Category category = new CategoryImpl();
#ManyToOne(targetEntity = ProductImpl.class, optional=false)
#JoinColumn(name = "PRODUCT_ID")
protected Product product = new ProductImpl();
Problem was in ProductDao
public List<Product> readFilteredActiveProductsByCategory(Long categoryId, Date currentDate,
ProductSearchCriteria searchCriteria) {
// Set up the criteria query that specifies we want to return Products
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Product> criteria = builder.createQuery(Product.class);
// The root of our search is Category since we are browsing
Root<CategoryImpl> category = criteria.from(CategoryImpl.class);
// We want to filter on attributes from product and sku
Join<Category, Product> product = category.join("allProducts"); <--Problem Occurs Here
Join<Product, Sku> sku = product.join("defaultSku");
// Product objects are what we want back
criteria.select(product);
I've tried this:
Join<CategoryImpl, CategoryProductXrefImpl> catXref = category.join("allProductXrefs");
Join<CategoryProductXrefImpl, Product> product = catXref.join("product");
Join<Product, Sku> sku = product.join("defaultSku");
I've tried it by Implementation, tried with a third join like:
Join<CategoryProductXrefImpl, CategoryProductXrefPK>,
tried using CategoryProductXrefPK instead of XrefImpl, Tried using CategoryProductXref as a second Root. I've spent a few days on this searching and trying different things. I ran out of ideas.
I'm not being able to query a MongoDB document according to field values of an embedded Java Collection.
I have the following entity:
#Entity
public class StationHistoryEntry // extends ...
{
#Embedded
private Set<SongFeedback> songFeedback = new HashSet<SongFeedback>();
// ...
}
And the following embedded class:
#Embedded
public class SongFeedback // extends ...
{
#Embedded
private FeedbackType feedbackType;
private ObjectId userId;
public enum FeedbackType {
THUMBS_UP, THUMBS_DOWN, STAR;
}
// other properties
}
What I need to do is to find StationHistoryEntries that have SongFeedback with a given userId and feedbackType=STAR.
I've tried the following but didn't succeed when the other SongFeedback properties (the ones not shown on the code snippet because I don't have control over their values) were not null, which happens in production:
public List<StationHistoryEntry> findStarredByUserId(ObjectId userId) {
SongFeedback songFeedback = new SongFeedback(FeedbackType.STAR, userId);
return ds.find(StationHistoryEntry.class)
.filter("songFeedback elem", songFeedback).asList();
}
And I've also tried the following, but it always returns an empty list:
public List<StationHistoryEntry> findStarredByUserId(ObjectId userId) {
Query<StationHistoryEntry> query = ds.createQuery(StationHistoryEntry.class);
query.and(
query.criteria("songFeedback.userId").equal(userId),
query.criteria("songFeedback.feedbackType").equal(FeedbackType.STAR));
return query.asList();
}
If it helps at all, I've created a Github repository with the stripped down code and a unit test: https://github.com/gabrielcs/MorphiaQueryStackOverflow
Any ideas? Thanks!
try this
public List<StationHistoryEntry> findStarredByUserId(ObjectId userId) {
Query<StationHistoryEntry> query = ds.createQuery(StationHistoryEntry.class);
query.and(
query.criteria("songFeedback.userId").equal(userId),
query.criteria("songFeedback.feedbackType").in(FeedbackType.STAR));
return query.asList();
}