Add basic module and redis data with jpa and example
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Sambo Chea 2022-04-11 12:35:47 +07:00
parent bc81cb0988
commit 8c999a9e7c
Signed by: sombochea
GPG Key ID: 3C7CF22A05D95490
14 changed files with 368 additions and 2 deletions

154
README.md
View File

@ -1,16 +1,170 @@
# CUBETIQ Web Modules (Template)
- Setup and Default Web Configuration
- Swagger UI and API's Documentation (SpringFox)
- General Purpose for External and Internal use-cases
- Dockerfile and Docker profile build support
# Modules
- API (Default Module)
### Spring Data
- Add `spring-data-jpa` dependency in `build.gradle.kts`
```kotlin
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
```
- Add `spring-data-redis` dependency in `build.gradle.kts` (Redis + Driver)
```kotlin
implementation("org.springframework.boot:spring-boot-starter-data-redis")
```
### Spring Boot Properties
- Recommend
```yaml
spring:
application:
name: ${APP_NAME:spring-web-api}
app:
data-dir: ${APP_DATA_DIR:${user.home}/${spring.application.name}}
```
- Upload File Properties
```yaml
server:
tomcat:
max-http-form-post-size: ${SERVER_MAX_HTTP_FORM_POST_SIZE:50MB}
spring:
servlet:
multipart:
max-file-size: 256MB
max-request-size: 256MB
enabled: true
```
- Logging
```yaml
logging:
file:
path: ${LOGGING_FILE_PATH:${app.data-dir}/logs/}
name: ${logging.file.path}/app.log
```
### Spring Data Redis
- Redis Properties
```yaml
spring:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:your_password}
```
### Spring Data JPA Properties
- Datasource Enhancement (Default: PostgresSQL)
```yaml
spring:
datasource:
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:org.postgresql.Driver}
url: ${DATA_SOURCE_URL:jdbc:postgresql://localhost:5432/demo}
username: ${POSTGRES_USERNAME:postgres}
password: ${POSTGRES_PASSWORD:postgres}
hikari:
max-lifetime: ${DATA_SOURCE_MAX_LIFETIME:1800000}
connection-timeout: ${DATA_SOURCE_CONNECTION_TIMEOUT:30000}
idle-timeout: ${DATA_SOURCE_IDLE_TIMEOUT:600000}
maximum-pool-size: ${DATA_SOURCE_MAXIMUM_POOL_SIZE:10}
allow-pool-suspension: ${DATA_SOURCE_ALLOW_POOL_SUSPENSION:true}
tomcat:
max_active: ${DATA_SOURCE_TOMCAT_MAX_ACTIVE:100}
max_idle: ${DATA_SOURCE_TOMCAT_MAX_IDLE:10}
min-idle: ${DATA_SOURCE_TOMCAT_MIN_IDLE:10}
initial_size: ${DATA_SOURCE_TOMCAT_INITIAL_SIZE:10}
remove_abandoned: ${DATA_SOURCE_TOMCAT_REMOVE_ABANDONED:true}
jpa:
database-platform: ${JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}
show-sql: ${JPA_SHOW_SQL:false}
hibernate:
ddl-auto: ${JPA_HIBERNATE_DDL_AUTO:update}
properties:
hibernate:
dialect: ${JPA_HIBERNATE_DIALECT:org.hibernate.dialect.PostgreSQLDialect}
open-in-view: ${JPA_OPEN_IN_VIEW:false}
```
- PostgreSQL
```yaml
spring:
datasource:
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:org.postgresql.Driver}
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:demo}
username: ${POSTGRES_USERNAME:postgres}
password: ${POSTGRES_PASSWORD:postgres}
```
- MySQL
```yaml
spring:
datasource:
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:com.mysql.cj.jdbc.Driver}
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DB:demo}?createDatabaseIfNotExist=true&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: ${MYSQL_USERNAME:demo}
password: ${MYSQL_PASSWORD:demo}
jpa:
database-platform: ${JPA_DATABASE_PLATFORM:org.hibernate.dialect.MySQLDialect}
properties:
hibernate:
dialect: ${JPA_HIBERNATE_DIALECT:org.hibernate.dialect.MySQLDialect}
```
- H2 (Embedded)
-
```yaml
spring:
datasource:
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:org.h2.Driver}
url: jdbc:h2:file:${H2_DB_PATH:./data/db};DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: ${H2_USERNAME:sa}
password: ${H2_PASSWORD:password}
jpa:
database-platform: ${JPA_DATABASE_PLATFORM:org.hibernate.dialect.H2Dialect}
h2:
console:
enabled: ${H2_CONSOLE_ENABLED:true}
```
- Avoid the Lazy Initialization Problem
```yaml
spring:
jpa:
properties:
hibernate:
enable_lazy_load_no_trans: ${HIBERNATE_LAZY_NO_TRANS:true}
```
# Contributors
- Sambo Chea <sombochea@cubetiqs.com>
### Language and Framework
- Spring Boot: 2.6.6
- Kotlin: 1.6.20
- Gradle: 7.4.1

View File

@ -5,6 +5,7 @@ plugins {
id("io.spring.dependency-management")
kotlin("jvm")
kotlin("plugin.spring")
kotlin("plugin.jpa")
}
val kotlinVersion = "1.6.20"
@ -32,6 +33,10 @@ springBoot {
}
dependencies {
// Spring Data JPA (Required for Database Layer)
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
// Migrating from SpringFox
implementation("org.springdoc:springdoc-openapi-ui:1.6.7")
@ -44,6 +49,10 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
runtimeOnly("com.h2database:h2")
runtimeOnly("org.postgresql:postgresql")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}

View File

@ -0,0 +1,20 @@
package com.cubetiqs.web.modules.redis
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
@RedisModule
@Configuration
class RedisConfig @Autowired constructor(
private val connectionFactory: RedisConnectionFactory,
) {
@Bean
fun redisTemplate(): RedisTemplate<String, RedisKVModel> {
val template = RedisTemplate<String, RedisKVModel>()
template.setConnectionFactory(connectionFactory)
return template
}
}

View File

@ -0,0 +1,31 @@
package com.cubetiqs.web.modules.redis
import com.cubetiqs.web.util.RouteConstants
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.web.bind.annotation.*
@RedisModule
@Tag(name = "Redis Controller")
@RestController
@RequestMapping(RouteConstants.INDEX + "redis")
class RedisController @Autowired constructor(
private val redisTemplate: RedisTemplate<String, RedisKVModel>,
) {
@GetMapping("/{key}")
fun getAll(
@PathVariable("key") key: String,
): Collection<RedisKVModel?> {
return redisTemplate.opsForValue().multiGet(listOf(key)) ?: listOf()
}
@PostMapping("/{key}")
fun set(
@PathVariable("key") key: String,
@RequestBody body: RedisKVModel
): RedisKVModel {
redisTemplate.opsForValue().set(key, body)
return body
}
}

View File

@ -0,0 +1,14 @@
package com.cubetiqs.web.modules.redis
import java.io.Serializable
data class RedisKVModel(
var key: String? = null,
var value: Any? = null,
) : Serializable {
companion object {
fun create(key: String, value: Any): RedisKVModel {
return RedisKVModel(key, value)
}
}
}

View File

@ -0,0 +1,6 @@
package com.cubetiqs.web.modules.redis
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
@ConditionalOnProperty(name = ["spring.redis.enabled"], havingValue = "true")
annotation class RedisModule

View File

@ -0,0 +1,60 @@
package com.cubetiqs.web.modules.user
import com.cubetiqs.web.util.RouteConstants
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.tags.Tag
import org.springdoc.core.converters.models.PageableAsQueryParam
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.web.bind.annotation.*
import java.util.*
@UserModule
@Tag(name = "User Controller")
@RestController
@RequestMapping(RouteConstants.INDEX + "user")
class UserController @Autowired constructor(
private val userRepository: UserRepository,
) {
@GetMapping
@PageableAsQueryParam
fun getAll(
@Parameter(hidden = true)
pageable: Pageable?,
): Page<UserEntity> {
return userRepository.findAll(pageable ?: Pageable.unpaged())
}
@ResponseStatus(value = org.springframework.http.HttpStatus.CREATED)
@PostMapping
fun create(
@RequestBody body: UserEntity
): UserEntity {
return userRepository.save(body)
}
@ResponseStatus(value = org.springframework.http.HttpStatus.OK)
@PutMapping("/{id}")
fun update(
@PathVariable id: String,
@RequestBody body: UserEntity
): UserEntity {
val user = userRepository.findById(UUID.fromString(id)).orElseThrow {
throw IllegalArgumentException("User not found")
}
body.id = user.id
return userRepository.save(body)
}
@ResponseStatus(value = org.springframework.http.HttpStatus.NO_CONTENT)
@DeleteMapping("/{id}")
fun delete(
@PathVariable id: String,
) {
val user = userRepository.findById(UUID.fromString(id)).orElseThrow {
throw IllegalArgumentException("User not found")
}
userRepository.delete(user)
}
}

View File

@ -0,0 +1,30 @@
package com.cubetiqs.web.modules.user
import org.hibernate.Hibernate
import java.io.Serializable
import java.util.*
import javax.persistence.*
@Entity
@Table(name = "user")
open class UserEntity(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
open var id: UUID? = null,
@Column(name = "name", length = 50)
open var name: String? = null,
@Column(name = "username", length = 50, unique = true)
open var username: String? = null,
) : Serializable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false
other as UserEntity
return id != null && id == other.id
}
override fun hashCode(): Int = javaClass.hashCode()
}

View File

@ -0,0 +1,6 @@
package com.cubetiqs.web.modules.user
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
@ConditionalOnProperty(name = ["module.user.enabled", "spring.datasource.enabled"], havingValue = "true")
annotation class UserModule

View File

@ -0,0 +1,9 @@
package com.cubetiqs.web.modules.user
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.util.*
@UserModule
@Repository
interface UserRepository : JpaRepository<UserEntity, UUID>

View File

@ -1,8 +1,22 @@
spring:
profiles:
active: ${APP_PROFILE:dev}
active: ${APP_PROFILE:demo}
application:
name: cubetiq-api-service
redis:
enabled: ${REDIS_ENABLED:false}
host: ${REDIS_HOST:localhost}
password: ${REDIS_PASSWORD:null}
datasource:
enabled: ${DATASOURCE_ENABLED:false}
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:org.h2.Driver}
url: jdbc:h2:file:${H2_DB_PATH:${cubetiq.app.data-dir}/data/db};DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: ${H2_USERNAME:sa}
password: ${H2_PASSWORD:password}
module:
user:
enabled: ${MODULE_USER_ENABLED:false}
cubetiq:
app:

View File

@ -0,0 +1,8 @@
----------------------- CUBEIQ @sombochea ---------------------------
________ _____ ___ __ ______
__ ___/________ ___________(_)_______ _______ _ __ | / /_____ ___ /_
_____ \ ___ __ \__ ___/__ / __ __ \__ __ `/ __ | /| / / _ _ \__ __ \
____/ / __ /_/ /_ / _ / _ / / /_ /_/ / __ |/ |/ / / __/_ /_/ /
/____/ _ .___/ /_/ /_/ /_/ /_/ _\__, / ____/|__/ \___/ /_.___/
/_/ /____/
Spring Boot Version: ${spring-boot.formatted-version}

View File

@ -5,7 +5,7 @@ plugins {
id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false
kotlin("jvm") version "1.6.20" apply false
kotlin("plugin.spring") version "1.6.20" apply false
// kotlin("plugin.jpa") version "1.6.10" apply false
kotlin("plugin.jpa") version "1.6.20" apply false
}
allprojects {

View File

@ -15,8 +15,13 @@ metadata:
namespace: spring-web-dev
name: spring-web-dev-secret
stringData:
REDIS_HOST: redis-service
REDIS_PASSWORD: demo
POSTGRES_USER: demo
POSTGRES_DB: demo
POSTGRES_PASSWORD: demo
APP_DATA_DIR: /opt/cubetiq/data
MODULE_USER_ENABLED: true
DATASOURCE_ENABLED: true
REDIS_ENABLED: true
type: Opaque