Skip to content

Commit 1996ba6

Browse files
author
martinmeerAT
committed
Merge branch 'spring_att4_pre_release'
# Conflicts: # .github/workflows/CDCI-master.yml
2 parents 9e5a458 + ec95f82 commit 1996ba6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1707
-547
lines changed

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,14 @@ out/
3636
### VS Code ###
3737
.vscode/
3838
/old.martinmeer/
39+
/src/main/resources/misc/testSQLQueries
40+
/src/main/java/org/martinmeer/otkassistant/.env
41+
/src/main/resources/misc/application-local.yml
42+
/logs/
43+
/.env
44+
/src/main/java/org/martinmeer/otkassistant/mthread/service/MThrdInputNormalizer.java
45+
/src/main/java/org/martinmeer/otkassistant/mthread/service/MThrdInputRefiner.java
46+
/src/main/java/org/martinmeer/otkassistant/mthread/model/MThrdNSpace.java
47+
/src/main/java/org/martinmeer/otkassistant/mthread/model/mThreadInputData.java
48+
/src/main/java/org/martinmeer/otkassistant/mthread/service/MThreadMainService.java
49+
/src/main/resources/.env

Makefile

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
.PHONY: test
2-
run-dist:
3-
./build/install/app/bin/app
4-
mkInstallDist:
1+
.PHONY: test, build
2+
#run-dist:
3+
# ./build/install/app/bin/app
4+
mkBuild:
55
./gradlew clean
6-
./gradlew installDist
6+
./gradlew build
77
lint:
88
./gradlew checkstyleMain
99
dependency:
1010
./gradlew dependencyUpdates
1111
doc:
1212
./gradlew javadoccgc
13-
run-test:
13+
mkTest:
1414
./gradlew test
1515
./gradlew checkstyleMain
1616
./gradlew jacocoTestReport

build.gradle.kts

Lines changed: 76 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
plugins {
22
java
3-
war
3+
//war
44
id("org.springframework.boot") version "3.4.1"
55
id("io.spring.dependency-management") version "1.1.7"
66
id("org.asciidoctor.jvm.convert") version "3.3.2"
7-
checkstyle
7+
//checkstyle
8+
id("com.github.ben-manes.versions") version "0.51.0"
9+
//id("nu.studer.dotenv") version "3.0.0"
810
}
911

1012
group = "org.martinmeer"
11-
version = "0.0.1-SNAPSHOT"
13+
version = "0.0.1"
1214

1315
java {
1416
toolchain {
@@ -22,46 +24,97 @@ configurations {
2224
}
2325
}
2426

27+
2528
repositories {
2629
mavenCentral()
2730
}
2831

2932
extra["snippetsDir"] = file("build/generated-snippets")
3033

34+
val mockitoAgent = configurations.create("mockitoAgent")
3135
dependencies {
36+
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
37+
annotationProcessor("org.projectlombok:lombok")
38+
compileOnly("org.projectlombok:lombok")
39+
40+
//Spring-Boot
41+
implementation("org.springframework.boot:spring-boot-starter-web")
42+
implementation("org.springframework.boot:spring-boot-starter-jdbc")
3243
implementation("org.springframework.boot:spring-boot-starter-actuator")
33-
implementation("org.springframework.boot:spring-boot-starter-data-rest")
3444
implementation("org.springframework.boot:spring-boot-starter-freemarker")
35-
implementation("org.springframework.boot:spring-boot-starter-security")
36-
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
37-
implementation("org.springframework.boot:spring-boot-starter-web")
38-
implementation("org.springframework.boot:spring-boot-starter-web-services")
39-
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
40-
compileOnly("org.projectlombok:lombok")
41-
developmentOnly("org.springframework.boot:spring-boot-devtools")
42-
developmentOnly("org.springframework.boot:spring-boot-docker-compose")
45+
implementation("org.springframework.boot:spring-boot-starter-log4j2")
46+
47+
configurations {
48+
all {
49+
exclude(group = "org.springframework.boot", module = "spring-boot-starter-logging")
50+
}
51+
}
52+
53+
54+
//developmentOnly("org.springframework.boot:spring-boot-devtools")
55+
//developmentOnly("org.springframework.boot:spring-boot-docker-compose")
4356
runtimeOnly("org.postgresql:postgresql")
44-
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
45-
annotationProcessor("org.projectlombok:lombok")
46-
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
47-
testImplementation("org.springframework.boot:spring-boot-starter-test")
48-
testImplementation("org.springframework.boot:spring-boot-testcontainers")
57+
58+
// Thymeleaf
59+
///implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
60+
61+
implementation("org.springframework.boot:spring-boot-starter-web")
62+
/*{
63+
exclude(module = "spring-boot-starter-tomcat")
64+
}*/
65+
//implementation("org.springframework.boot:spring-boot-starter-tomcat")
66+
//providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
67+
68+
69+
//Test
70+
testImplementation("org.springframework.boot:spring-boot-starter-test") {
71+
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
72+
}
73+
//testImplementation("org.springframework.boot:spring-boot-testcontainers")
4974
testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")
50-
testImplementation("org.springframework.security:spring-security-test")
51-
testImplementation("org.testcontainers:junit-jupiter")
52-
testImplementation("org.testcontainers:postgresql")
75+
//testImplementation("org.springframework.security:spring-security-test")
76+
//testImplementation("org.testcontainers:junit-jupiter")
77+
//testImplementation("org.testcontainers:postgresql")
5378
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
54-
}
5579

56-
tasks.withType<Test> {
57-
useJUnitPlatform()
80+
testImplementation(libs.mockito)
81+
mockitoAgent(libs.mockito) { isTransitive = false }
82+
83+
84+
//Logging
85+
implementation("org.springframework.boot:spring-boot-starter-log4j2")
86+
configurations {
87+
all {
88+
exclude(group = "org.springframework.boot", module = "spring-boot-starter-logging")
89+
}
90+
}
91+
implementation("io.github.cdimascio:dotenv-java:3.1.0")
92+
5893
}
5994

95+
//tasks
96+
97+
/*tasks.war {
98+
archiveFileName.set("otkassist.war") // Название WAR-файла
99+
}*/
100+
60101
tasks.test {
102+
useJUnitPlatform{
103+
excludeTags("excludeFromBuild") // Exclude tests with this tag
104+
}
105+
systemProperty("spring.profiles.active", "test")
106+
107+
108+
//environment("DB_USERNAME", System.getenv("DB_USERNAME"))
109+
//environment("DB_PASSWORD", System.getenv("DB_PASSWORD"))
61110
outputs.dir(project.extra["snippetsDir"]!!)
111+
112+
jvmArgs("-javaagent:${mockitoAgent.asPath}")
113+
62114
}
63115

64116
tasks.asciidoctor {
65117
inputs.dir(project.extra["snippetsDir"]!!)
66118
dependsOn(tasks.test)
67119
}
120+

gradle/libs.versions.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
[versions]
3+
mockito = "5.14.0"
4+
5+
[libraries]
6+
mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }

notes/Fetcher-for-List-and-Object

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
Да, конечно! Вы можете добавить в `SchemaAwareNamedParameterJdbcTemplate` два метода: один для получения **одиночного объекта**, другой для **списка объектов**. Это обеспечит гибкость и безопасность типов. Давайте реализуем оба варианта.
2+
3+
---
4+
5+
### 1. Добавление методов в `SchemaAwareNamedParameterJdbcTemplate`
6+
7+
Модифицируем класс, чтобы он поддерживал оба сценария:
8+
9+
```java
10+
public class SchemaAwareNamedParameterJdbcTemplate {
11+
12+
// ... существующие поля и конструкторы ...
13+
14+
/**
15+
* Возвращает один объект из запроса.
16+
*/
17+
public <T> T queryForObjectWithSchema(
18+
String schemaName,
19+
String sql,
20+
Map<String, Object> params,
21+
RowMapper<T> rowMapper
22+
) {
23+
try {
24+
jdbcTemplate.execute("SET LOCAL search_path TO " + sanitizeSchemaName(schemaName));
25+
return namedParameterJdbcTemplate.queryForObject(sql, params, rowMapper);
26+
} catch (DataAccessException e) {
27+
throw new RuntimeException("Error executing query for object", e);
28+
}
29+
}
30+
31+
/**
32+
* Возвращает список объектов из запроса.
33+
*/
34+
public <T> List<T> queryForListWithSchema(
35+
String schemaName,
36+
String sql,
37+
Map<String, Object> params,
38+
RowMapper<T> rowMapper
39+
) {
40+
try {
41+
jdbcTemplate.execute("SET LOCAL search_path TO " + sanitizeSchemaName(schemaName));
42+
return namedParameterJdbcTemplate.query(sql, params, rowMapper);
43+
} catch (DataAccessException e) {
44+
throw new RuntimeException("Error executing query for list", e);
45+
}
46+
}
47+
48+
// ... метод sanitizeSchemaName ...
49+
}
50+
```
51+
52+
---
53+
54+
### 2. Адаптация `AbstractFetchedData`
55+
56+
Теперь абстрактный класс можно параметризовать так, чтобы он работал и с одиночными объектами, и со списками, в зависимости от потребностей.
57+
58+
#### Вариант A: Работа с одиночным объектом
59+
```java
60+
public abstract class AbstractFetchedData<T> implements DataFetcher {
61+
62+
protected String baseData;
63+
protected T fetchedData; // Одиночный объект
64+
protected String sql;
65+
protected String schemaName;
66+
67+
private final SchemaAwareNamedParameterJdbcTemplate dbQueryTemplate;
68+
69+
protected AbstractFetchedData(SchemaAwareNamedParameterJdbcTemplate dbQueryTemplate, String sql) {
70+
this.dbQueryTemplate = dbQueryTemplate;
71+
this.sql = sql;
72+
}
73+
74+
@Override
75+
public void fetchFromDatabase() {
76+
Map<String, Object> params = Collections.singletonMap("baseData", typeConverter(baseData));
77+
T result = dbQueryTemplate.queryForObjectWithSchema( // Используем метод для объекта
78+
schemaName,
79+
sql,
80+
params,
81+
getObjectMapper()
82+
);
83+
setFetchedData(result);
84+
}
85+
86+
protected abstract T typeConverter(String baseData);
87+
88+
protected abstract RowMapper<T> getObjectMapper();
89+
90+
// Геттер и сеттер для fetchedData
91+
protected void setFetchedData(T data) {
92+
this.fetchedData = data;
93+
}
94+
95+
public T getFetchedData() {
96+
return fetchedData;
97+
}
98+
}
99+
```
100+
101+
**Пример использования:**
102+
```java
103+
public class UserFetcher extends AbstractFetchedData<User> {
104+
@Override
105+
protected User typeConverter(String baseData) {
106+
return new User(baseData); // Например, создание User из строки
107+
}
108+
109+
@Override
110+
protected RowMapper<User> getObjectMapper() {
111+
return (rs, rowNum) -> new User(rs.getString("name"));
112+
}
113+
}
114+
115+
// Использование:
116+
UserFetcher fetcher = new UserFetcher(template, "SELECT * FROM users WHERE id = :baseData");
117+
fetcher.setBaseData("123");
118+
fetcher.fetchFromDatabase();
119+
User user = fetcher.getFetchedData(); // Одиночный объект
120+
```
121+
122+
---
123+
124+
#### Вариант B: Работа со списком объектов
125+
Если в некоторых сценариях нужен список, создайте отдельный абстрактный класс (например, `AbstractListFetchedData`):
126+
127+
```java
128+
public abstract class AbstractListFetchedData<T> implements DataFetcher {
129+
130+
protected String baseData;
131+
protected List<T> fetchedData; // Список объектов
132+
protected String sql;
133+
protected String schemaName;
134+
135+
private final SchemaAwareNamedParameterJdbcTemplate dbQueryTemplate;
136+
137+
protected AbstractListFetchedData(SchemaAwareNamedParameterJdbcTemplate dbQueryTemplate, String sql) {
138+
this.dbQueryTemplate = dbQueryTemplate;
139+
this.sql = sql;
140+
}
141+
142+
@Override
143+
public void fetchFromDatabase() {
144+
Map<String, Object> params = Collections.singletonMap("baseData", typeConverter(baseData));
145+
List<T> result = dbQueryTemplate.queryForListWithSchema( // Используем метод для списка
146+
schemaName,
147+
sql,
148+
params,
149+
getObjectMapper()
150+
);
151+
setFetchedData(result);
152+
}
153+
154+
protected abstract T typeConverter(String baseData); // Возвращает параметр для запроса
155+
156+
protected abstract RowMapper<T> getObjectMapper();
157+
158+
protected void setFetchedData(List<T> data) {
159+
this.fetchedData = data;
160+
}
161+
162+
public List<T> getFetchedData() {
163+
return fetchedData;
164+
}
165+
}
166+
```
167+
168+
**Пример использования:**
169+
```java
170+
public class AllUsersFetcher extends AbstractListFetchedData<User> {
171+
@Override
172+
protected User typeConverter(String baseData) {
173+
return null; // Если параметр не нужен (например, запрос SELECT * FROM users)
174+
}
175+
176+
@Override
177+
protected RowMapper<User> getObjectMapper() {
178+
return (rs, rowNum) -> new User(rs.getString("name"));
179+
}
180+
}
181+
182+
// Использование:
183+
AllUsersFetcher fetcher = new AllUsersFetcher(template, "SELECT * FROM users");
184+
fetcher.fetchFromDatabase();
185+
List<User> users = fetcher.getFetchedData(); // Список
186+
```
187+
188+
---
189+
190+
### Итоги:
191+
1. **Два метода в `SchemaAwareNamedParameterJdbcTemplate`**:
192+
- `queryForObjectWithSchema()` — для одиночных объектов.
193+
- `queryForListWithSchema()` — для списков.
194+
195+
2. **Два абстрактных класса**:
196+
- `AbstractFetchedData<T>` — для случаев, когда запрос возвращает **один объект**.
197+
- `AbstractListFetchedData<T>` — для случаев, когда запрос возвращает **список**.
198+
199+
3. **Безопасность типов**:
200+
- Больше не требуется приведение типов вручную.
201+
- Ошибки несоответствия типов будут пойманы на этапе компиляции.
202+
203+
---
204+
205+
### Когда что использовать:
206+
- **`AbstractFetchedData<T>`** — для запросов с `WHERE` (например, поиск по уникальному идентификатору).
207+
- **`AbstractListFetchedData<T>`** — для запросов, возвращающих множество строк (например, `SELECT * FROM table`).
208+
209+
Если вы хотите объединить оба варианта в одном классе, можно добавить параметр, указывающий тип результата (например, через `enum`), но это усложнит код. Лучше разделить логику на два класса для ясности.

0 commit comments

Comments
 (0)