Skip to content

Commit bd33ddc

Browse files
committed
fix: @query with projection and List<List>
1 parent 89e588b commit bd33ddc

File tree

7 files changed

+392
-0
lines changed

7 files changed

+392
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package io.micronaut.data.hibernate.querygroupby
2+
3+
import io.micronaut.context.annotation.Property
4+
import io.micronaut.data.hibernate.entities.MicronautProject
5+
import io.micronaut.data.hibernate.entities.MicronautTask
6+
import io.micronaut.test.extensions.spock.annotation.MicronautTest
7+
import jakarta.inject.Inject
8+
import spock.lang.PendingFeature
9+
import spock.lang.Specification
10+
11+
import java.time.LocalDate
12+
13+
@MicronautTest(startApplication = false, packages = "io.micronaut.data.hibernate.entities")
14+
@Property(name = "datasources.default.name", value = "mydb")
15+
@Property(name = 'jpa.default.properties.hibernate.hbm2ddl.auto', value = 'create-drop')
16+
class TaskRepositorySpec extends Specification {
17+
18+
@Inject
19+
MicronautProjectRepository projectRepository
20+
21+
@Inject
22+
MicronautTaskRepository taskRepository
23+
24+
@PendingFeature
25+
void "@Query projecting to a POJO"() {
26+
given:
27+
MicronautProject p1 = projectRepository.save(new MicronautProject("P1", "Project 1", "Description of Project 1"))
28+
MicronautProject p2 = projectRepository.save(new MicronautProject("P2", "Project 2", "Description of Project 2"))
29+
MicronautProject p3 = projectRepository.save(new MicronautProject("P3", "Project 3", "Description of Project 3"))
30+
MicronautTask t1 = taskRepository.save(new MicronautTask("Task 1", "Task 1 Description", LocalDate.of(2025, 1, 12), p1, TaskStatus.TO_DO))
31+
MicronautTask t2 = taskRepository.save(new MicronautTask("Task 2", "Task 2 Description", LocalDate.of(2025, 2, 10), p1, TaskStatus.TO_DO))
32+
MicronautTask t3 = taskRepository.save(new MicronautTask("Task 3", "Task 3 Description", LocalDate.of(2025, 3, 16), p1, TaskStatus.TO_DO))
33+
MicronautTask t4 = taskRepository.save(new MicronautTask("Task 4", "Task 4 Description", LocalDate.of(2025, 6, 25), p1, TaskStatus.IN_PROGRESS))
34+
35+
expect:
36+
3 == countIterableElements(projectRepository.findAll())
37+
38+
4 == countIterableElements(taskRepository.findAll())
39+
40+
when:
41+
Iterable<TasksPerYear> tasks = taskRepository.countByDueYear()
42+
43+
then:
44+
2 == countIterableElements(tasks)
45+
46+
cleanup:
47+
taskRepository.delete(t4)
48+
taskRepository.delete(t3)
49+
taskRepository.delete(t2)
50+
taskRepository.delete(t1)
51+
projectRepository.delete(p3)
52+
projectRepository.delete(p2)
53+
projectRepository.delete(p1)
54+
}
55+
56+
@PendingFeature
57+
void "@Query returning List of List"() {
58+
given:
59+
MicronautProject p1 = projectRepository.save(new MicronautProject("P1", "Project 1", "Description of Project 1"))
60+
MicronautProject p2 = projectRepository.save(new MicronautProject("P2", "Project 2", "Description of Project 2"))
61+
MicronautProject p3 = projectRepository.save(new MicronautProject("P3", "Project 3", "Description of Project 3"))
62+
MicronautTask t1 = taskRepository.save(new MicronautTask("Task 1", "Task 1 Description", LocalDate.of(2025, 1, 12), p1, TaskStatus.TO_DO))
63+
MicronautTask t2 = taskRepository.save(new MicronautTask("Task 2", "Task 2 Description", LocalDate.of(2025, 2, 10), p1, TaskStatus.TO_DO))
64+
MicronautTask t3 = taskRepository.save(new MicronautTask("Task 3", "Task 3 Description", LocalDate.of(2025, 3, 16), p1, TaskStatus.TO_DO))
65+
MicronautTask t4 = taskRepository.save(new MicronautTask("Task 4", "Task 4 Description", LocalDate.of(2025, 6, 25), p1, TaskStatus.IN_PROGRESS))
66+
67+
expect:
68+
3 == countIterableElements(projectRepository.findAll())
69+
70+
4 == countIterableElements(taskRepository.findAll())
71+
when:
72+
List<List<Integer>> tasks = taskRepository.countByDueYearReturnList()
73+
74+
then:
75+
2 == tasks.size()
76+
tasks.every { it.size() == 2 }
77+
78+
cleanup:
79+
taskRepository.delete(t4)
80+
taskRepository.delete(t3)
81+
taskRepository.delete(t2)
82+
taskRepository.delete(t1)
83+
projectRepository.delete(p3)
84+
projectRepository.delete(p2)
85+
projectRepository.delete(p1)
86+
}
87+
88+
static int countIterableElements(Iterable<?> iterable) {
89+
int count = 0
90+
Iterator<?> iterator = iterable.iterator()
91+
if (iterator.hasNext()) {
92+
do {
93+
iterator.next()
94+
count++
95+
} while (iterator.hasNext())
96+
}
97+
count
98+
}
99+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package io.micronaut.data.hibernate.entities;
2+
3+
import javax.persistence.CascadeType;
4+
import javax.persistence.Column;
5+
import javax.persistence.Entity;
6+
import javax.persistence.FetchType;
7+
import javax.persistence.GeneratedValue;
8+
import javax.persistence.GenerationType;
9+
import javax.persistence.Id;
10+
import javax.persistence.OneToMany;
11+
import java.util.HashSet;
12+
import java.util.Objects;
13+
import java.util.Set;
14+
15+
@Entity
16+
public class MicronautProject {
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.IDENTITY)
19+
private Long id;
20+
@Column(unique = true, nullable = false, updatable = false)
21+
private String code;
22+
private String name;
23+
private String description;
24+
@OneToMany(mappedBy = "project", orphanRemoval = true, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
25+
private Set<MicronautTask> tasks = new HashSet<>();
26+
public MicronautProject() {
27+
}
28+
29+
public MicronautProject(String code, String name, String description) {
30+
this.code = code;
31+
this.name = name;
32+
this.description = description;
33+
}
34+
35+
@Override
36+
public int hashCode() {
37+
return Objects.hashCode(code);
38+
}
39+
@Override
40+
public boolean equals(Object obj) {
41+
if (this == obj)
42+
return true;
43+
if (obj == null)
44+
return false;
45+
if (getClass() != obj.getClass())
46+
return false;
47+
MicronautProject other = (MicronautProject) obj;
48+
if (code == null) {
49+
if (other.code != null)
50+
return false;
51+
} else if (!code.equals(other.code))
52+
return false;
53+
return true;
54+
}
55+
56+
public Long getId() {
57+
return id;
58+
}
59+
60+
public void setId(Long id) {
61+
this.id = id;
62+
}
63+
64+
public String getCode() {
65+
return code;
66+
}
67+
68+
public void setCode(String code) {
69+
this.code = code;
70+
}
71+
72+
public String getName() {
73+
return name;
74+
}
75+
76+
public void setName(String name) {
77+
this.name = name;
78+
}
79+
80+
public String getDescription() {
81+
return description;
82+
}
83+
84+
public void setDescription(String description) {
85+
this.description = description;
86+
}
87+
88+
public Set<MicronautTask> getTasks() {
89+
return tasks;
90+
}
91+
92+
public void setTasks(Set<MicronautTask> tasks) {
93+
this.tasks = tasks;
94+
}
95+
96+
@Override
97+
public String toString() {
98+
return "Project [id=" + id + ", code=" + code + ", name=" + name + ", description=" + description + "]";
99+
}
100+
101+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package io.micronaut.data.hibernate.entities;
2+
3+
import io.micronaut.data.hibernate.querygroupby.TaskStatus;
4+
5+
import javax.persistence.Column;
6+
import javax.persistence.Entity;
7+
import javax.persistence.GeneratedValue;
8+
import javax.persistence.GenerationType;
9+
import javax.persistence.Id;
10+
import javax.persistence.ManyToOne;
11+
import java.time.LocalDate;
12+
import java.util.Objects;
13+
import java.util.UUID;
14+
15+
@Entity
16+
public class MicronautTask {
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.IDENTITY)
19+
private Long id;
20+
@Column(unique = true, nullable = false, updatable = false)
21+
private String uuid = UUID.randomUUID().toString();
22+
private String name;
23+
private String description;
24+
private LocalDate dueDate;
25+
26+
private TaskStatus status;
27+
28+
@ManyToOne(optional = false)
29+
private MicronautProject project;
30+
31+
32+
public MicronautTask() {
33+
}
34+
public MicronautTask(String name, String description, LocalDate dueDate, MicronautProject project) {
35+
this(name, description, dueDate, project, TaskStatus.TO_DO);
36+
}
37+
38+
public MicronautTask(String name, String description, LocalDate dueDate, MicronautProject project, TaskStatus status) {
39+
this.name = name;
40+
this.description = description;
41+
this.dueDate = dueDate;
42+
this.status = status;
43+
this.project = project;
44+
}
45+
46+
@Override
47+
public boolean equals(Object obj) {
48+
if (this == obj)
49+
return true;
50+
if (obj == null)
51+
return false;
52+
if (getClass() != obj.getClass())
53+
return false;
54+
MicronautTask other = (MicronautTask) obj;
55+
if (uuid == null) {
56+
if (other.uuid != null)
57+
return false;
58+
} else if (!uuid.equals(other.uuid))
59+
return false;
60+
return true;
61+
}
62+
@Override
63+
public int hashCode() {
64+
return Objects.hash(uuid);
65+
}
66+
67+
68+
public Long getId() {
69+
return id;
70+
}
71+
72+
public void setId(Long id) {
73+
this.id = id;
74+
}
75+
76+
public String getUuid() {
77+
return uuid;
78+
}
79+
80+
public void setUuid(String uuid) {
81+
this.uuid = uuid;
82+
}
83+
84+
public String getName() {
85+
return name;
86+
}
87+
88+
public void setName(String name) {
89+
this.name = name;
90+
}
91+
92+
public String getDescription() {
93+
return description;
94+
}
95+
96+
public void setDescription(String description) {
97+
this.description = description;
98+
}
99+
100+
public LocalDate getDueDate() {
101+
return dueDate;
102+
}
103+
104+
public void setDueDate(LocalDate dueDate) {
105+
this.dueDate = dueDate;
106+
}
107+
108+
public TaskStatus getStatus() {
109+
return status;
110+
}
111+
112+
public void setStatus(TaskStatus status) {
113+
this.status = status;
114+
}
115+
116+
public MicronautProject getProject() {
117+
return project;
118+
}
119+
120+
public void setProject(MicronautProject project) {
121+
this.project = project;
122+
}
123+
124+
125+
@Override
126+
public String toString() {
127+
return "Task [id=" + id + ", name=" + name + ", description=" + description + ", dueDate=" + dueDate + ", status=" + status + ", project=" + project + "]";
128+
}
129+
130+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.micronaut.data.hibernate.querygroupby;
2+
3+
import io.micronaut.data.annotation.Repository;
4+
import io.micronaut.data.hibernate.entities.MicronautProject;
5+
import io.micronaut.data.repository.CrudRepository;
6+
@Repository
7+
public interface MicronautProjectRepository extends CrudRepository<MicronautProject, Long> {
8+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.micronaut.data.hibernate.querygroupby;
2+
3+
import io.micronaut.data.annotation.Query;
4+
import io.micronaut.data.annotation.Repository;
5+
import io.micronaut.data.hibernate.entities.MicronautTask;
6+
import io.micronaut.data.repository.CrudRepository;
7+
8+
import java.util.List;
9+
@Repository
10+
public interface MicronautTaskRepository extends CrudRepository<MicronautTask, Long> {
11+
@Query("select count(*), year(t.dueDate) from MicronautTask t group by year(t.dueDate)")
12+
Iterable<TasksPerYear> countByDueYear();
13+
14+
@Query("select count(*), year(t.dueDate) from MicronautTask t group by year(t.dueDate)")
15+
List<List<Integer>> countByDueYearReturnList();
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.micronaut.data.hibernate.querygroupby;
2+
3+
public enum TaskStatus {
4+
TO_DO("To Do"),
5+
IN_PROGRESS("In Progress"),
6+
ON_HOLD("On Hold"),
7+
DONE("Done");
8+
private final String label;
9+
10+
private TaskStatus(String label) {
11+
this.label = label;
12+
}
13+
public String getLabel() {
14+
return label;
15+
}
16+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.micronaut.data.hibernate.querygroupby;
2+
3+
import io.micronaut.core.annotation.Introspected;
4+
5+
@Introspected
6+
public class TasksPerYear {
7+
private final String number;
8+
private final String year;
9+
10+
public TasksPerYear(String number, String year) {
11+
this.number = number;
12+
this.year = year;
13+
}
14+
15+
public String getNumber() {
16+
return number;
17+
}
18+
19+
public String getYear() {
20+
return year;
21+
}
22+
}

0 commit comments

Comments
 (0)