created spring boot starter for exposed (#582)

* created spring boot starter for exposed

uses spring-boot-starter-data-jdbc

ability to autogenerate-ddl using exposed SchemaUtils.Create for any packages that extend Table()

spring.exposed.exclude-packages allows you to exclude certain packages

uses spring-transaction to enable @Transactional annotation

* updated spring-boot version to the latest

* added a readme on how to use this library
This commit is contained in:
Trey Bastian
2019-06-26 19:10:39 +01:00
committed by Andrey.Tarashevskiy
parent 454959528e
commit 1b951d24cd
12 changed files with 312 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
# Exposed Spring Boot Starter
This is a starter for [Spring Boot](https://spring.io/projects/spring-boot) to utilize [Exposed](https://github.com/JetBrains/Exposed) as the ORM instead of [Hibernate](https://hibernate.org/)
## Getting Started
This starter will give you the latest version of [Exposed](https://github.com/JetBrains/Exposed) and the spring-transaction library along with [Spring Boot Data Starter JDBC](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jdbc)
### Maven
```mxml
<repositories>
<repository>
<id>jcenter</id>
<name>jcenter</name>
<url>http://jcenter.bintray.com</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jetbrains.exposed</groupId>
<artifactId>exposed-spring-boot-starter</artifactId>
<version>0.15.1</version>
</dependency>
</dependencies>
```
### Gradle
```groovy
repositories {
jcenter()
}
dependencies {
implementation 'org.jetbrains.exposed:exposed-spring-boot-starter:0.15.1'
}
```
## Setting up a database connection
This starter utilizes spring-boot-starter-data-jdbc so all properties that you are used to for setting up a database in spring are applicable here.
### application.properties (h2 example)
```properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClasName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
```
## Automatic Schema Creation
This starter will create the database schema if enabled automatically using any class that extends `org.jetbrains.exposed.sql.Table`
Sometimes you will want to exclude packages from that list, we have included the property `spring.exposed.excluded-packages` which will exclude everything under the provided package
### application.properties
```properties
spring.exposed.generate-ddl = true
spring.exposed.excluded-packages = com.example.models.ignore,com.example.utils
```

View File

@@ -0,0 +1,51 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
import tanvd.kosogor.proxy.publishJar
plugins {
kotlin("jvm") apply true
}
repositories {
jcenter()
maven("https://dl.bintray.com/jfrog/jfrog-jars")
}
dependencies {
api(project(":exposed"))
api(project(":spring-transaction"))
api("org.springframework.boot", "spring-boot-starter-data-jdbc", "2.1.6.RELEASE")
api("org.springframework.boot", "spring-boot-autoconfigure", "2.1.6.RELEASE")
compileOnly("org.springframework.boot", "spring-boot-configuration-processor", "2.1.6.RELEASE")
testImplementation("org.springframework.boot", "spring-boot-starter-test", "2.1.6.RELEASE")
testImplementation("com.h2database", "h2", "1.4.199")
}
publishJar {
publication {
artifactId = "exposed-spring-boot-starter"
}
bintray {
username = project.properties["bintrayUser"]?.toString() ?: System.getenv("BINTRAY_USER")
secretKey = project.properties["bintrayApiKey"]?.toString() ?: System.getenv("BINTRAY_API_KEY")
repository = "exposed"
info {
githubRepo = "https://github.com/JetBrains/Exposed.git"
vcsUrl = "https://github.com/JetBrains/Exposed.git"
userOrg = "kotlin"
license = "Apache-2.0"
}
}
}
tasks.withType(Test::class.java) {
jvmArgs = listOf("-XX:MaxPermSize=256m")
testLogging {
events.addAll(listOf(TestLogEvent.PASSED, TestLogEvent.FAILED, TestLogEvent.SKIPPED))
showStandardStreams = true
exceptionFormat = TestExceptionFormat.FULL
}
}

View File

@@ -0,0 +1,43 @@
package org.jetbrains.exposed.spring
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.Table
import org.slf4j.LoggerFactory
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.boot.autoconfigure.AutoConfigurationPackages
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
import org.springframework.core.Ordered
import org.springframework.core.type.filter.AssignableTypeFilter
import org.springframework.core.type.filter.RegexPatternTypeFilter
import org.springframework.transaction.annotation.Transactional
import java.util.regex.Pattern
open class DatabaseInitializer(private val applicationContext: ApplicationContext, private val excludedPackages: List<String>) : ApplicationRunner, Ordered {
override fun getOrder(): Int = DATABASE_INITIALIZER_ORDER
companion object {
const val DATABASE_INITIALIZER_ORDER = 0
}
private val logger = LoggerFactory.getLogger(javaClass)
@Transactional
override fun run(args: ApplicationArguments?) {
val exposedTables = discoverExposedTables(applicationContext, excludedPackages)
logger.info("Schema generation for tables '{}'", exposedTables.map { it.tableName })
logger.info("ddl {}", exposedTables.map { it.ddl }.joinToString())
SchemaUtils.create(*exposedTables.toTypedArray())
}
}
fun discoverExposedTables(applicationContext: ApplicationContext, excludedPackages: List<String>): List<Table> {
val provider = ClassPathScanningCandidateComponentProvider(false)
provider.addIncludeFilter(AssignableTypeFilter(Table::class.java))
excludedPackages.forEach { provider.addExcludeFilter(RegexPatternTypeFilter(Pattern.compile(it.replace(".", "\\.") + ".*"))) }
val packages = AutoConfigurationPackages.get(applicationContext)
val components = packages.map { provider.findCandidateComponents(it) }.flatten()
return components.map { Class.forName(it.beanClassName).kotlin.objectInstance as Table }
}

View File

@@ -0,0 +1,30 @@
package org.jetbrains.exposed.spring.autoconfigure
import org.jetbrains.exposed.spring.DatabaseInitializer
import org.jetbrains.exposed.spring.SpringTransactionManager
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.AutoConfigureAfter
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.transaction.annotation.EnableTransactionManagement
import javax.sql.DataSource
@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration::class)
@EnableTransactionManagement
open class ExposedAutoConfiguration(private val applicationContext: ApplicationContext) {
@Value("\${spring.exposed.excluded-packages:}#{T(java.util.Collections).emptyList()}")
private lateinit var excludedPackages: List<String>
@Bean
open fun springTransactionManager(datasource: DataSource) = SpringTransactionManager(datasource)
@Bean
@ConditionalOnProperty("spring.exposed.generate-ddl", havingValue="true", matchIfMissing = false)
open fun databaseInitializer() = DatabaseInitializer(applicationContext, excludedPackages)
}

View File

@@ -0,0 +1,15 @@
{
"properties": [
{
"name": "spring.exposed.generate-ddl",
"type": "java.lang.Boolean",
"description": "Auto generate the database schema.",
"defaultValue": false
},
{
"name": "spring.exposed.excluded-packages",
"type": "java.util.List",
"description": "Packages to exclude from schema generation."
}
]
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.jetbrains.exposed.spring.autoconfigure.ExposedAutoConfiguration

View File

@@ -0,0 +1,12 @@
package org.jetbrains.exposed.spring
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
@SpringBootApplication
open class Application {
open fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
}

View File

@@ -0,0 +1,35 @@
package org.jetbrains.exposed.spring
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.spring.tables.TestTable
import org.jetbrains.exposed.spring.tables.ignore.IgnoreTable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.ApplicationContext
import org.springframework.test.context.junit4.SpringRunner
@RunWith(SpringRunner::class)
@SpringBootTest(classes = [org.jetbrains.exposed.spring.Application::class],
properties = ["spring.autoconfigure.exclude=org.jetbrains.exposed.spring.autoconfigure.ExposedAutoConfiguration"])
open class DatabaseInitializerTest {
@Autowired
private lateinit var applicationContext: ApplicationContext
@Test(expected = ExposedSQLException::class)
fun `should create schema for TestTable and not for IgnoreTable`() {
Database.connect("jdbc:h2:mem:test", "org.h2.Driver")
transaction {
DatabaseInitializer(applicationContext, listOf("org.jetbrains.exposed.spring.tables.ignore")).run(null)
Assert.assertEquals(0, TestTable.selectAll().count())
IgnoreTable.selectAll().count()
}
}
}

View File

@@ -0,0 +1,54 @@
package org.jetbrains.exposed.spring.autoconfigure
import org.jetbrains.exposed.spring.DatabaseInitializer
import org.jetbrains.exposed.spring.SpringTransactionManager
import org.jetbrains.exposed.spring.tables.TestTable
import org.jetbrains.exposed.sql.selectAll
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.transaction.annotation.Transactional
@RunWith(SpringRunner::class)
@SpringBootTest(classes = [org.jetbrains.exposed.spring.Application::class],
properties = ["spring.datasource.url=jdbc:h2:mem:test", "spring.datasource.driver-class-name=org.h2.Driver"])
open class ExposedAutoConfigurationTest {
@Autowired(required = false)
private var springTransactionManager: SpringTransactionManager? = null
@Autowired(required = false)
private var databaseInitializer: DatabaseInitializer? = null
@Test
fun `should initialize the database connection`() {
Assert.assertNotNull(springTransactionManager)
}
@Test
fun `should not create schema`() {
Assert.assertNull(databaseInitializer)
}
}
@RunWith(SpringRunner::class)
@SpringBootTest(classes = [org.jetbrains.exposed.spring.Application::class],
properties = ["spring.datasource.url=jdbc:h2:mem:test", "spring.datasource.driver-class-name=org.h2.Driver","spring.exposed.generate-ddl=true"])
open class ExposedAutoConfigurationTestAutoGenerateDDL {
@Autowired(required = false)
private var springTransactionManager: SpringTransactionManager? = null
@Test
fun `should initialize the database connection`() {
Assert.assertNotNull(springTransactionManager)
}
@Test @Transactional
open fun `should create schema`() {
Assert.assertEquals(0, TestTable.selectAll().count())
}
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.exposed.spring.tables
import org.jetbrains.exposed.dao.IntIdTable
object TestTable: IntIdTable("test_table") {
var name = varchar("name", 100)
}

View File

@@ -0,0 +1,7 @@
package org.jetbrains.exposed.spring.tables.ignore
import org.jetbrains.exposed.dao.IntIdTable
object IgnoreTable: IntIdTable("ignore_table") {
var name = varchar("name", 100)
}

View File

@@ -1,3 +1,4 @@
rootProject.name = "exposed"
include("exposed")
include("spring-transaction")
include("exposed-spring-boot-starter")