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.
Related
I created one class
class Employee { Integer id; String name; String departments; }
and in sql server database i have records
I stored departments as ";" separated. For Example Department = Computer;Civil
1,Chaitanya,Computer;Civil
2,Tom,Physics;Chemistry
3,Harry,Economics;Commerce
4,Henry,Computer;Civil;Mechanical
5,Ravi,null
Now i want to filter data with departments let's say there is one multiselect in frontend where i have list of departments and i select two departments for example-> Computer,Civil and in backend i got List<String> deparmentFilter as parameter say Computer;Civil
Now as per my requirement i have to return two data from Spring Boot Controller
1,Chaitanya,Computer;Civil
4,Henry,Computer;Civil;Mechanical
Right Now what i did is i executed the query to fetch all the records and then i right below logic
List<Employee> employeesToBeRemoved = new ArrayList<>();
if (!departmentNames.isEmpty()) {
allEmployees.forEach(employee -> {
if (employee.getDepartment() != null) {
Set<String> departmentNamesResult = new HashSet<>(Arrays.asList(employee.getDepartment().
split(";")));
Boolean isExist = Collections.disjoint(departmentNamesResult, departmentNames);
if (Boolean.TRUE.equals(isExist)) {
employeesToBeRemoved.add(employee);
}
} else {
employeesToBeRemoved.add(employee);
}
});
}
allEmployees.removeAll(employeesToBeRemoved);
I tried to move it to predicates but not able to do that, This solution is taking much time to execute,
Please suggest me some other better ways (optimized way) to improve performance.
Is there is any way to add this filter in predicates?
Another approach i am thinking (12/05/2022)
Let's say i have one table employee_department_mapping and in that table i have employeeId and departmentName so in this correct way to add predicate?
CriteriaQuery<Object> subQuery1 = criteriaBuilder.createQuery();
Root<EmployeeDepartmentMapping> subQueryEmpDptMp = subQuery1.from(EmployeeDepartmentMapping.class);
predicates1.add(subQueryEmpDptMp.get("departmentName").in(departmentNames));
You might achieve better performance by splitting your table and using join:
class Employee { Integer id; String name; Integer departmentsId; }
class EmployeeDepartments { Integer departmentsId; String department; }
You may use Element Collection to achieve this.
Now, instead of having a the following row:
1,Chaitanya,Computer;Civil
You will have the following:
table1:
1,Chaitanya,123
table2:
123,Compter
123,Civil
Execute a join to get all row from table2 with table1 to get your result
I´m new to implementing clean architecture although I really like the concept, however, when I think of repository implementation I'm always unsure.
For example: I always find diagrams like this
In these diagrams, the repository interfaces use the entities and the entities know nothing about anything. The thing is I think it may be more useful for entities to be conscious of repository interfaces instead. I think it wouldn't violate the inversion of control principle since they are just interfaces and not implementations.
an example (not real code or language because language is not important in this case):
Class StudentEntity:
important method: getMathGrade
Class MathClassroomEntity:
constructor(repository){
this.repo = repository
}
important method: getStudents(){
this.repo.getStudents()
}
important method: getAverageGrade(){
students = this.getStudents()
sum = 0
foreach student in students:
sum = student.getMathGrade()
return sum/students.length
}
As you can see, many important business logic in one entity is related to other entities.
if the entities don't know anything about repos (interfaces at least).
how can I put these methods in my entities?
should I make my entities abstract? which I think is not so pretty
should I put this business logic in my use cases? which sounds even worse
why do they make repo interfaces use entities and not the other way? what are the advantages?
I know this is a lot, so thanks a lot in advance
how can I put these methods in my entities?
You don't need to put these methods in your entities.
The use case queries the repository and the repository should return the MathClassroomEntity which should just contain the students.
class RepositoryImpl implements Repository {
public MathClassroom getMathClassroom(){
return new MathClassroom(getStudents);
}
private List<Student> getStudents(){
return .....;
}
}
Thus the MathClassroom will only know about students
public class MathClassroom {
private List<Student> students;
public MathClassroom(List<Student> students){
this.students = students;
}
public double getAverageGrade(){
double sum = 0;
for(Student student : students){
sum += student.getMathGrade()
}
return sum / students.size();
}
}
Easy to test and decoupled from the repository.
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)
I know how to retrieve a bean from a service in a datafetcher:
public class MyDataFetcher implements DataFetcher {
...
#Override
public Object get(DataFetchingEnvironment environment) {
return myService.getData();
}
}
But schemas with nested lists should use a BatchedExecutionStrategy and create batched DataFetchers with get() methods annotated #Batched (see graphql-java doc).
But where do I put my getData() call then?
///// Where to put this code?
List list = myService.getData();
/////
public class MyDataFetcher implements DataFetcher {
#Batched
public Object get(DataFetchingEnvironment environment) {
return list.get(environment.getIndex()); // where to get the index?
}
}
WARNING: The original BatchedExecutionStrategy has been deprecated and will get removed. The current preferred solution is the Data Loader library. Also, the entire execution engine is getting replaced in the future, and the new one will again support batching "natively". You can already use the new engine and the new BatchedExecutionStrategy (both in nextgen packages) but they have limited support for instrumentations. The answer below applies equally to both the legacy and the nextgen execution engine.
Look at it like this. Normal DataFetcherss receive a single object as source (DataFetchingEnvironment#getSource) and return a single object as a result. For example, if you had a query like:
{
user (name: "John") {
company {
revenue
}
}
Your company resolver (fetcher) would get a User object as source, and would be expected to somehow return a Company based on that e.g.
User owner = (User) environment.getSource();
Company company = companyService.findByOwner(owner);
return company;
Now, in the exact same scenario, if your DataFetcher was batched, and you used BatchedExecutionStrategy, instead of receiving a User and returning a Company, you'd receive a List<User> and would return a List<Company> instead.
E.g.
List<User> owners = (List<User>) environment.getSource();
List<Company> companies = companyService.findByOwners(owners);
return companies;
Notice that this means your underlying logic must have a way to fetch multiple things at once, otherwise it wouldn't be batched. So your myService.getData call would need to change, unless it can already fetch data for multiple source object in one go.
Also notice that batched resolution makes sense in nested queries only, as the top level resolver can already fetch a list of object, without the need for batching.
i separated my application into a DAL, BL, UI.
I used entity framework code first throw repositories to access the sql database.
public class Person{
...
}
public class PersonRepository{
Create(...){...}
Update(...){...}
Delete(...){...}
GetById(...){...}
Query(...){...}
...
Now the thing is the BL i'm working on a method to get all the Persons who are leaving near an adress
public GetPersonsNear(string Address){
...
}
private bool AddressesAreClose(string address1, string address2)
{
...
}
the thing is linq does'nt let me use my method (in a query passed in the "Query" method of the repository)
...
PersonRepository personRepository = new PersonRepository();
var person = repository.Query(p => AddressAreClose(adress,p.Adress);
...
therefor i needed to get All the elements of the table in a list using a simple foreach loop to make the tests and keeping only the relevant ones
...
PersonRepository personRepository = new PersonRepository();
var persons = personRepository.GetAll;
foreach(person in persons)
{
if(AdressAreClose(adress,person.adress))
...
}
for now i populated the database with only a few elements to test it, but i'm not sure it would work very well with the far more greater number it will contain later specially with all the test i'm planing to add
isn't there a more clever way to do this ??? I'm open to anything
Well first of all, you should use generics in your repository, even if it's constrained to Person. This way you can build pipes/filters off your queries to clean up your LINQ queries and facilitate reuse.
Of course, without seeing the full signature/implementation of your Query method, it's hard to tell. But either way, you need to return IEnumerable<Person> or IQueryable<Person> to make the following work.
So, you could turn AddressesAreClose into a pipe/filter, like this:
public static bool WhereAddressesAreClose(this IQueryable<Person> source, string address)
{
return source.Where(/* your conditions */);
}
Then you can use it in your LINQ query:
var person = repository
.Query() // Should be IQueryable<Person>
.WhereAddressAreClose(adress);
.ToList();
Depending on the size of your data and whether or not your implementing caching, you should limit the results on the server (database), not post-query with a foreach loop.
If the performance isn't great, consider adding indexes, using compiled queries or moving to a stored procedure.