From 075777edf103749cf5fe25af13e1429a3c4a1897 Mon Sep 17 00:00:00 2001 From: Ivan Kondrashkov Date: Sun, 24 Nov 2024 12:33:57 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20hw05-orm-?= =?UTF-8?q?gradle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 12 +++ build.gradle | 3 + buildSrc/src/main/groovy/Versions.groovy | 3 + hw05-example-gradle/Dockerfile | 2 + hw05-example-gradle/build.gradle | 36 ++++++++ .../main/groovy/ru/otus/homework/Main.groovy | 77 +++++++++++++++++ .../src/main/resources/application.properties | 4 + .../db/migration/V1.0.0__init_schema.sql | 12 +++ .../homework/DbServiceClientImplTest.groovy | 68 +++++++++++++++ .../EntityClassMetaDataImplTest.groovy | 55 ++++++++++++ .../homework/EntitySQLMetaDataImplTest.groovy | 54 ++++++++++++ hw05-orm-gradle/build.gradle | 20 +++++ .../ru/otus/homework/ReflectionUtility.groovy | 34 ++++++++ .../ru/otus/homework/annotation/Id.groovy | 11 +++ .../datasource/DriverManagerDataSource.groovy | 85 +++++++++++++++++++ .../DataBaseOperationException.groovy | 8 ++ .../exception/EntityMetaDataException.groovy | 8 ++ .../exception/ResultMapperException.groovy | 8 ++ .../mapper/EntityClassMetaData.groovy | 12 +++ .../mapper/EntityClassMetaDataImpl.groovy | 84 ++++++++++++++++++ .../homework/mapper/EntitySQLMetaData.groovy | 8 ++ .../mapper/EntitySQLMetaDataImpl.groovy | 46 ++++++++++ .../homework/mapper/ResultListMapper.groovy | 32 +++++++ .../ru/otus/homework/model/Client.groovy | 11 +++ .../ru/otus/homework/model/Manager.groovy | 12 +++ .../homework/repository/DataTemplate.groovy | 8 ++ .../repository/DataTemplateJdbc.groovy | 43 ++++++++++ .../homework/repository/DbExecutor.groovy | 8 ++ .../homework/repository/DbExecutorImpl.groovy | 36 ++++++++ .../homework/service/DBServiceClient.groovy | 9 ++ .../homework/service/DBServiceManager.groovy | 9 ++ .../service/DbServiceClientImpl.groovy | 53 ++++++++++++ .../service/DbServiceManagerImpl.groovy | 54 ++++++++++++ .../sessionmanager/TransactionRunner.groovy | 5 ++ .../TransactionRunnerJdbc.groovy | 40 +++++++++ .../test-resources/test-resources-port.txt | 2 +- settings.gradle | 2 + 37 files changed, 973 insertions(+), 1 deletion(-) create mode 100644 hw05-example-gradle/Dockerfile create mode 100644 hw05-example-gradle/build.gradle create mode 100644 hw05-example-gradle/src/main/groovy/ru/otus/homework/Main.groovy create mode 100644 hw05-example-gradle/src/main/resources/application.properties create mode 100644 hw05-example-gradle/src/main/resources/db/migration/V1.0.0__init_schema.sql create mode 100644 hw05-example-gradle/src/test/groovy/ru/otus/homework/DbServiceClientImplTest.groovy create mode 100644 hw05-example-gradle/src/test/groovy/ru/otus/homework/EntityClassMetaDataImplTest.groovy create mode 100644 hw05-example-gradle/src/test/groovy/ru/otus/homework/EntitySQLMetaDataImplTest.groovy create mode 100644 hw05-orm-gradle/build.gradle create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/ReflectionUtility.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/annotation/Id.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/datasource/DriverManagerDataSource.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/DataBaseOperationException.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/EntityMetaDataException.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/ResultMapperException.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntityClassMetaData.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntityClassMetaDataImpl.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntitySQLMetaData.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntitySQLMetaDataImpl.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/ResultListMapper.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/model/Client.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/model/Manager.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DataTemplate.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DataTemplateJdbc.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DbExecutor.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DbExecutorImpl.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DBServiceClient.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DBServiceManager.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DbServiceClientImpl.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DbServiceManagerImpl.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/sessionmanager/TransactionRunner.groovy create mode 100644 hw05-orm-gradle/src/main/groovy/ru/otus/homework/sessionmanager/TransactionRunnerJdbc.groovy diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index bd8d656..2ed5c9c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -28,6 +28,18 @@ jobs: java-version: '17' distribution: 'temurin' + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Build Docker images + run: docker-compose -f docker-compose.yml build + + - name: Start Docker Compose services + run: docker-compose -f docker-compose.yml up -d + # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - name: Setup Gradle diff --git a/build.gradle b/build.gradle index 3e37ff5..0c23d6f 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,9 @@ allprojects { dependency("org.junit.jupiter:junit-jupiter:${Versions.junit_jupiter}") dependency("org.apache.poi:poi:${Versions.apache_poi}") dependency("org.apache.poi:poi-ooxml:${Versions.apache_poi}") + dependency("com.zaxxer:HikariCP:${Versions.hikari}") + dependency("org.postgresql:postgresql:${Versions.postgres}") + dependency("org.flywaydb:flyway-core:${Versions.flyway}") } } diff --git a/buildSrc/src/main/groovy/Versions.groovy b/buildSrc/src/main/groovy/Versions.groovy index 77bda42..cad874a 100644 --- a/buildSrc/src/main/groovy/Versions.groovy +++ b/buildSrc/src/main/groovy/Versions.groovy @@ -2,4 +2,7 @@ interface Versions { String guava = '31.1-jre' String junit_jupiter = '5.9.0' String apache_poi = '4.0.0' + String hikari = '5.0.1' + String postgres = '42.5.1' + String flyway = '9.12.0' } \ No newline at end of file diff --git a/hw05-example-gradle/Dockerfile b/hw05-example-gradle/Dockerfile new file mode 100644 index 0000000..3f24607 --- /dev/null +++ b/hw05-example-gradle/Dockerfile @@ -0,0 +1,2 @@ +FROM amazoncorretto:17-alpine-jdk +COPY hw05-example-gradle/build/libs/*.jar app.jar \ No newline at end of file diff --git a/hw05-example-gradle/build.gradle b/hw05-example-gradle/build.gradle new file mode 100644 index 0000000..6997d31 --- /dev/null +++ b/hw05-example-gradle/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'idea' + id 'groovy' + id 'com.github.johnrengelman.shadow' +} + +group 'ru.otus.homework' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation localGroovy() + implementation 'com.zaxxer:HikariCP' + implementation 'org.postgresql:postgresql' + implementation 'org.flywaydb:flyway-core' + testImplementation 'org.junit.jupiter:junit-jupiter' + implementation project(':hw05-orm-gradle') +} + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } +} + +shadowJar { + archiveBaseName.set('hw05-example-gradle-fatJar') + archiveVersion.set('0.1') + manifest { + attributes 'Main-Class': 'ru.otus.homework.Main' + } +} \ No newline at end of file diff --git a/hw05-example-gradle/src/main/groovy/ru/otus/homework/Main.groovy b/hw05-example-gradle/src/main/groovy/ru/otus/homework/Main.groovy new file mode 100644 index 0000000..d95367c --- /dev/null +++ b/hw05-example-gradle/src/main/groovy/ru/otus/homework/Main.groovy @@ -0,0 +1,77 @@ +package ru.otus.homework + +import javax.sql.DataSource +import org.flywaydb.core.Flyway +import ru.otus.homework.model.Client +import ru.otus.homework.model.Manager +import ru.otus.homework.repository.DataTemplate +import ru.otus.homework.repository.DataTemplateJdbc +import ru.otus.homework.repository.DbExecutorImpl +import ru.otus.homework.service.DbServiceClientImpl +import ru.otus.homework.service.DbServiceManagerImpl +import ru.otus.homework.mapper.EntitySQLMetaDataImpl +import ru.otus.homework.mapper.EntityClassMetaDataImpl +import ru.otus.homework.datasource.DriverManagerDataSource +import ru.otus.homework.sessionmanager.TransactionRunnerJdbc + +static void main(String[] args) { + Properties properties = new Properties() + ClassLoader classLoader = getClass().getClassLoader() + File file = new File(classLoader.getResource("application.properties").getFile()) + file.withDataInputStream { + properties.load(it) + } + + final String URL = properties."datasource.url" + final String USER = properties."datasource.username" + final String PASSWORD = properties."datasource.password" + final String DRIVER = properties."datasource.driver-class-name" + + def dataSource = new DriverManagerDataSource(URL, USER, PASSWORD, DRIVER) + flywayMigrations(dataSource) + def transactionRunner = new TransactionRunnerJdbc(dataSource) + def dbExecutor = new DbExecutorImpl() + + def entityClassMetaDataClient = new EntityClassMetaDataImpl<>(Client.class) + def entitySQLMetaDataClient = new EntitySQLMetaDataImpl<>(entityClassMetaDataClient) + def dataTemplateClient = new DataTemplateJdbc<>( + dbExecutor: dbExecutor, + entitySQLMetaData: entitySQLMetaDataClient, + entityClassMetaData: entityClassMetaDataClient + ) + + def dbServiceClient = new DbServiceClientImpl(transactionRunner, dataTemplateClient as DataTemplate); + def clientAfterSave = dbServiceClient.saveClient(new Client(name: "dbServiceFirst")) + def clientSecond = dbServiceClient.saveClient(new Client(name: "dbServiceSecond")) + def clientSecondSelected = dbServiceClient.getClient(clientSecond.getId()) + def clientForUpdate = clientSecondSelected + clientForUpdate?.setName("New name for client") + def clientAfterUpdate = dbServiceClient.saveClient(clientForUpdate) + + def entityClassMetaDataManager = new EntityClassMetaDataImpl<>(Manager.class) + def entitySQLMetaDataManager = new EntitySQLMetaDataImpl<>(entityClassMetaDataManager) + def dataTemplateManager = new DataTemplateJdbc<>( + dbExecutor: dbExecutor, + entitySQLMetaData: entitySQLMetaDataManager, + entityClassMetaData: entityClassMetaDataManager + ) + + def dbServiceManager = new DbServiceManagerImpl(transactionRunner, dataTemplateManager as DataTemplate) + def managerAfterSave = dbServiceManager.saveManager(new Manager(label: "ManagerFirst", param1: "param1")) + def managerSecond = dbServiceManager.saveManager(new Manager(label: "ManagerSecond", param1: "param2")) + def managerSecondSelected = dbServiceManager.getManager(managerSecond.getNo()) + def managerForUpdate = managerSecondSelected + managerForUpdate?.setLabel("New Label for manager") + managerForUpdate?.setParam1("New param for manager") + def managerAfterUpdate = dbServiceManager.saveManager(managerForUpdate) +} + +private static void flywayMigrations(DataSource dataSource) { + def flyway = Flyway.configure() + .dataSource(dataSource) + .baselineVersion('0') + .baselineOnMigrate(true) + .locations("classpath:/db/migration") + .load() + flyway.migrate() +} \ No newline at end of file diff --git a/hw05-example-gradle/src/main/resources/application.properties b/hw05-example-gradle/src/main/resources/application.properties new file mode 100644 index 0000000..25a3317 --- /dev/null +++ b/hw05-example-gradle/src/main/resources/application.properties @@ -0,0 +1,4 @@ +datasource.username=root +datasource.password=root +datasource.url=jdbc:postgresql://localhost:6542/todo +datasource.driver-class-name=org.postgresql.Driver \ No newline at end of file diff --git a/hw05-example-gradle/src/main/resources/db/migration/V1.0.0__init_schema.sql b/hw05-example-gradle/src/main/resources/db/migration/V1.0.0__init_schema.sql new file mode 100644 index 0000000..3e0ad07 --- /dev/null +++ b/hw05-example-gradle/src/main/resources/db/migration/V1.0.0__init_schema.sql @@ -0,0 +1,12 @@ +DROP TABLE IF EXISTS Client, Manager; + +CREATE TABLE IF NOT EXISTS Client ( + id SERIAL, + name VARCHAR(50) +); + +CREATE TABLE IF NOT EXISTS Manager ( + no SERIAL, + label VARCHAR(50), + param1 VARCHAR(50) +); \ No newline at end of file diff --git a/hw05-example-gradle/src/test/groovy/ru/otus/homework/DbServiceClientImplTest.groovy b/hw05-example-gradle/src/test/groovy/ru/otus/homework/DbServiceClientImplTest.groovy new file mode 100644 index 0000000..71390c7 --- /dev/null +++ b/hw05-example-gradle/src/test/groovy/ru/otus/homework/DbServiceClientImplTest.groovy @@ -0,0 +1,68 @@ +package ru.otus.homework + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import ru.otus.homework.model.Client +import ru.otus.homework.mapper.EntitySQLMetaDataImpl +import ru.otus.homework.mapper.EntityClassMetaDataImpl +import ru.otus.homework.repository.DataTemplateJdbc +import ru.otus.homework.repository.DbExecutorImpl +import ru.otus.homework.service.DBServiceClient +import ru.otus.homework.service.DbServiceClientImpl +import ru.otus.homework.datasource.DriverManagerDataSource +import ru.otus.homework.sessionmanager.TransactionRunnerJdbc + +class DbServiceClientImplTest { + Client client + DBServiceClient serviceClient + + final String USER = "root" + final String PASSWORD = "root" + final String URL = "jdbc:postgresql://localhost:6542/todo" + final String DRIVER = "org.postgresql.Driver" + + @BeforeEach + void init() { + def dataSource = new DriverManagerDataSource(URL, USER, PASSWORD, DRIVER) + def entityClassMetaData = new EntityClassMetaDataImpl<>(Client.class) + def entitySQLMetaData = new EntitySQLMetaDataImpl(entityClassMetaData) + def dbExecutor = new DbExecutorImpl() + def runnerJdbc = new TransactionRunnerJdbc(dataSource) + def templateJdbc = new DataTemplateJdbc( + dbExecutor: dbExecutor, + entitySQLMetaData: entitySQLMetaData, + entityClassMetaData: entityClassMetaData + ) + + client = new Client(name: 'Djon') + serviceClient = new DbServiceClientImpl(runnerJdbc, templateJdbc) + } + + @AfterEach + void tearDown() { + client = null + serviceClient = null + } + + @Test + void saveClient() { + def saveClient = serviceClient.saveClient(client) + + assert saveClient.id != null + assert saveClient.name == 'Djon' + } + + @Test + void getClient() { + def saveClient = serviceClient.saveClient(client) + + assert saveClient.id != null + assert saveClient.name == 'Djon' + + saveClient = serviceClient.getClient(saveClient.id) + + assert saveClient.id != null + assert saveClient.name == 'Djon' + } +} \ No newline at end of file diff --git a/hw05-example-gradle/src/test/groovy/ru/otus/homework/EntityClassMetaDataImplTest.groovy b/hw05-example-gradle/src/test/groovy/ru/otus/homework/EntityClassMetaDataImplTest.groovy new file mode 100644 index 0000000..fd5e02a --- /dev/null +++ b/hw05-example-gradle/src/test/groovy/ru/otus/homework/EntityClassMetaDataImplTest.groovy @@ -0,0 +1,55 @@ +package ru.otus.homework + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import ru.otus.homework.model.Client +import ru.otus.homework.mapper.EntityClassMetaDataImpl + +class EntityClassMetaDataImplTest { + EntityClassMetaDataImpl entityClassMetaData + + @BeforeEach + void init() { + entityClassMetaData = new EntityClassMetaDataImpl<>(Client.class) + } + + @AfterEach + void tearDown() { + entityClassMetaData = null + } + + @Test + void getIdField() { + def id = entityClassMetaData.getIdField() + + assert id.name == 'id' + assert id.getType() == Integer + } + + @Test + void getName() { + def name = entityClassMetaData.getName() + + assert name == 'Client' + } + + @Test + void getAllFields() { + def fields = entityClassMetaData.getAllFields() + + assert fields.size() == 2 + assert fields.get(0).name == 'id' + assert fields.get(1).name == 'name' + + } + + @Test + void getFieldsWithoutId() { + def fields = entityClassMetaData.getFieldsWithoutId() + + assert fields.size() == 1 + assert fields.get(0).name == 'name' + + } +} \ No newline at end of file diff --git a/hw05-example-gradle/src/test/groovy/ru/otus/homework/EntitySQLMetaDataImplTest.groovy b/hw05-example-gradle/src/test/groovy/ru/otus/homework/EntitySQLMetaDataImplTest.groovy new file mode 100644 index 0000000..8dbb38b --- /dev/null +++ b/hw05-example-gradle/src/test/groovy/ru/otus/homework/EntitySQLMetaDataImplTest.groovy @@ -0,0 +1,54 @@ +package ru.otus.homework + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import ru.otus.homework.model.Client +import ru.otus.homework.mapper.EntitySQLMetaDataImpl +import ru.otus.homework.mapper.EntityClassMetaDataImpl + +class EntitySQLMetaDataImplTest { + Client client + EntitySQLMetaDataImpl entitySQLMetaData + + @BeforeEach + void init() { + client = new Client(id: 1, name: 'Djon') + def entityClassMetaData = new EntityClassMetaDataImpl<>(Client.class) + entitySQLMetaData = new EntitySQLMetaDataImpl(entityClassMetaData) + } + + @AfterEach + void tearDown() { + client = null + entitySQLMetaData = null + } + + @Test + void getSelectAllSql() { + def sql = entitySQLMetaData.getSelectAllSql() + + assert sql == 'SELECT * FROM Client' + } + + @Test + void getSelectByIdSql() { + def sql = entitySQLMetaData.getSelectByIdSql(client.id) + + assert sql == 'SELECT * FROM Client WHERE id = 1' + } + + @Test + void getInsertSql() { + def sql = entitySQLMetaData.getInsertSql(client) + + assert sql == 'INSERT INTO Client (name) VALUES(?)' + } + + @Test + void getUpdateSql() { + def sql = entitySQLMetaData.getUpdateSql(client) + + assert sql == 'UPDATE Client SET name = ? WHERE id = 1' + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/build.gradle b/hw05-orm-gradle/build.gradle new file mode 100644 index 0000000..0b947d3 --- /dev/null +++ b/hw05-orm-gradle/build.gradle @@ -0,0 +1,20 @@ +plugins { + id 'groovy' +} + +group 'ru.otus.homework' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation localGroovy() + implementation 'com.zaxxer:HikariCP' +} + +tasks.register('copyResources', Copy) { + from "${projectDir}/src/main/resources" + into "${buildDir}/classes/java/main/resources" +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/ReflectionUtility.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/ReflectionUtility.groovy new file mode 100644 index 0000000..0bbd951 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/ReflectionUtility.groovy @@ -0,0 +1,34 @@ +package ru.otus.homework + +import java.lang.reflect.Field +import java.lang.reflect.Constructor +import ru.otus.homework.exception.EntityMetaDataException + +class ReflectionUtility { + + static def getValueFromObjectByField(Field field, object) { + try { + field.setAccessible(true) + field.get(object) + } catch (ex) { + throw new EntityMetaDataException(ex.getMessage()) + } + } + + static void setValue(Field field, object, value) { + try { + field.setAccessible(true) + field.set(object, value) + } catch (ex) { + throw new EntityMetaDataException(ex.getMessage()) + } + } + + static T useConstructor(Constructor constructor) { + try { + constructor.newInstance() + } catch (ex) { + throw new EntityMetaDataException(ex.getMessage()) + } + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/annotation/Id.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/annotation/Id.groovy new file mode 100644 index 0000000..9d4c1ae --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/annotation/Id.groovy @@ -0,0 +1,11 @@ +package ru.otus.homework.annotation + +import java.lang.annotation.Target +import java.lang.annotation.Retention +import java.lang.annotation.ElementType +import java.lang.annotation.RetentionPolicy + +@Target([ElementType.FIELD]) +@Retention(RetentionPolicy.RUNTIME) +@interface Id { +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/datasource/DriverManagerDataSource.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/datasource/DriverManagerDataSource.groovy new file mode 100644 index 0000000..8d4073c --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/datasource/DriverManagerDataSource.groovy @@ -0,0 +1,85 @@ +package ru.otus.homework.datasource + +import java.sql.Connection +import javax.sql.DataSource +import java.sql.SQLException +import java.util.logging.Logger +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource + +class DriverManagerDataSource implements DataSource { + private DataSource dataSourcePool + + DriverManagerDataSource(String url, String user, String pwd, String driver) { + createConnectionPool(url, user, pwd, driver) + } + + @Override + Connection getConnection() throws SQLException { + return dataSourcePool.getConnection() + } + + @Override + Connection getConnection(String username, String password) { + throw new UnsupportedOperationException() + } + + @Override + PrintWriter getLogWriter() { + throw new UnsupportedOperationException() + } + + @Override + void setLogWriter(PrintWriter out) { + throw new UnsupportedOperationException() + + } + + @Override + int getLoginTimeout() { + throw new UnsupportedOperationException() + } + + @Override + void setLoginTimeout(int seconds) { + throw new UnsupportedOperationException() + } + + @Override + Logger getParentLogger() { + throw new UnsupportedOperationException() + } + + @Override + T unwrap(Class iface) { + throw new UnsupportedOperationException() + } + + @Override + boolean isWrapperFor(Class iface) { + throw new UnsupportedOperationException() + } + + private void createConnectionPool(String url, String user, String pwd, String driver) { + def config = new HikariConfig() + config.setJdbcUrl(url) + config.setDriverClassName(driver) + config.setConnectionTimeout(3000) + config.setIdleTimeout(60000) + config.setMaxLifetime(600000) + config.setAutoCommit(false) + config.setMinimumIdle(5) + config.setMaximumPoolSize(10) + config.setPoolName("DemoHiPool") + config.setRegisterMbeans(true) + + config.addDataSourceProperty("cachePrepStmts", "true") + config.addDataSourceProperty("prepStmtCacheSize", "250") + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048") + + config.setUsername(user) + config.setPassword(pwd) + + dataSourcePool = new HikariDataSource(config) + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/DataBaseOperationException.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/DataBaseOperationException.groovy new file mode 100644 index 0000000..5dfbecf --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/DataBaseOperationException.groovy @@ -0,0 +1,8 @@ +package ru.otus.homework.exception + +class DataBaseOperationException extends RuntimeException { + + DataBaseOperationException(String message, Throwable cause) { + super(message, cause) + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/EntityMetaDataException.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/EntityMetaDataException.groovy new file mode 100644 index 0000000..35a00ab --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/EntityMetaDataException.groovy @@ -0,0 +1,8 @@ +package ru.otus.homework.exception + +class EntityMetaDataException extends RuntimeException { + + EntityMetaDataException(String message) { + super(message) + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/ResultMapperException.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/ResultMapperException.groovy new file mode 100644 index 0000000..20eb8d2 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/exception/ResultMapperException.groovy @@ -0,0 +1,8 @@ +package ru.otus.homework.exception + +class ResultMapperException extends RuntimeException { + + ResultMapperException(String message) { + super(message) + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntityClassMetaData.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntityClassMetaData.groovy new file mode 100644 index 0000000..6ee250f --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntityClassMetaData.groovy @@ -0,0 +1,12 @@ +package ru.otus.homework.mapper + +import java.lang.reflect.Field +import java.lang.reflect.Constructor + +interface EntityClassMetaData { + String getName() + Constructor getConstructor() + Field getIdField() + List getAllFields() + List getFieldsWithoutId() +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntityClassMetaDataImpl.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntityClassMetaDataImpl.groovy new file mode 100644 index 0000000..d8fd9b0 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntityClassMetaDataImpl.groovy @@ -0,0 +1,84 @@ +package ru.otus.homework.mapper + +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import java.lang.reflect.Constructor +import ru.otus.homework.annotation.Id +import ru.otus.homework.exception.EntityMetaDataException + +class EntityClassMetaDataImpl implements EntityClassMetaData { + Class clazz + Field idField + List allFields + List withoutIdFields + Constructor constructor + + EntityClassMetaDataImpl(Class clazz) { + this.clazz = clazz + this.idField = findIdField() + this.allFields = findAllFields() + this.withoutIdFields = findWithoutIdFields() + this.constructor = findConstructorWithoutParameters() + } + + @Override + Field getIdField() { + return idField + } + + + @Override + String getName() { + return clazz.getSimpleName() + } + + @Override + List getAllFields() { + return allFields + } + + @Override + List getFieldsWithoutId() { + return withoutIdFields + } + + def findIdField() { + if (checkFieldId()) { + def fields = findAllFields() + .findAll {it.isAnnotationPresent(Id.class)} + .collect() + return fields.first() + } + } + + def findAllFields() { + def fields = clazz.getDeclaredFields() + .findAll { Modifier.isPrivate(it.getModifiers())} + .findAll {!Modifier.isStatic(it.getModifiers())} + .findAll {!Modifier.isTransient(it.getModifiers())} + .collect() + return fields + } + + def findWithoutIdFields() { + if (checkFieldId()) { + def fields = findAllFields() + .findAll {!it.isAnnotationPresent(Id.class)} + .collect() + return fields + } + } + + def findConstructorWithoutParameters() { + return clazz.getConstructor() + } + + def checkFieldId() { + for (def field in clazz.getDeclaredFields()) { + if(field.isAnnotationPresent(Id.class)) { + return true + } + } + throw new EntityMetaDataException("@id not found for class ${getName()}") + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntitySQLMetaData.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntitySQLMetaData.groovy new file mode 100644 index 0000000..35ad73f --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntitySQLMetaData.groovy @@ -0,0 +1,8 @@ +package ru.otus.homework.mapper + +interface EntitySQLMetaData { + String getSelectAllSql() + String getSelectByIdSql(Long id) + String getInsertSql(Object object) + String getUpdateSql(Object object) +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntitySQLMetaDataImpl.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntitySQLMetaDataImpl.groovy new file mode 100644 index 0000000..5d601d8 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/EntitySQLMetaDataImpl.groovy @@ -0,0 +1,46 @@ +package ru.otus.homework.mapper + +class EntitySQLMetaDataImpl implements EntitySQLMetaData { + private final EntityClassMetaData entityClassMetaData + + EntitySQLMetaDataImpl(entityClassMetaData) { + this.entityClassMetaData = entityClassMetaData + } + + @Override + String getSelectAllSql() { + return "SELECT * FROM ${entityClassMetaData.getName()}" + } + + @Override + String getSelectByIdSql(Long id) { + return "SELECT * FROM ${entityClassMetaData.getName()} WHERE ${entityClassMetaData.getIdField().name} = ${id}" + } + + @Override + String getInsertSql(Object object) { + def names = [] + def values = [] + def properties = object.properties + + properties.each {entry -> + if (entityClassMetaData.getFieldsWithoutId().collect {it.name}.contains(entry.key)) { + names << entry.key + values << entry.value + } + } + return "INSERT INTO ${entityClassMetaData.getName()} (${names.join(",")}) VALUES(${values.collect {'?'}.join(",")})" + } + + @Override + String getUpdateSql(Object object) { + def properties = object.properties + return "UPDATE ${entityClassMetaData.getName()} SET ${getUpdateFields(properties)} WHERE ${entityClassMetaData.getIdField().name} = ${properties.get(entityClassMetaData.getIdField().name)}" + } + + def getUpdateFields(properties) { + return properties + .findAll {entry -> entityClassMetaData.getFieldsWithoutId().collect {it.name}.contains(entry.key)} + .collect {entry -> "${entry.key} = ?"}.join(",") + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/ResultListMapper.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/ResultListMapper.groovy new file mode 100644 index 0000000..aeab767 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/mapper/ResultListMapper.groovy @@ -0,0 +1,32 @@ +package ru.otus.homework.mapper + +import java.sql.ResultSet +import java.sql.SQLException +import java.lang.reflect.Field +import java.util.function.Function +import ru.otus.homework.ReflectionUtility + +class ResultListMapper implements Function> { + private final EntityClassMetaData entityClassMetaData + + ResultListMapper(EntityClassMetaData entityClassMetaData) { + this.entityClassMetaData = entityClassMetaData + } + + @Override + List apply(ResultSet resultSet) { + try { + List tList = new ArrayList<>() + while (resultSet.next()) { + T object = ReflectionUtility.useConstructor(entityClassMetaData.getConstructor()) + for (Field f : entityClassMetaData.getAllFields()) { + ReflectionUtility.setValue(f, object, resultSet.getObject(f.getName())) + } + tList.add(object) + } + tList + } catch (SQLException ex) { + List.of(ex.printStackTrace()) + } + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/model/Client.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/model/Client.groovy new file mode 100644 index 0000000..e3cb455 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/model/Client.groovy @@ -0,0 +1,11 @@ +package ru.otus.homework.model + +import groovy.transform.ToString +import ru.otus.homework.annotation.Id + +@ToString(includeFields = true) +class Client { + @Id + Integer id + String name +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/model/Manager.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/model/Manager.groovy new file mode 100644 index 0000000..09eeda7 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/model/Manager.groovy @@ -0,0 +1,12 @@ +package ru.otus.homework.model + +import groovy.transform.ToString +import ru.otus.homework.annotation.Id + +@ToString(includeFields = true) +class Manager { + @Id + Integer no + String label + String param1 +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DataTemplate.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DataTemplate.groovy new file mode 100644 index 0000000..da8d3b7 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DataTemplate.groovy @@ -0,0 +1,8 @@ +package ru.otus.homework.repository + +interface DataTemplate { + def findById(connection, id) + def findAll(connection) + int insert(connection, object) + void update(connection, object) +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DataTemplateJdbc.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DataTemplateJdbc.groovy new file mode 100644 index 0000000..67c5b29 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DataTemplateJdbc.groovy @@ -0,0 +1,43 @@ +package ru.otus.homework.repository + +import groovy.transform.Canonical +import ru.otus.homework.mapper.ResultListMapper +import ru.otus.homework.mapper.EntityClassMetaData +import ru.otus.homework.mapper.EntitySQLMetaData + +@Canonical +class DataTemplateJdbc implements DataTemplate { + private DbExecutor dbExecutor + private EntitySQLMetaData entitySQLMetaData + private EntityClassMetaData entityClassMetaData + + @Override + def findById(connection, id) { + return dbExecutor.executeSelect(connection, entitySQLMetaData.getSelectByIdSql(id), {new ResultListMapper<>(entityClassMetaData).apply(it)}) + } + + @Override + def findAll(connection) { + return dbExecutor.executeSelect(connection, entitySQLMetaData.getSelectAllSql(), {new ResultListMapper<>(entityClassMetaData).apply(it)}) + } + + @Override + int insert(connection, object) { + return dbExecutor.executeStatement(connection, entitySQLMetaData.getInsertSql(object), getListParams(object, entityClassMetaData)) + } + + @Override + void update(connection, object) { + dbExecutor.executeStatement(connection, entitySQLMetaData.getUpdateSql(object), getListParams(object, entityClassMetaData)) + } + + static def getListParams(Object object, EntityClassMetaData entityClassMetaData) { + def values = [] + object.properties.each {entry -> + if (entityClassMetaData.getFieldsWithoutId().collect {it.name}.contains(entry.key)) { + values << entry.value + } + } + return values + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DbExecutor.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DbExecutor.groovy new file mode 100644 index 0000000..f69f316 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DbExecutor.groovy @@ -0,0 +1,8 @@ +package ru.otus.homework.repository + +import java.sql.Connection + +interface DbExecutor { + Long executeStatement(Connection connection, String sql, List params) + Optional executeSelect(Connection connection, String sql, Closure rsHandler) +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DbExecutorImpl.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DbExecutorImpl.groovy new file mode 100644 index 0000000..2121139 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/repository/DbExecutorImpl.groovy @@ -0,0 +1,36 @@ +package ru.otus.homework.repository + +import java.sql.Statement +import java.sql.Connection +import static java.sql.Types.VARCHAR +import ru.otus.homework.exception.DataBaseOperationException + +class DbExecutorImpl implements DbExecutor { + + @Override + Long executeStatement(Connection connection, String sql, List params) { + try (def pst = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + for (def idx = 0; idx < params.size(); idx++) { + pst.setObject(idx + 1, params.get(idx), VARCHAR) + } + pst.executeUpdate() + try (def rs = pst.getGeneratedKeys()) { + rs.next() + return rs.getLong(1) + } + } catch (ex) { + throw new DataBaseOperationException("executeInsert error", ex) + } + } + + @Override + Optional executeSelect(Connection connection, String sql, Closure rsHandler) { + try (def pst = connection.prepareStatement(sql)) { + try (def rs = pst.executeQuery()) { + return Optional.ofNullable(rsHandler.call(rs)) + } + } catch (ex) { + throw new DataBaseOperationException("executeSelect error", ex) + } + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DBServiceClient.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DBServiceClient.groovy new file mode 100644 index 0000000..3ac829b --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DBServiceClient.groovy @@ -0,0 +1,9 @@ +package ru.otus.homework.service + +import ru.otus.homework.model.Client + +interface DBServiceClient { + Client saveClient(Client client) + Client getClient(Long id) + List findAll() +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DBServiceManager.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DBServiceManager.groovy new file mode 100644 index 0000000..456af25 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DBServiceManager.groovy @@ -0,0 +1,9 @@ +package ru.otus.homework.service + +import ru.otus.homework.model.Manager + +interface DBServiceManager { + Manager saveManager(Manager manager) + Manager getManager(Long no) + List findAll() +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DbServiceClientImpl.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DbServiceClientImpl.groovy new file mode 100644 index 0000000..7a765a5 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DbServiceClientImpl.groovy @@ -0,0 +1,53 @@ +package ru.otus.homework.service + +import groovy.util.logging.Slf4j +import ru.otus.homework.model.Client +import ru.otus.homework.repository.DataTemplate +import ru.otus.homework.sessionmanager.TransactionRunner + +@Slf4j +class DbServiceClientImpl implements DBServiceClient { + private final DataTemplate dataTemplate + private final TransactionRunner transactionRunner + + DbServiceClientImpl(TransactionRunner transactionRunner, DataTemplate dataTemplate) { + this.transactionRunner = transactionRunner + this.dataTemplate = dataTemplate + } + + @Override + Client saveClient(Client client) { + transactionRunner.doInTransaction(connection -> { + if (client.getId() == null) { + def clientId = dataTemplate.insert(connection, client) + def createdClient = new Client( + id: clientId, + name: client.getName() + ) + log.info("created client: {}", createdClient) + client = createdClient + } + dataTemplate.update(connection, client) + log.info("updated client: {}", client) + client + }) + } + + @Override + Client getClient(Long id) { + transactionRunner.doInTransaction(connection -> { + def clientOptional = dataTemplate.findById(connection, id) + log.info("client: {}", clientOptional) + clientOptional.get().first() + }) + } + + @Override + List findAll() { + transactionRunner.doInTransaction(connection -> { + def clientList = dataTemplate.findAll(connection) + log.info("clientList: {}", clientList) + clientList.get() + }) + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DbServiceManagerImpl.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DbServiceManagerImpl.groovy new file mode 100644 index 0000000..4a9da30 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/service/DbServiceManagerImpl.groovy @@ -0,0 +1,54 @@ +package ru.otus.homework.service + +import groovy.util.logging.Slf4j +import ru.otus.homework.model.Manager +import ru.otus.homework.repository.DataTemplate +import ru.otus.homework.sessionmanager.TransactionRunner + +@Slf4j +class DbServiceManagerImpl implements DBServiceManager { + private final DataTemplate managerDataTemplate + private final TransactionRunner transactionRunner + + DbServiceManagerImpl(TransactionRunner transactionRunner, DataTemplate managerDataTemplate) { + this.transactionRunner = transactionRunner + this.managerDataTemplate = managerDataTemplate + } + + @Override + Manager saveManager(Manager manager) { + transactionRunner.doInTransaction(connection -> { + if (manager.getNo() == null) { + def managerNo = managerDataTemplate.insert(connection, manager) + def createdManager = new Manager( + no: managerNo, + label: manager.getLabel(), + param1: manager.getParam1() + ) + log.info("created manager: {}", createdManager) + manager = createdManager + } + managerDataTemplate.update(connection, manager) + log.info("updated manager: {}", manager) + manager + }) + } + + @Override + Manager getManager(Long no) { + transactionRunner.doInTransaction(connection -> { + def managerOptional = managerDataTemplate.findById(connection, no) + log.info("manager: {}", managerOptional) + managerOptional.get().first() + }) + } + + @Override + List findAll() { + transactionRunner.doInTransaction(connection -> { + def managerList = managerDataTemplate.findAll(connection) + log.info("managerList: {}", managerList) + managerList.get() + }) + } +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/sessionmanager/TransactionRunner.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/sessionmanager/TransactionRunner.groovy new file mode 100644 index 0000000..33d6204 --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/sessionmanager/TransactionRunner.groovy @@ -0,0 +1,5 @@ +package ru.otus.homework.sessionmanager + +interface TransactionRunner { + T doInTransaction(Closure action) +} \ No newline at end of file diff --git a/hw05-orm-gradle/src/main/groovy/ru/otus/homework/sessionmanager/TransactionRunnerJdbc.groovy b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/sessionmanager/TransactionRunnerJdbc.groovy new file mode 100644 index 0000000..f98c7aa --- /dev/null +++ b/hw05-orm-gradle/src/main/groovy/ru/otus/homework/sessionmanager/TransactionRunnerJdbc.groovy @@ -0,0 +1,40 @@ +package ru.otus.homework.sessionmanager + +import groovy.sql.Sql +import javax.sql.DataSource +import java.util.concurrent.Callable +import ru.otus.homework.exception.DataBaseOperationException + +class TransactionRunnerJdbc implements TransactionRunner { + private final DataSource dataSource + private final Sql connection + + TransactionRunnerJdbc(DataSource dataSource) { + this.dataSource = dataSource + this.connection = new Sql(dataSource) + } + + @Override + T doInTransaction(Closure action) { + return wrapException(() -> { + try (def connection = dataSource.getConnection()) { + try { + def result = action.call(connection) + connection.commit() + return result + } catch (ex) { + connection.rollback() + throw new DataBaseOperationException("doInTransaction ru.otus.homework.exception", ex) + } + } + }) + } + + private T wrapException(Callable action) { + try { + return action.call() + } catch (Exception ex) { + throw new DataBaseOperationException("ru.otus.homework.exception", ex) + } + } +} \ No newline at end of file diff --git a/hw09-back/.micronaut/test-resources/test-resources-port.txt b/hw09-back/.micronaut/test-resources/test-resources-port.txt index 6ee0916..076f2e6 100644 --- a/hw09-back/.micronaut/test-resources/test-resources-port.txt +++ b/hw09-back/.micronaut/test-resources/test-resources-port.txt @@ -1 +1 @@ -51104 \ No newline at end of file +57398 \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 3eda9f2..f022066 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,8 @@ include 'hw01-gradle' include 'hw02-gradle' include 'hw03-gradle' include 'hw04-json-gradle' +include 'hw05-orm-gradle' +include 'hw05-example-gradle' include 'hw06-xlsx-gradle' include 'hw08-dsl-gradle' include 'hw09-back' \ No newline at end of file