Skip to content

Commit 16aae1d

Browse files
author
talhadilber
committed
New Projection Developed: Auto Projection with Annotated Model
1 parent 085591f commit 16aae1d

File tree

8 files changed

+159
-7
lines changed

8 files changed

+159
-7
lines changed

README.md

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,11 @@ where authorizat4_.menu_icon like ?
447447

448448
### 8- Projection Examples
449449

450-
Spring Data projections always boring. But this project projection is very simple. When you want to use specific fields
451-
in the result, you can add selected fields on select list on `DynamicQuery` object. You can add multiple fields to the
450+
Spring Data projections always boring. But this project projections are very simple.
451+
There are two ways to use projections. I suggested using the second way. Because the second way is easier and more reusable.
452+
453+
#### A- Manual Projection
454+
When you want to use specific fields in the result, you can add selected fields on select list on `DynamicQuery` object. You can add multiple fields to the
452455
select clause. You can also use the `Pair` class to give an alias to the field.
453456

454457
Why we are using Pair class? Because we want to use the same field name in the select clause. But we want to use
@@ -494,6 +497,56 @@ where authorizat4_.menu_icon like ?
494497

495498
_Note: you can find the example on demo github repository._
496499

500+
501+
#### B- Auto Projection with Annotated Model
502+
Model Annotations: `@JdqModel`, `@JdqField`, `@JdqIgnoreField`
503+
504+
We are discovering select clause if model has `@JdqModel` annotation AND select clause is empty.
505+
Autofill Rules are Simple:
506+
- If field has `@JdqField` annotation, we are using this field name in the select clause.
507+
- If field has not any annotation, we are using field name in the select clause.
508+
- If field has `@JdqIgnoreField` annotation, we are ignoring this field in the select clause.
509+
510+
**Usage of `@JdqField` annotation:**
511+
512+
`@JdqField` annotation has a parameter. This parameter is a string. This string is a field name in the select clause. If you want to use different field name in the select clause, you can use this annotation. And also If you need to use joined column in the select clause, you can use this annotation.
513+
514+
_Examples:_
515+
516+
```java
517+
@JdqModel // This annotation is required for using projection with joined column
518+
@Data
519+
public static class UserJdqModel {
520+
@JdqField("name") // This annotation is not required. But if you want to use different field name in the result, you can use this annotation.
521+
private String nameButDifferentFieldName;
522+
@JdqField("user.name") // This annotation is required for using joined column in the projection
523+
private String userNameWithJoin;
524+
525+
private Integer age; // This field is in the select clause. Because this field has not any annotation.
526+
527+
@JdqIgnoreField // This annotation is required for ignoring this field in the select clause.
528+
private String surname;
529+
}
530+
531+
// USAGE EXAMPLE
532+
List<UserJdqModel> result = customerRepository.findAll(dynamicQuery, UserJdqModel.class);
533+
```
534+
_Autofilled select Result If you fill Manuel:_
535+
```java
536+
select.add(Pair.of("name", "nameButDifferentFieldName"));
537+
select.add(Pair.of("user.name", "userNameWithJoin"));
538+
select.add(Pair.of("age", "age"));
539+
```
540+
541+
_Hibernate Query:_
542+
543+
```sql
544+
select customer0_.name as col_0_0_, user1_.name as col_1_0_, customer0_.age as col_2_0_
545+
from customer customer0_
546+
inner join test_user user1_ on customer0_.user_id = user1_.id
547+
where customer0_.age > 25
548+
```
549+
497550
### 9- Pagination Examples
498551

499552
You can find all pagination methods in the `JpaDynamicQueryRepository` interface. You can use the `findAllAsPage` method

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
<groupId>io.github.tdilber</groupId>
1515
<artifactId>spring-jpa-dynamic-query</artifactId>
16-
<version>0.3.1</version>
16+
<version>0.4.0</version>
1717
<packaging>jar</packaging>
1818
<name>Spring Jpa Dynamic Query</name>
1919
<description>Spring Jpa Dynamic Query (JDQ) Project</description>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.beyt.jdq.annotation.model;
2+
3+
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Retention(RetentionPolicy.RUNTIME)
10+
@Target({ElementType.FIELD})
11+
public @interface JdqField {
12+
String value();
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.beyt.jdq.annotation.model;
2+
3+
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Retention(RetentionPolicy.RUNTIME)
10+
@Target({ElementType.FIELD})
11+
public @interface JdqIgnoreField {
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.beyt.jdq.annotation.model;
2+
3+
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Retention(RetentionPolicy.RUNTIME)
10+
@Target({ElementType.TYPE})
11+
public @interface JdqModel {
12+
}

src/main/java/com/beyt/jdq/query/DynamicQueryManager.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.beyt.jdq.query;
22

3+
import com.beyt.jdq.annotation.model.JdqModel;
4+
import com.beyt.jdq.annotation.model.JdqField;
5+
import com.beyt.jdq.annotation.model.JdqIgnoreField;
36
import com.beyt.jdq.dto.Criteria;
47
import com.beyt.jdq.dto.DynamicQuery;
58
import com.beyt.jdq.dto.enums.CriteriaOperator;
@@ -28,6 +31,7 @@
2831
import javax.persistence.Tuple;
2932
import javax.persistence.TypedQuery;
3033
import javax.persistence.criteria.*;
34+
import java.lang.reflect.Field;
3135
import java.lang.reflect.Method;
3236
import java.util.*;
3337
import java.util.stream.Collectors;
@@ -144,6 +148,7 @@ protected static <Entity, ResultType> Iterable<ResultType> getEntityListBySelect
144148
if (resultTypeClass.equals(entityClass) && CollectionUtils.isEmpty(dynamicQuery.getSelect())) {
145149
return getEntityListWithReturnClass(repositoryExecutor, dynamicQuery, resultTypeClass, isPage);
146150
} else {
151+
extractIfJdqModel(dynamicQuery, resultTypeClass);
147152
Iterable<Tuple> entityListBySelectableFilter = getEntityListWithReturnClass(repositoryExecutor, dynamicQuery, Tuple.class, isPage);
148153

149154
if (!CollectionUtils.isEmpty(dynamicQuery.getSelect())) {
@@ -161,6 +166,24 @@ protected static <Entity, ResultType> Iterable<ResultType> getEntityListBySelect
161166
}
162167
}
163168

169+
private static <ResultType> void extractIfJdqModel(DynamicQuery dynamicQuery, Class<ResultType> resultTypeClass) {
170+
if (resultTypeClass.isAnnotationPresent(JdqModel.class)) {
171+
List<Pair<String, String>> select = new ArrayList<>();
172+
for (Field declaredField : resultTypeClass.getDeclaredFields()) {
173+
if (declaredField.isAnnotationPresent(JdqIgnoreField.class)) {
174+
continue;
175+
}
176+
177+
if (declaredField.isAnnotationPresent(JdqField.class)) {
178+
select.add(Pair.of(declaredField.getAnnotation(JdqField.class).value(), declaredField.getName()));
179+
} else {
180+
select.add(Pair.of(declaredField.getName(), declaredField.getName()));
181+
}
182+
}
183+
dynamicQuery.setSelect(select);
184+
}
185+
}
186+
164187
protected static <Entity, ResultType> Iterable<ResultType> getEntityListWithReturnClass(JpaSpecificationExecutor<Entity> repositoryExecutor, DynamicQuery dynamicQuery, Class<ResultType> resultTypeClass, boolean isPage) {
165188
Class<Entity> entityClass = getEntityClass(repositoryExecutor);
166189
EntityManager entityManager = ApplicationContextUtil.getEntityManager();
@@ -299,7 +322,7 @@ protected static <ResultType> Iterable<ResultType> convertResultToResultTypeList
299322

300323
List<ResultType> resultTypeList = stream.map(t -> {
301324
try {
302-
ResultType resultObj = resultTypeClass.newInstance();
325+
ResultType resultObj = resultTypeClass.getConstructor().newInstance();
303326

304327
for (Map.Entry<Integer, Method> entry : setterMethods.entrySet()) {
305328
entry.getValue().invoke(resultObj, t.get(entry.getKey()));

src/test/java/com/beyt/jdq/query/DynamicQueryManagerTest.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import com.beyt.jdq.BaseTestInstance;
44
import com.beyt.jdq.TestApplication;
5+
import com.beyt.jdq.annotation.model.JdqField;
6+
import com.beyt.jdq.annotation.model.JdqIgnoreField;
7+
import com.beyt.jdq.annotation.model.JdqModel;
58
import com.beyt.jdq.dto.Criteria;
69
import com.beyt.jdq.dto.CriteriaList;
710
import com.beyt.jdq.dto.DynamicQuery;
@@ -10,8 +13,7 @@
1013
import com.beyt.jdq.exception.DynamicQueryNoAvailableOrOperationUsageException;
1114
import com.beyt.jdq.testenv.entity.Customer;
1215
import com.beyt.jdq.testenv.entity.User;
13-
import lombok.Getter;
14-
import lombok.Setter;
16+
import lombok.*;
1517
import org.junit.jupiter.api.BeforeAll;
1618
import org.junit.jupiter.api.Test;
1719
import org.junit.jupiter.api.TestInstance;
@@ -56,6 +58,17 @@ protected void init() {
5658
customerRepository.save(customer8);
5759
}
5860

61+
@Test
62+
void test() {
63+
64+
assertEquals(toList(customer4, customer5, customer6, customer7),
65+
customerRepository.findAll(CriteriaList.of(
66+
Criteria.of("age", CriteriaOperator.EQUAL, 23, 24),
67+
Criteria.of("age", CriteriaOperator.NOT_EQUAL, 20, 21),
68+
Criteria.OR(), // ( [ (23 or 24) AND (not 20 and not 21) ] "OR" [ (not 24) AND (25 or 26) ])
69+
Criteria.of("age", CriteriaOperator.NOT_EQUAL, 24),
70+
Criteria.of("age", CriteriaOperator.EQUAL, 25, 26))));
71+
}
5972

6073
@Test
6174
void findAll() {
@@ -230,6 +243,31 @@ void searchQuery() {
230243
List<Customer> allWithSearchQuery4 = customerRepository.findAll(dynamicQuery2, Customer.class);
231244
}
232245

246+
@Data
247+
@JdqModel
248+
@NoArgsConstructor
249+
@AllArgsConstructor
250+
public static class UserJdqModel {
251+
@JdqField("name")
252+
private String nameButDifferentFieldName;
253+
@JdqField("user.name")
254+
private String userNameWithJoin;
255+
256+
private Integer age;
257+
@JdqIgnoreField
258+
private String surname;
259+
}
260+
261+
@Test
262+
void JdqModels() {
263+
DynamicQuery dynamicQuery = new DynamicQuery();
264+
dynamicQuery.getWhere().add(Criteria.of("age", CriteriaOperator.GREATER_THAN, 25));
265+
266+
267+
List<UserJdqModel> result = customerRepository.findAll(dynamicQuery, UserJdqModel.class);
268+
assertEquals(toList(new UserJdqModel(customer7.getName(), customer7.getUser().getName(), customer7.getAge(), null), new UserJdqModel(customer8.getName(), customer8.getUser().getName(), customer8.getAge(), null)), result);
269+
}
270+
233271
@Test
234272
void simplifiedSearchQuery() {
235273
List<User> result = customerRepository.queryBuilder()

src/test/java/com/beyt/jdq/util/field/FieldUtilTest.java renamed to src/test/java/com/beyt/jdq/testenv/repository/field/FieldUtilTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
package com.beyt.jdq.util.field;
1+
package com.beyt.jdq.testenv.repository.field;
22

3+
import com.beyt.jdq.util.field.FieldUtil;
34
import org.junit.jupiter.api.Test;
45

56
import java.text.ParseException;

0 commit comments

Comments
 (0)