I'm trying to understand how to use Spring Data's Query by Example capabilities, and am struggling to understand how to use ExampleMatcher and its various with* methods. Classic examples of the matcher in use includes snippets like this:
Person person = new Person();
person.setFirstname("Dave");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths("lastname")
.withIncludeNullValues()
.withStringMatcherEnding();
Example<Person> example = Example.of(person, matcher);
For some reason, I just can't wrap my brain around this DSL. Let's take the Person example from the docs. Let's say a Person entity looks like this:
// Pseudo code; omitting JPA annotations for this example
public class Person {
private String firstName;
private String lastName;
private Date dob;
private Long score;
// Getters, setters & constructor omitted
}
Can anyone show me an example of how to construct an ExampleMatcher that would allow me to find Person records meeting the following criteria:
First name starts with "Sme"; and
Last name is less than 10 characters long; and
Date of birth is before 1970-01-01; and
Score is between 10 and 20, inclusively
If any of these criteria aren't possible with ExampleMatcher, that's fine, but can someone show me which ones are or explain what methods might get me close to what I'm looking for?
You can get records with firstName starting with "Sme" and score=50
Person person = new Person();
person.setFirstName("Sme");
person.setScore(50L);
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstName", startsWith())
.withMatcher("score", exact());
Example<History> example = Example.of(person, matcher);
personRepository.findAll(example)
Related
We have multiple tables like :
School one to many teacher
teacher one to many subject
teacher one to many classes
Entity are as follows
public class School {
private String name;
private long id;
private List<teacher> teachers;
public School() {
}
}
public class teachers {
private String name;
private Long id;
private List<Subject> subjects;
private List<Classes> classes;
}
public class Subject {
private String name;
private long id;
public Subject() {
}
}
public class Classes{
private String name;
private long id;
public Classes() {
}
}
we have written the jooq query for the required fields. For a single school data, we were getting multiple rows instead of one that was expected. However, We were unable to map the data.
We tried :
ModelMapper( Unable to find a way to covert multiple basically horizontal(table) records to vertical)
intoGroups() worked only till
single join(bw two tables)
simpleflatmapper same issue
Is there any way we can achieve it. Are we missing something?
PS: In response, We don't require all the columns(variable) from all the tables.
That's a tricky question for a school assignment, given that this has been, historically, one of jOOQ's most missing features :)
A jOOQ 3.15+ solution using MULTISET
In addition to the below SQL/XML or SQL/JSON based solution, jOOQ 3.15 now supports the standard SQL MULTISET value constructor operator as well as a synthetic MULTISET_AGG aggregate function, which can be used like this:
List<School> schools =
ctx.select(
SCHOOL.NAME,
SCHOOL.ID,
multisetAgg(
TEACHER.NAME,
TEACHER.ID,
multiset(
select(SUBJECT.NAME, SUBJECT.ID)
.from(SUBJECT)
.where(SUBJECT.TEACHER_ID.eq(TEACHER.ID))
).as("subjects").convertFrom(r -> r.map(Records.mapping(Subject::new))),
multiset(
select(CLASS.NAME, CLASS.ID)
.from(CLASS)
.where(CLASS.TEACHER_ID.eq(TEACHER.ID))
).as("classes").convertFrom(r -> r.map(Records.mapping(Classes::new)))
).as("teachers").convertFrom(r -> r.map(Records.mapping(Teachers::new)))
)
.from(SCHOOL)
.join(TEACHER).on(TEACHER.SCHOOL_ID.eq(SCHOOL.ID))
.groupBy(SCHOOL.NAME, SCHOOL.ID)
.fetch(Records.mapping(School::new));
The above approach using the various Records.mapping() overloads along with ad-hoc data type conversion assumes the presence of an immutable constructor, such as you'd get if your classes were Java 16 records:
record Subject (String name, long id) {}
A jOOQ 3.14+ solution using SQL/XML or SQL/JSON
Starting from jOOQ 3.14 and the new SQL/XML and SQL/JSON support, this will be possible relatively easily. In essence, you will be using your RDBMS's native XML or JSON support to nest collections directly in SQL. (All other approaches using joins and trying to deduplicate and shoe-horn flat result sets into nested data structures will not work well enough, as you've noticed)
You can write a query like this (assuming you use the code generator, and assuming you're interested in a tree structure with the School at the top):
List<School> schools =
ctx.select(jsonObject(
jsonEntry("name", SCHOOL.NAME),
jsonEntry("id", SCHOOL.ID),
jsonEntry("teachers", jsonArrayAgg(jsonObject(
jsonEntry("name", TEACHER.NAME),
jsonEntry("id", TEACHER.ID),
jsonEntry("subjects", field(
select(jsonArrayAgg(jsonObject(SUBJECT.NAME, SUBJECT.ID)))
.from(SUBJECT)
.where(SUBJECT.TEACHER_ID.eq(TEACHER.ID))
)),
jsonEntry("classes", field(
select(jsonArrayAgg(jsonObject(CLASS.NAME, CLASS.ID)))
.from(CLASS)
.where(CLASS.TEACHER_ID.eq(TEACHER.ID))
))
)))
))
.from(SCHOOL)
.join(TEACHER).on(TEACHER.SCHOOL_ID.eq(SCHOOL.ID))
.groupBy(SCHOOL.NAME, SCHOOL.ID)
.fetchInto(School.class);
This solution is based on assumptions of your schema, namely that there is a to-one relationship between both SUBJECT -> TEACHER and CLASS -> TEACHER.
Also, you can see I've still used a join to group TEACHER per SCHOOL, aggregating the teachers using JSON_ARRAYAGG(). That's one option, another correlated subquery as for the SUBJECT and CLASS queries would have been possible as well.
A simpler solution might be possible using SQL Server's FOR JSON clause, which can be emulated in other dialects.
I've an entity defined like below. If I use save() Hibernate does not create a new index for newly created entity. Updating/modifying an existing entity works well and as expected.
I'm using kotling with spring boot 2.
#Entity(name = "shipment")
#Indexed
data class Shipment(
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = -1,
#JoinColumn(name = "user") #ManyToOne() var user: User?,
#IndexedEmbedded
#JoinColumn(name = "sender") #ManyToOne(cascade = [CascadeType.ALL]) val sender: Contact,
#IndexedEmbedded
#JoinColumn(name = "sender_information") #ManyToOne(cascade = [CascadeType.ALL]) val senderInformation: ShipmentInformation,
) {}
Save function, I'm using this same function to update my entity and index is updated if index exists.
#Transactional
fun save(user: User, shipment: Shipment): Shipment {
shipment.user = user;
return this.shipmentRepository.save(shipment)
}
application.properties
spring.jpa.properties.hibernate.search.default.directory_provider=filesystem
spring.jpa.properties.hibernate.search.default.indexBase=./lucene/
spring.jpa.open-in-view=false
If I restart the server, indexing manually works too.
#Transactional
override fun onApplicationEvent(event: ApplicationReadyEvent) {
val fullTextEntityManager: FullTextEntityManager = Search.getFullTextEntityManager(entityManager)
fullTextEntityManager.createIndexer().purgeAllOnStart(true)
fullTextEntityManager.createIndexer().optimizeAfterPurge(true)
fullTextEntityManager.createIndexer().batchSizeToLoadObjects(15)
fullTextEntityManager.createIndexer().cacheMode(CacheMode.IGNORE)
fullTextEntityManager.createIndexer().threadsToLoadObjects(2)
fullTextEntityManager.createIndexer().typesToIndexInParallel(2)
fullTextEntityManager.createIndexer().startAndWait()
return
}
I tried to force to use JPA transaction manager but It did not help me.
#Bean(name = arrayOf("transactionManager"))
#Primary
fun transactionManager(#Autowired entityManagerFactory: EntityManagerFactory): org.springframework.orm.jpa.JpaTransactionManager {
return JpaTransactionManager(entityManagerFactory)
}
Update
I think I found why I don't get the results of newly inserted entities.
My search query has a condition on "pid" field which is declared:
#Field(index = Index.YES, analyze = Analyze.NO, store = Store.NO)
#SortableField
#Column(name = "id", updatable = false, insertable = false)
#JsonIgnore
#NumericField val pid: Long,
and query:
query.must(queryBuilder.keyword().onField("customer.pid").matching(user.customer.id.toString()).createQuery())
pid is not stored and so newly inserted values are not visible. Can this be the cause?
BTW: How can I query/search by nested indexed document id? In my case it is customer.id which is DocumentId. I've tried to change the query like below but don't get any result, should I create a new field to query?
query.must(queryBuilder.keyword().onField("customer.id").matching(user.customer.id.toString()).createQuery())
Update 2
I found a solution and now getting the newly inserted datas too. There was an error with definition of "pid" field and I've defined my Fields as below and it works as expected.
#Fields(
Field(name = "pid", index = Index.YES, analyze = Analyze.YES, store = Store.NO)
)
#SortableField(forField = "pid")
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long?,
Can we search and sort by id in an easy way or is it the best practice? I know that we should use native JPA functions to get results by id but in my case I need to search by an embedded id to restrict search results. (depends on role of user) so therefore it is not an option for me.
And I don't understand why manual indexing works...
BTW: How can I query/search by nested indexed document id? In my case it is customer.id which is DocumentId. I've tried to change the query like below but don't get any result, should I create a new field to query?
Normally you don't need to create a separate field if all you want is to perform an exact match.
Can we search and sort by id in an easy way
Searching, yes, at least in Hibernate Search 5.
Sorting, no: you need a dedicated field.
or is it the best practice?
The best practice is to declare a field alongside your #DocumentId if you need anything more complex than an exact match on the ID.
I know that we should use native JPA functions to get results by id
I'm not sure I understand what you mean by "native JPA functions".
but in my case I need to search by an embedded id to restrict search results. (depends on role of user)
Yes, this should work. That is, it should work if the id is properly populated.
And I don't understand why manual indexing works...
Neither do I, but I suppose the explanation lies in the "error in the definition of "pid" field". Maybe the ID wasn't populated properly in some cases, leading to the entity being considered as deleted by Hibernate Search?
If you need me to give you a definitive answer, the best way to get it would be to create a reproducer. You can use this as a template: https://github.com/hibernate/hibernate-test-case-templates/tree/master/search
This looks odd:
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = -1,
I'd expect a nullable long, initialized to null (or whatever is the Kotlin equivalent).
I'm not sure this is the problem, but I imagine it could be, as a non-null ID is generally only expected from an already persisted entity.
Other than that, I think you're on the right track: if mass indexing works but not automatic indexing, it may have something to do with your changes not being executed in database transactions.
I write a queries in jdbcTemplate to create reports, Now I want to add filter to the reports. for example if i have query to create report of all contacts per day, now I want to filter it that be just between two dates not all
What the best way to do it ?
There is a special way to do it in Spring jdbcTemplate?
See following tutorial and example code taken from that one below.
public Person select(String name){
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("name", name);
String selectAllSql = "SELECT * FROM PERSON where name = :name";
List<Person> persons = getJdbcTemplate().query(selectAllSql, new PersonRowMapper(),parameters);
return persons.get(0);
}
}
To answer your comment:
If some times i dont use the filter what i do with the parmeters what i have to send
You have two options
1) You will need to use if statements to construct your sql and parameters.
2) Use another library for this purpose. I used ElSql in production before.
My query parameter is like this:
q=name:abc+age:20+roleid:(23|45)|audeince:(23|24).Here + is for AND | is for OR
I have to accept this query param as it is into my spring controller and have to make query to solar to fetch the data.

#Controller
#RequestMapping("/user")
public class BooksController {
#RequestMapping(value="/details", method=RequestMethod.GET)
public ResponseEntity<?> getUser(final HttpServletRequest request) {
String params = requestParams.get("q")[0];
//passing this string to make query in apache solar
}
}
I need to write a parser to split the param value to make a solar query.how to write a query parser to split the above url to make solar query satisfying OR AND condition.name:abc+age:20+roleid:(23|45)|audeince:(23|24) means create a solar query where name=abc and age=20 and roleid in (23,24) or audience in (23,24) .This way user sends query.
Eg:firstName:(abc|bcd)+lastName:abc+emailId:abc+dsID:abc|countryCd:US+audienceId:(123+678)
first using regex convert like this
firstName:(abc|bcd)+ -----------segment1
lastName:abc+.............segment2
emailId:abc+.............segment3
dsID:abc|--------------segment4
countryCd:US+----------segment5
audienceId:(123+678)------segment 6;
like many segments may come in the url
i have a class called
class Queryobj{
private String field;
private List value;
private String internalOperator;
private String externalOperator;
}
firstName:(abc|bcd)+ again using regex map Like this
field=firstName
value={abc,bcd}
internalOperator=|
externalOperator=+
like second segment emailId:abc+
field=emailId
value=abc
internalOperator=null
externalOperator=+
same for other segments also .if there are n segments we have n objects.
After that add each object to Linked List.if internalOperator or externalOperator is null then leave it as null .How can I achieve that
You can use this regex pattern to get "key:value operator" segment
Pattern keyValuePattern = Pattern.compile("[\\w]+:([\\w#.]+|\\([\\w|+#.]+\\))[+|]?");
In our application we have such a case:
Constraints should be evaluated in particular order. (cheap to expensive)
Constraints should not be evaluated after a violation per field.
All fields should be validated.
For first two, groupsequence is fitting very good. However for my 3rd requirement I could not find a way to solve.
public class AccountBean {
#CheepValidation
#ExpensiveValidation
#VeryExpensiveValidation
private String name;
#CheepValidation
#ExpensiveValidation
#VeryExpensiveValidation
private String surname
}
For example,
Let's say that, for name field VeryExpensiveValidationconstraint is violated and for surname field ExpensiveValidation constraint is violated.
For this case I should display:
For field name: Only VeryExpensiveValidation error message
For field surname: Only ExpensiveValidation error message
Note that for field surname we did not evaluate VeryExpensiveValidation constraint.
Is there a way to implement it with JSR 303?
Thanks
You can use groups and #GroupSequence, but it's a bit unwieldy.
public class AccountBean {
#CheapValidation(groups=Name1.class)
#ExpensiveValidation(groups=Name2.class)
#VeryExpensiveValidation(groups=Name3.class)
String name;
#CheapValidation(groups=Surname1.class)
#ExpensiveValidation(groups=Surname2.class)
#VeryExpensiveValidation(groups=Surname3.class)
String surname;
public interface Name1 {}
public interface Name2 {}
public interface Name3 {}
#GroupSequence({Name1.class, Name2.class, Name3.class})
public interface Name {}
public interface Surname1 {}
public interface Surname2 {}
public interface Surname3 {}
#GroupSequence({Surname1.class, Surname2.class, Surname3.class})
public interface Surname {}
}
Then validate with:
validator.validate(myAccountBean,
AccountBean.Name.class, AccountBean.Surname.class)
The key is to have two entirely independent group sequences.
Unfortunately, it seems you must explicitly list the groups for all the fields you want to validate. I wasn't able to get it working with a 'default' #GroupSequence. Can anyone improve on this?
I've implemented ordered validation with GroupSequence but, generally speaking, GroupSequence beans validation implementation is not transparent.
Meaning, untill first group is fully validated, you can not trigger the validation of the second group.
E.g.
I have 3 validated fields with custom validators. The idea is pretty straightforward: every field should be validated with the set of validators from top to bottom independently (descending cardinality).
#StringPropertyNotNullOrEmptyConstraint(message = "Group name is required", groups = {ValidationStep1.class})
private final StringProperty groupName;
#StringPropertyNotNullOrEmptyConstraint(message = "Group password is required", groups = {ValidationStep1.class})
#StringPropertyMatchConstraint(message = "The given password phrases do not match", dependentProperties = {"groupPasswordMatch"}, groups = {ValidationStep2.class})
private final StringProperty groupPassword;
#StringPropertyNotNullOrEmptyConstraint(message = "Group password match is required", groups = {ValidationStep1.class})
#StringPropertyMatchConstraint(message = "The given passwords phrases do not match", dependentProperties = {"groupPassword"}, groups = {ValidationStep2.class})
private final StringProperty groupPasswordMatch;
public interface ValidationStep1 {
}
public interface ValidationStep2 {
}
#GroupSequence({GroupDialogModel.class, ValidationStep1.class, ValidationStep2.class})
public interface GroupDialogModelValidationSequence {
}
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
Set<ConstraintViolation<GroupDialogModel>> constraintViolations = validator.validate(this, GroupDialogModelValidationSequence.class);
The caveat of this approach is that each field should go through ValidationStep1 first and only after each validation of step 1 succeeds it goes to step 2. For example, even if password fields are not empty, but contain different values, validation for them succeeds if group name field does not contain any value. And only after I enter some value to the group name, ValidationStep1 group succeeds and then it displays validation result of ValidationStep2 (passwords do not match).
Making each group for each field in every sequence is bad practice IMHO, but it seems like there is no other choice.
Any other solution is much appreciated.