Many to Many prepared query - ormlite

I am using ORMLite and I have this models (I have omitted id fields and visibility for readability):
class Book {
String title;
List<Author> authors;
}
class Author {
String name;
List<Book> books;
}
class BookAuthor {
#DatabaseField(foreign = true)
Author author;
#DatabaseField(foreign = true)
Book book;
}
I want a query with all books order_by title and with their authors in their list, so I have this methods, the first to resolve many-to-many and then to get books shorted:
private PreparedQuery<Author> makeAuthorsForBookQuery() throws SQLException {
QueryBuilder<BookAuthor, Integer> bookAuthorQb = bookAuthorDao.queryBuilder();
bookAuthorQb.selectColumns("author");
SelectArg userSelectArg = new SelectArg();
bookAuthorQb.where().eq("", userSelectArg);
QueryBuilder<Author, String> authorQb = authorDao.queryBuilder();
authorQb.where().in("name", bookAuthorQb);
return authorQb.prepare();
}
private PreparedQuery<Book> makeBooksQuery() throws SQLException {
QueryBuilder<Book,Integer> bookQb = bookDao.queryBuilder();
bookDao.orderBy(Manga.TITLE_COLUMN_NAME, true);
return bookDao.prepare();
But if I do to this way then I have to iterate over each book and to fire the AutorsForBookQuery and assign the result to the book list.
My question is: Does exist any other way to get this? I mean, something as to do an inner-join where map the authors in the books automagically and save it as a PreparedQuery?
PD: My goal is to load this prepared query in a CloseableIterator

I want a query with all books order_by title and with their authors in their list...
What I would do #Fran is to do 2 queries. One for Books ordered by title. Then another query for the authors joined with the BookAuthor class with book.id in the list of books you got. So it is 2 queries but then it is not another query by Book.
ORMLite isn't helping you much there for sure.

Related

Spring Data - MongoRepository - #Query to find item within nested list of objects

I'm trying to query MongoDB to return a single Answer object contained within a QuestionDocument object.
I am using Spring, MongoRepository, and JDK 11.
My QuestionDocument POJO:
#Data
#Document(collection = "Questions")
#AllArgsConstructor(onConstructor = #__(#Autowired))
#NoArgsConstructor
public class QuestionDocument {
#Id
private String questionId;
(...)
private List<Answer> answers;
(...)
}
My Answer POJO:
#Data
public class Answer implements Serializable {
private String answerId;
(...)
My QuestionRepository:
#Repository
public interface QuestionRepository extends MongoRepository<QuestionDocument, String> {
#Query(value = "{ { 'questionId' : ?0 }, { 'answers.$answerId' : ?1 } }")
Answer findByQuestionIdAndAnswerId(String questionId, String answerId);
My QuestionServiceImpl:
public getAnswer(String questionId, String answerId){
Answer answer = findByQuestionIdAndAnswerId(questionId, answerId);
return answer;
}
protected Answer findByQuestionIdAndAnswerId(String questionId, String answerId){
Answer answer;
try {
answer = questionRepository.findByQuestionIdAndAnswerId(questionId, answerId);
} catch (Exception e) {
throw new IllegalArgumentException("There is no answer with this ID.");
}
return answer;
}
When I hit my endpoint in Postman, the correct response body appears, but all of its values are null. I have verified that the correct questionId and answerId are passed in my parameters.
I have also consulted several additional SO posts and Spring and MongoDB documentation, but so far, implementing what I've read regarding traversing nested objects by property hasn't helped.
How does my #Query value need to change to properly return a specific Answer object from this nested list of answers?
I have attempted to create findBy methods like:
findByQuestion_Answers_AnswerId(String answerId);
I have attempted to add #DBRef above my List<Answer> answers, and adding #Document(collection = "Answers") and #Id above private String answerId; in my Answer POJO. I then cleared my database, created a new question and answer, and queried for the specific answerId, and still returned null data.
What I expect, is that given the questionId and answerId, the query will return one Answer object and its associated information (answerBody, answerAuthor, etc.).
My postman response states SUCCESS, but the data is null.
You can change the Query to this.
#Query(value = "{{'questionId' : ?0, 'answers.answerId' : ?1}}")
or, just define this method.
findByQuestionIdAndAnswerId(String questionId, String answerId);
The return type will be of QuestionDocument, not Answer.
More details here.

JPA query attribute with a converter

I have a SpringBoot 2.6.11 application with JPA 2.2.
I have an entity like this:
#Data
#Entity
#Table(name = "entity")
public class Entity implements Serializable {
....
#Convert(converter = ListConverter.class)
private List<String> referenceCode;
....
}
I have this Converter:
#Converter(autoApply = true)
public class ListConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> attribute) {
return String.join(";", attribute);
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
return new ArrayList<>(Arrays.asList(dbData.split(";")));
}
}
And when I insert or extract this element all working fine. But now I wanna query that element and I don't know how to do it. If I do something like that:
public List<Entity> findByReferenceCode(String reference);
It doesn't work, if I do:
#Query("select e from Entity e where e.referenceCode IN ?1")
public List<Entity> findByReferenceCode(List<String> reference);
Still doesn't work..
The only way I found is by the nativeQuery but is really an extrema ratio. Ho can I solve this?
Thank you
To really do what you want here, you need to use an #ElementCollection. The reason being that there is no reliable way for JPA to query a single column and treat it as a collection. Reliably querying a collection requires a second table (which is what #ElementCollection does). You can continue to use the #Converter, but your queries will have to be customized to handle the disparity between the entity attribute type (list) and the actual database column type (string).
If you are okay with the limitations of the #Converter then it's fine (I have used them this way) but if you truly need to query the attribute like a collection (e.g. search for multiple independent items, perform counts, aggregations, etc) and you want those queries to be generated by a JPA layer, then you will have to use #ElementCollection and let it create a second table.

Designing mongodb schema with searchable nested arrays.

I'm new to Mongodb coming from relational databases and I'd also like to point out I'm using SpringBoot with JPA. If I were to build an automotive classified site where I would have thousands of Users and 100s of thousands of listings, how would I go about setting up the schema? I've read some articles that say normalizing nosql data is bad practices.
Anyhow lets say we have the following structure.
User
id
name
email
Cars
id
make
model
year
I would need to be able to list many cars with the User and what i've seen in my examples is it creates a nested array of cars within User. This would work great for user accounts where I'd like to provide the user with all their cars
Where I get a bit confused is with the cars. The cars need to be able to be searched very quickly and would not need the user info right away. In a sql db I would typically do a search against the cars (year, make, model) and grab the user later on if I needed it.
In mongodb, do you create a User document that contains a nested car array? or do you somehow create 2 documents that are both automatically maintained and search against the car document for performance reasons?
Sample code
#Document(collection = "person")
public class Person {
#Id
private String id;
private String firstName;
private String lastName;
// #DBRef(lazy = true)
private List<Listing> listings;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
#Document(collection = "listing")
public class Listing {
#Id
public String id;
public String year;
public String make;
public String model;
public String trim;
public Listing(String year, String make, String model, String trim) {
this.year = year;
this.make = make;
this.model = model;
this.trim = trim;
}
}
#Override
public void run(String... args) throws Exception {
repository.deleteAll();
List<Listing> listings = new ArrayList<>();
Listing listing = new Listing("2008", "Ford", "Focus", "SE");
//listingRepository.save(listing);
listings.add(listing);
Person person = new Person("Alice", "Smith");
person.setListings(listings);
// save a couple of customers
repository.save(person);
person = new Person("Bob", "Smith");
listings = new ArrayList<>();
listings.add(new Listing("2018", "Chrysler", "300", "S"));
person.setListings(listings);
repository.save(person);
// fetch all customers
System.out.println("Customers found with findAll():");
System.out.println("-------------------------------");
for (Person _person : repository.findAll()) {
System.out.println(_person);
}
System.out.println();
// fetch an individual customer
System.out.println("Person found with findByFirstName('Alice'):");
System.out.println("--------------------------------");
System.out.println(repository.findByFirstName("Alice"));
System.out.println("Persons found with findByLastName('Smith'):");
System.out.println("--------------------------------");
for (Person _person : repository.findByLastName("Smith")) {
System.out.println(_person);
}
List<Listing> _listings = listingRepository.findAll();
System.out.println("listings " + _listings.size());
_listings.forEach(v -> {
System.out.println(v.toString());
});
}
Going by your entity model,I think what you are looking for is analogous to Many to Many/One to Many relationship in a relational database. So you can go for One way Embedding or Two way Embedding in MongoDb.
For One way embedding, You can create a Car collection like below:
db.carCollection.insertMany([{
_id:1,
make: 'porcha',
model:'qwerty',
year:'2018'
},
{
_id:2,
make: 'ferrara',
model:'uiop',
year:'2018'
}])
You can then go on to create user collection as below:
db.userCollection.insert({
_id:1,
user:'Tom',
email:'tom#tom.com',
car_ids:[1,2]
})
The car_ids is an array which will hold the ids of cars that belong to the user.
You can fetch the cars belonging to an user as(using findOne to fetch the user. Search parameter should be an unique id. I am considering email to be unique here.Ideally it should be user's id) :
var user=db.userCollection.findOne({email:'tom#tom.com'})
db.carCollection.find({_id:{$in:user.car_ids}})
This will fetch you all the cars per user
For fetching cars only you can simply do:
db.carCollection.find({})
For Two way embedding you can have similar array (as in user collection) inside cars collection so that each car can be identified to its user.

Searching by query for attributes of complex objects in Java EE

I have created the object Person, I can deleted and modify it and I can also search for Person by his name or phonenumber... but I don't know for exemple how to search for a person by his ** home address**. Here is my code:
My entity Person.java:
public class Person{
private Long id;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
private Address address;
....
}
My entity Address.java
public class Address{
...
private String streetName;
...
}
And here is the most interesting function that I am trying to modify to get what I want, I would like to search for Persons who live in xxx (streetName = xxx). Here is my function getByQuery:
public List<Person> getByQuery(PersonSearchQuery searchQuery) {
Map<String, String> criteriaQuery = new HashMap<String, String>();
if (searchQuery.getName() != null)
criteriaQuery.put("name",searchQuery.getName());
TypedQuery<Person> query = this.findByQuery(criteriaQuery);
return query.getResultList();
}
The object PersonSearchQuery contains just to attributes name (String) and streetName (String) and their getters.
Function findByQuery:
public TypedQuery<T> findByQuery(Map<String, String> criteriaQuery) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(this.entityClass);
Root<T> root = criteria.from(this.entityClass);
criteria.select(root);
Predicate predicate = builder.conjunction();
if (criteriaQuery.size() != 0) {
for (String key : criteriaQuery.keySet()) {
try{
predicate = builder.and(predicate, builder.equal(root.<String>get(key), criteriaQuery.get(key)));
}catch(IllegalArgumentException e){
continue;
}
}
}
criteria.where(predicate);
return this.em.createQuery(criteria);
}
So I can search for Persons by their names by I cannot search for them by streetName the problem is my function getByQuery I would like to do something like this:
if (searchQuery.getStreetName() != null)
criteriaQuery.put("Address.streetName",searchQuery.getStreetName());
The problem is I don't know how to define the key in this case. Thanks for your help
I only use CriteriaBuilder if I have several similar Entities which needs to be used/rendered in the same way, so if person is the only Entity with an Address reference I would just use JPQL, like this:
entityManager.createQuery(
"select p from Person p where p.address.streetName like :streetName", Person.class)
.setParameter("streetName", "xyz" + "%").getResultList()
The main reason I tend to avoid CriteriaBuilder, is because it has a rather steep learning curve, and you need to write a lot of code to express very simple concepts. In contrast any developer familiar with SQL can read and maintain JPQL code.
These days I always use frameworks, like DeltaSpike Data (for EE) and Spring Data, they both implements most of the basic DAO/Repository features, so If you don't mind an extra dependency (and some magic) it can save you a lot of boilerplate JPA code.

Morphia/MongoDB: Accessing "embedding" object from an #Embedded object

I have a Morphia schema similar to this one:
#Entity
class BlogEntry {
#Embedded
List<BlogComment> comments
}
#Embedded
class BlogComment {
String content
Long authorId
}
(code above just for illustration)
I'm trying to get a specific BlogComment in order to update it with new content. I have the corresponding BlogEntry object available, and I have the authorId, which let's say for the purposes of this question that these two together are sufficient to uniquely identify the correct BlogComment.
My question is, BlogComment does not explicitly contain a reference to its "parent" BlogEntry object, so how can I write a morphia query to retrieve this BlogComment? Something like:
//fetch the unique comment corresponding to this blog entry and this author ID.
BlogComment comment = ds.find(BlogComment.class, "blogEntryId =", blogEntry.id)
.filter("authorId", authorId)
.get();
Since you already have the blog entry object why not use a simple Java loop to filter it out?
#Entity
class BlogEntry {
#Embedded
List<BlogComment> comments
public BlogComment findCommentByAuthorId(String authorId) {
if (null == authorId) return null;
for (BlogComment comment: blogEntry.comments) {
if (authorId.equals(comment.authorId) return comment;
}
return null;
}
}