JPA query to retrieve data using list of matching tuples as arguments [duplicate] - sql

I have an Entity Class like this:
#Entity
#Table(name = "CUSTOMER")
class Customer{
#Id
#Column(name = "Id")
Long id;
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
How to write findBy method for the below query using crudrepository spring data jpa?
select * from customer where (email, mobile) IN (("a#b.c","8971"), ("e#f.g", "8888"))
I'm expecting something like
List<Customer> findByEmailMobileIn(List<Tuple> tuples);
I want to get the list of customers from given pairs

I think this can be done with org.springframework.data.jpa.domain.Specification. You can pass a list of your tuples and proceed them this way (don't care that Tuple is not an entity, but you need to define this class):
public class CustomerSpecification implements Specification<Customer> {
// names of the fields in your Customer entity
private static final String CONST_EMAIL_ID = "emailId";
private static final String CONST_MOBILE = "mobile";
private List<MyTuple> tuples;
public ClaimSpecification(List<MyTuple> tuples) {
this.tuples = tuples;
}
#Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// will be connected with logical OR
List<Predicate> predicates = new ArrayList<>();
tuples.forEach(tuple -> {
List<Predicate> innerPredicates = new ArrayList<>();
if (tuple.getEmail() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_EMAIL_ID), tuple.getEmail()));
}
if (tuple.getMobile() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_MOBILE), tuple.getMobile()));
}
// these predicates match a tuple, hence joined with AND
predicates.add(andTogether(innerPredicates, cb));
});
return orTogether(predicates, cb);
}
private Predicate orTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.or(predicates.toArray(new Predicate[0]));
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
}
Your repo is supposed to extend interface JpaSpecificationExecutor<Customer>.
Then construct a specification with a list of tuples and pass it to the method customerRepo.findAll(Specification<Customer>) - it returns a list of customers.

It is maybe cleaner using a projection :
#Entity
#Table(name = "CUSTOMER")
class CustomerQueryData {
#Id
#Column(name = "Id")
Long id;
#OneToOne
#JoinColumns(#JoinColumn(name = "emailId"), #JoinColumn(name = "mobile"))
Contact contact;
}
The Contact Entity :
#Entity
#Table(name = "CUSTOMER")
class Contact{
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
After specifying the entities, the repo :
CustomerJpaProjection extends Repository<CustomerQueryData, Long>, QueryDslPredicateExecutor<CustomerQueryData> {
#Override
List<CustomerQueryData> findAll(Predicate predicate);
}
And the repo call :
ArrayList<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("a#b.c","8971"));
contacts.add(new Contact("e#f.g", "8888"));
customerJpaProjection.findAll(QCustomerQueryData.customerQueryData.contact.in(contacts));
Not tested code.

Related

Query Many-to-many jpa

I need to implement SELECT with a many-to-many relationship in #QUERY. Perhaps I am misinterpreting the information written in the documentation.
My query looks like this:
#Query("select massages.id from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1")
List<MasageEntity> findMassagesIdByStringId(#Param("strings_id") long strings_id);
In my example, I use table names. The names are underlined as an error (without compilation). Maybe I should use Entities. Then how do I do it with many-to-many relationship?
I will show a part of my Entities.
I have two Entities. MasageEntity and RstringEntity.
//MasageEntity
#Entity
#Table(name = "massages")
public class MasageEntity {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "string_text")
private String string_text;
#Column(name = "string_speed")
private Long string_speed;
#Column(name = "string_color_type")
private Long string_color_type;
#Column(name = "string_color")
private String string_color;
#Column(name = "string_timing_type")
private String string_timing_type;
#Column(name = "string_timing")
private String string_timing;
#Column(name = "showed")
private Long showed;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "string_massage",
joinColumns = { #JoinColumn(name = "massage_id") },
inverseJoinColumns = { #JoinColumn(name = "string_id") })
//RstringEntity
#Entity
#Table(name = "string")
public class RstringsEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name="code")
private String code;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, mappedBy = "strings")
#JsonIgnore
private Set<MasageEntity> masagess = new HashSet<>();
public RstringsEntity() {}
There are multiple ways how to perform queries in Spring Boot: native SQL and JPQL.
In case of native queries we are using pure SQL language, defining query on DB level.
In case of Java Persistence Query Language (JPQL) we define query via entity objects.
Solution 1, native queries
You created the native query in repository, but to use it we need mark it like SQL nativeQuery = true. It required for framework to understang what query laguage do you use. #Query annotation use JPQL by defult, so that's the reason of your errors.
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID via native query
#Query(value = "select massages.* from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1", nativeQuery = true)
List<MassageEntity> findMassagesByStringIdNativeSQL(#Param("strings_id") long strings_id);
//find Message IDs by String ID via native query
#Query(value = "select massages.id from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1", nativeQuery = true)
List<Long> findMassagesIdByStringIdNativeSQL(#Param("strings_id") long strings_id);
}
Solution 2, JPQL queries
Example how to define JPQL queries for your case. JPQL will be translated to SQL during execution.
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID via JPQL
#Query("select message from MassageEntity message join message.strings string where string.id = :strings_id")
List<MassageEntity> findMassagesByStringIdJPQL(#Param("strings_id") long strings_id);
//find Message IDs by String ID via JPQL
#Query("select message.id from MassageEntity message join message.strings string where string.id = :strings_id")
List<Long> findMassagesIDByStringIdJPQL(#Param("strings_id") long strings_id);
}
Native query generated by Hibernate:
select
massageent0_.id as id1_3_,
massageent0_.string_text as string_t2_3_
from
massages massageent0_
inner join
string_massage strings1_
on massageent0_.id=strings1_.massage_id
inner join
string rstringsen2_
on strings1_.string_id=rstringsen2_.id
where
rstringsen2_.id=?
Solution 3, Spring auto-generated queries
Spring can auto-generate queries by repository method definition.
Example for your case:
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID
List<MassageEntity> findByStrings_Id(#Param("id") long strings_id);
}
Entries which I used fro sulutions:
#Entity
#Table(name = "massages")
public class MassageEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "string_text")
private String string_text;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "string_massage",
joinColumns = { #JoinColumn(name = "massage_id") },
inverseJoinColumns = { #JoinColumn(name = "string_id") })
private Set<RstringsEntity> strings = new HashSet<>();
}
#Entity
#Table(name = "string")
public class RstringsEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "code")
private String code;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, mappedBy = "strings")
#JsonIgnore
private Set<MassageEntity> massages = new HashSet<>();
}

Android room compiler error when trying to return a list of own objects in the DAO: incompatible types: <null> cannot be converted to int

I am using room for database operations. I have a class TableQuestion which holds a string and a id and I have a class TableAnswer which holds a string, an id plus the id of the question where it is refering to. QuizTask brings together the Question with all its answers. The query getQuestionsWithAnswer should return a QuizTask which wraps the question with all its answers. The error metioned in the title happens in auto-generated code of room.
The relevant part of the interface:
#android.arch.persistence.room.Dao
public interface dbDao {
#Transaction
#Query("SELECT table_question.question, table_answer.answer FROM table_question, table_answer WHERE table_question.id = table_answer.id_question")
LiveData<List<QuizTask>> getQuestionsWithAnswers();
}
Class TableQuestion:
#Entity(foreignKeys = #ForeignKey(entity = TableQuestionnaire.class,
parentColumns = "id",
childColumns = "id_questionnaire",
onDelete = CASCADE),
tableName = "table_question")
public class TableQuestion {
#PrimaryKey
public final int id;
#NonNull
public String question;
#NonNull
public int id_questionnaire;
public String subject;
public String category;
public String sub_category;
#Ignore
public String questionnaire;
public TableQuestion(int id, #NonNull String question, int id_questionnaire, String subject, String category, String sub_category) {
this.id = id;
this.question = question;
this.id_questionnaire = id_questionnaire;
this.questionnaire = null;
this.subject = subject;
this.category = category;
this.sub_category = sub_category;
}
public void setQuestionnaire(String questionnaire){
this.questionnaire = questionnaire;
}
}
Class TableAnswer:
#Entity(foreignKeys = #ForeignKey(entity = TableQuestion.class,
parentColumns = "id",
childColumns = "id_question",
onDelete = CASCADE),
tableName = "table_answer")
public class TableAnswer {
#PrimaryKey
public final int id;
#NonNull
public String answer;
#NonNull
public final int id_question;
public boolean rightAnswer;
public TableAnswer(int id, String answer, int id_question, boolean rightAnswer) {
this.id = id;
this.answer = answer;
this.id_question = id_question;
this.rightAnswer = rightAnswer;
}
}
Class QuizTask:
public class QuizTask {
#Embedded
private TableQuestion question;
#Relation(parentColumn = "id", entityColumn = "id_question")
private List<TableAnswer> answers;
public void setQuestion(TableQuestion question){ this.question = question; }
public TableQuestion getQuestion(){
return question;
}
public void setAnswers(List<TableAnswer> answers) { this.answers = answers; }
public List<TableAnswer> getAnswers() {
return answers;
}
}
AndroidStudio doesn't show any error upon compilation. When room auto-generates the code for getQuestionWithAnswers it shows a compiler error "incompatible types: cannot be converted to int". In the auto-generated dbDao_Impl.java is a row where a TableQuestion object is tried to create but with null for the id parameter. That's where the error occurs. What do I have to change?
I found the issue:
#Query("SELECT table_question.question, table_answer.answer FROM table_question, table_answer WHERE table_question.id = table_answer.id_question")
There is no id selected but used later on. That's why room tries to create objects with Id = null which is not possible. So simply change to:
#Query("SELECT * FROM table_question, table_answer WHERE table_question.id = table_answer.id_question")

Spring Data JPA - Many to many query

I have two entities Person and Movie.
#Entity
public class Person {
..some fields
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "actors")
#OrderBy("id")
private Set<Movie> moviesActor = new TreeSet<>();
}
#Entity
public class Movie {
..fields
#JoinTable(name = "actor_movie",
joinColumns = { #JoinColumn(name = "movie_id") },
inverseJoinColumns = { #JoinColumn(name = "actor_id") })
private Set<Person> actors = new TreeSet<>();
}
There is many to many relationship so there is new table actor_movie to keep it. And how can I get every person that has any movie in its set? So what I want is to achieve is get every person that exists in actor_movie table. I tried used Spring data jpa but couldn't find right query.
Best Practices in entity relations:
Always use fetch = FetchType.LAZY.
When you want to fetch another side of the relation too, use JOIN FETCH Query.This resolves LazyInitializationException of hibernate also.
Always use spring.jpa.open-in-view=false
Example:
By Spring Data JPA with Hibernate as JPA Provider.
Entities:
public class Blog{
...
#ManyToMany(fetch = FetchType.LAZY) //default is LAZY in ManyToMany
#JoinTable(name="blog_tag",
joinColumns = #JoinColumn(name = "blog_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id"))
#OrderBy //order by tag id
private Set<Tag> tags = new HashSet<>();
//2 utility methods in owner side
public void addTag(Tag tag){
tags.add(tag);
tag.getBlogs().add(this);
}
public void removeTag(Tag tag){
tags.remove(tag);
tag.getBlogs().remove(this);
}
//override equals & hashcode
}
public class Tag {
...
#ManyToMany(mappedBy = "tags")
private Set<Blog> blogs = new HashSet<>();
//override equals & hashcode
}
Now suppose, you want to fetch a Blog containing Tag items:
Repository:
#Repository
public interface BlogRepository extends JpaRepository<Blog, Long> {
#Query("select b from Blog b join fetch b.tags where b.name = :name")
Blog getBlog(#Param("name") String blogName);
}
service:
public interface BlogService {
Blog getBlog(String blogName);
}
#Service
public class BlogServiceImpl implements BlogService{
#Autowired
private BlogRepository blogRepository;
#Override
public Blog getBlog(String blogName) {
return blogRepository.getBlog(blogName);
}
}
You only need a single JOIN between Person and Movie. As Hibernate abstracts the existence of the middle table, you don't need to worry about it.
So, with Spring Data Repository:
class PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByMoviesActor();
}
With Jpql:
SELECT person FROM Person person JOIN person.moviesActor movie
Since you are using Fetch type lazy, you need to use join fetch to get moviesActor.
You can use jpql with spring data. I have not tested the queries below, but should work.
public interface PersonRepository extends JpaRepository<Person, Long> { //Long if Person.id is of type Long
#Query("SELECT p FROM Person p LEFT JOIN FETCH p.moviesActor WHERE size(p.moviesActor) > 0");
List<Person> findActors1();
// Or
#Query("SELECT p FROM Person p JOIN FETCH p.moviesActor");
List<Person> findActors2();
}
More about jpql size() operator here: https://www.thoughts-on-java.org/jpql/
You can use join directely :
#Query("SELECT p FROM Person p JOIN p.moviesActor movie");
List findPersonHasMovie();

Persistence Entity must not be null

with following code I am getting error Persistence entity must not be null. what could be the mistake.
public interface DistrictRepo extends PagingAndSortingRepository<District, Integer> {
#Query(
"select d.districtId, d.districtName from District d where d.districtId in (:districtIds) group by d.districtId"
)
#RestResource(path="byList")
List<Object[]> byList(#Param("districtIds") List<Integer> districtIds);
}
If you are going to make a "search" method, use this approach:
#Projection(name = "idAndName", types = {District.class})
public interface IdAndName {
Integer getId();
String getName();
}
#RestResource(path="byIds", rel="byIds")
#Query("select d from District d where d.districtId in (:districtIds)")
List<District> findByIds(#Param("ids") Integer... ids);
Then use this url:
http://localhost:8080/api/districts/search/byIds?ids=1,2,3&projection=idAndName
More info about projection
If you need to use complex queries with grouping and aggregation that return DTOs you can not use "search" methods. Instead you have to implement custom controller, for example:
#RepositoryRestController
#RequestMapping("/districts")
public class DistrictController {
#Autoware
private DistrictRepo repo;
#GetMapping("/report")
public ResponseEntity<?> report(#RequestParam(value = "ids") Integer... ids) {
List<Dto> dtos = repo.getDtosByIds(ids);
return ResponseEntity.ok(new Resources<>(dtos));
}
}
Where Dto is something like this:
#Data // This is Lombok annotation (https://projectlombok.org/)
#Relation(value = "district", collectionRelation = "districts")
public class Dto {
private final Integer id;
private final String name;
}
And something like this the repo method:
public interface DistrictRepo extends PagingAndSortingRepository<District, Integer> {
#Query("select new ...Dto(d.districtId, d.districtName) from District d where d.districtId in (:districtIds) group by d.districtId")
#RestResource(path="byList", rel="byList")
List<Dto> getDtosByIds(#Param("ids") Integer... ids);
}
More info.

How to configure Hibernate Search to find words with accents

I need to implement a global search, on a website which I am implementing using Spring (4.0.2)/Hibernate(4.3.1)/MySQL. I have decided to use Hibernate Search(4.5.0) for this.
This seems to be working fine, but only when I do a search for an exact pattern.
Imagine I have the following text on an indexed field:
"A história do Capuchinho e do Lobo Mau"
1) If I search for "história" or "lobo mau", the query will retrieve the corresponding indexed entity, as I would have expected.
2) If I search for "historia" or "lobos maus" the search will not retrieve the entity.
As far as I have read, it should be possible to configure Hibernate Search to perform a much smarter search than this. Can anyone point me on the right direction to achieve this? See below key aspects of the implementation I executed. Thanks!
This is the "parent" indexed entity
#Entity
#Table(name="NEWS_HEADER")
#Indexed
public class NewsHeader implements Serializable {
static final long serialVersionUID = 20140301L;
private int id;
private String articleHeader;
private String language;
private Set<NewsParagraph> paragraphs = new HashSet<NewsParagraph>();
/**
* #return the id
*/
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.AUTO)
#DocumentId
public int getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(int id) {
this.id = id;
}
/**
* #return the articleHeader
*/
#Column(name="ARTICLE_HEADER")
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
public String getArticleHeader() {
return articleHeader;
}
/**
* #param articleHeader the articleHeader to set
*/
public void setArticleHeader(String articleHeader) {
this.articleHeader = articleHeader;
}
/**
* #return the language
*/
#Column(name="LANGUAGE")
public String getLanguage() {
return language;
}
/**
* #param language the language to set
*/
public void setLanguage(String language) {
this.language = language;
}
/**
* #return the paragraphs
*/
#OneToMany(mappedBy="newsHeader", fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#IndexedEmbedded
public Set<NewsParagraph> getParagraphs() {
return paragraphs;
}
// Other standard getters/setters go here
And this the IndexedEmbedded entity
#Entity
#Table(name="NEWS_PARAGRAPH")
public class NewsParagraph implements Serializable {
static final long serialVersionUID = 20140302L;
private int id;
private String content;
private NewsHeader newsHeader;
/**
* #return the id
*/
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.AUTO)
public int getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(int id) {
this.id = id;
}
/**
* #return the content
*/
#Column(name="CONTENT")
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
public String getContent() {
return content;
}
// Other standard getters/setters go here
This is my search method, implemented on my SearchDAOImpl
public class SearchDAOImpl extends DAOBasics implements SearchDAO {
...
public List<NewsHeader> searchParagraph(String patternStr) {
Session session = null;
Transaction tx;
List<NewsHeader> result = null;
try {
session = sessionFactory.getCurrentSession();
FullTextSession fullTextSession = Search.getFullTextSession(session);
tx = fullTextSession.beginTransaction();
// Create native Lucene query using the query DSL
QueryBuilder queryBuilder = fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity(NewsHeader.class).get();
org.apache.lucene.search.Query luceneSearchQuery = queryBuilder
.keyword()
.onFields("articleHeader", "paragraphs.content")
.matching(patternStr)
.createQuery();
// Wrap Lucene query in a org.hibernate.Query
org.hibernate.Query hibernateQuery =
fullTextSession.createFullTextQuery(luceneSearchQuery, NewsHeader.class, NewsParagraph.class);
// Execute search
result = hibernateQuery.list();
} catch (Exception xcp) {
logger.error(xcp);
} finally {
if ((session != null) && (session.isOpen())) {
session.close();
}
}
return result;
}
...
}
This is what I have ended up doing, to resolve my problem.
Configure an AnalyzerDef at the entity level. Within it, use LowerCaseFilterFactory, ASCIIFoldingFilterFactory and SnowballPorterFilterFactory to achieve the type of filtering I needed.
#AnalyzerDef(name = "customAnalyzer",
tokenizer = #TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
#TokenFilterDef(factory = LowerCaseFilterFactory.class),
#TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
#TokenFilterDef(factory = SnowballPorterFilterFactory.class)
})
public class NewsHeader implements Serializable {
...
}
Add this notation for each of the fields I want indexed, either in the Parent entity or its IndexedEmbedded counterpart, to use the defined above analyzer.
#Field(index=Index.YES, store=Store.NO)
#Analyzer(definition = "customAnalyzer")
You will need to either re-index, or re-insert your entities, for the analyser to take effect.
You could configure, or you can use a standard language analyzer, such as PortugueseAnalyzer. I'd recommend starting from the existing analyzer, and creating you own if necessary, using it as a starting point for tweaking the filter chain.
You can set this in using the #Analyzer annotation for the field:
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO, analyzer = #Analyzer(impl = org.apache.lucene.analysis.pt.PortugueseAnalyzer.class))
Or you can set that analyzer as the default for the class, if you place an #analyzerannotation are the head of the class instead.
If you want to search accent character and also find same normal keyword in result then you must have to implement ASCIIFoldingFilterFactory class in analyzer like
#AnalyzerDef(name = "customAnalyzer",
tokenizer = #TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
#TokenFilterDef(factory = LowerCaseFilterFactory.class),
#TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
#TokenFilterDef(factory = StopFilterFactory.class, params = {
#Parameter(name="words", value= "com/ik/resource/stoplist.properties" ),
#Parameter(name="ignoreCase", value="true")
})
})
#Analyzer(definition = "customAnalyzer") apply on entity or fields