I am trying to integrate FF4J into my spring boot application project however it looks like there is some issue with driver or FF4J and the db schema is not created.
My configuration file:
#Configuration
#ConditionalOnClass(FF4j::class)
class FF4JConfiguration {
#field:Value("\${spring.ff4j.datasource.url}")
private lateinit var driverUrl: String
#field:Value("\${spring.datasource.driver-class-name}")
private lateinit var driverClass: String
#field:Value("\${spring.datasource.username}")
private lateinit var user: String
#field:Value("\${spring.datasource.password}")
private lateinit var password: String
#Bean
fun getFF4j(): FF4j {
val source = MysqlDataSource()
source.setURL(driverUrl)
source.password = password
source.user = user
val fF4j = FF4j()
fF4j.featureStore = FeatureStoreSpringJdbc().apply { setDataSource(source) }
fF4j.propertiesStore = PropertyStoreSpringJdbc().apply { setDataSource(source) }
fF4j.eventRepository = JdbcEventRepository(source)
fF4j.audit(true)
fF4j.autoCreate(true)
return fF4j
}
}
When I debug this is how the datasource looks like:
but I do get this BadSqlGrammarException and I have no idea why it cannot create SQL tables for me in the database. The server is running and database is fine because I have other tables as well.
My build.gradle.kts
implementation("org.ff4j:ff4j-core:1.8.12")
implementation("org.ff4j:ff4j-web:1.8.12")
implementation("org.ff4j:ff4j-store-springjdbc:1.8.12")
implementation("mysql:mysql-connector-java:8.0.30")
Any ideas why this happens?
This is fine. FF4J does not create schema automatically for you. You have to create it yourself by doing so:
fF4j.featureStore = JdbcFeatureStore().apply {
dataSource = source
createSchema()
}
fF4j.propertiesStore = JdbcPropertyStore().apply {
dataSource = source
createSchema()
}
fF4j.eventRepository = JdbcEventRepository(source).apply { createSchema() }
and you will be good to go. Most likely the exception you got is because the ff4j_features table does not exist. Now it will create the schema and all tables and features should working fine
Related
I want to write integration test for my microservice currently using Kotlin, Jooq and R2dbc at repository level.
I want my test to work in R2dbc mode as well, but for some reason getting this exception:
Caused by: org.testcontainers.containers.JdbcDatabaseContainer$NoDriverFoundException: Could not get Driver
at org.testcontainers.containers.JdbcDatabaseContainer.getJdbcDriverInstance(JdbcDatabaseContainer.java:187)
at org.testcontainers.containers.JdbcDatabaseContainer.createConnection(JdbcDatabaseContainer.java:209)
at org.testcontainers.containers.JdbcDatabaseContainer.waitUntilContainerStarted(JdbcDatabaseContainer.java:147)
at org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:466)
... 10 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
Probably, I have to point somewhere that I want to use r2dbc only, not jdbc? I've seen the specs but not sure whether I applied TC_INITSCRIPT and TC_IMAGE_TAG correctly.
I don't use Spring Data r2dbc (jooq only), that's why ResourceDatabasePopulator is not an option for me.
My test looks like:
#SpringBootTest(classes = [UserServiceApp::class])
#ActiveProfiles(profiles = ["test"])
#AutoConfigureWebTestClient
class UserServiceAppIT(#Autowired val client: WebTestClient) {
#Nested
inner class Find {
#Test
#DisplayName("Find existing user by id")
fun `existing user credentials returns OK`() {
val expectedUser = getCredentialsUser() //this is a class with expected data
val response = client.get()
.uri("/user/2") //this is my endpoint
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk
.expectBody(UserCredentialsModel::class.java)
.returnResult()
.responseBody
assertThat(response)
.isNotNull
.isEqualTo(expectedUser)
}
}
Test config in yaml file:
server.port: 8080
spring:
application:
name: User Service Test
r2dbc:
url: r2dbc:tc:mysql:///pharmacy?TC_IMAGE_TAG=8.0.26&TC_INITSCRIPT=classpath/resources/init.sql
password: root
username: root
pool:
initial-size: 1
max-size: 10
max-idle-time: 30m
Dependencies (gradle):
buildscript {
ext {
springDependencyVersion = '1.0.11.RELEASE'
springBootVersion = '2.5.3'
kotlinVersion = '1.5.0'
jooqPluginVersion = '6.0'
springdocVersion = '1.5.10'
r2dbcMySQLVersion = '0.8.2.RELEASE'
r2dbcPoolVersion = '0.8.7.RELEASE'
mockKVersion = '1.12.0'
kotestVersion = '4.4.3'
kotlinJsonVersion = '1.2.1'
kotlinDateVersion = '0.2.1'
testcontainersVersion = '1.16.0'
}
}
It is easy to integrate Jooq with R2dbc.
#Configuration
class JooqConfig {
#Bean
fun dslContext(connectionFactory: ConnectionFactory) =
using(TransactionAwareConnectionFactoryProxy(connectionFactory), SQLDialect.POSTGRES)
}
NOTE: Do not include Jooq starter if you are using Spring 2.7.x. The Jooq autoconfiguration only supports Jdbc.
An example using Jooq.
class PostRepositoryImpl(private val dslContext: DSLContext) : PostRepositoryCustom {
override fun findByKeyword(title: String): Flow<PostSummary> {
val sql = dslContext
.select(
POSTS.ID,
POSTS.TITLE,
field("count(comments.id)", SQLDataType.BIGINT)
)
.from(
POSTS
.leftJoin(COMMENTS.`as`("comments"))
.on(COMMENTS.POST_ID.eq(POSTS.ID))
)
.where(
POSTS.TITLE.like("%$title%")
.and(POSTS.CONTENT.like("%$title%"))
.and(COMMENTS.CONTENT.like("%$title%"))
)
.groupBy(POSTS.ID)
return Flux.from(sql)
.map { r -> PostSummary(r.value1(), r.value2(), r.value3()) }
.asFlow();
}
override suspend fun countByKeyword(title: String): Long {
val sql = dslContext
.select(
DSL.field("count(distinct(posts.id))", SQLDataType.BIGINT)
)
.from(
POSTS
.leftJoin(COMMENTS.`as`("comments"))
.on(COMMENTS.POST_ID.eq(POSTS.ID))
)
.where(
POSTS.TITLE.like("%$title%")
.and(POSTS.CONTENT.like("%$title%"))
.and(COMMENTS.CONTENT.like("%$title%"))
)
return Mono.from(sql).map { it.value1() ?: 0 }.awaitSingle()
}
}
TestContainers database requires a Jdbc driver, add MySQL Jdbc driver with testcontainter into your test scope.
The following is an example using Postgres and Testcontainers.
#OptIn(ExperimentalCoroutinesApi::class)
#Testcontainers
#DataR2dbcTest()
#Import(JooqConfig::class, R2dbcConfig::class)
class PostRepositoriesTest {
companion object {
private val log = LoggerFactory.getLogger(PostRepositoriesTest::class.java)
#Container
val postgreSQLContainer = PostgreSQLContainer("postgres:12")
.withCopyFileToContainer(
MountableFile.forClasspathResource("/init.sql"),
"/docker-entrypoint-initdb.d/init.sql"
)
#JvmStatic
#DynamicPropertySource
fun registerDynamicProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.r2dbc.url") {
"r2dbc:postgresql://${postgreSQLContainer.host}:${postgreSQLContainer.firstMappedPort}/${postgreSQLContainer.databaseName}"
}
registry.add("spring.r2dbc.username") { postgreSQLContainer.username }
registry.add("spring.r2dbc.password") { postgreSQLContainer.password }
}
}
#Autowired
lateinit var postRepository: PostRepository
#Autowired
lateinit var dslContext: DSLContext
#BeforeEach
fun setup() = runTest {
log.info(" clear sample data ...")
val deletedPostsCount = Mono.from(dslContext.deleteFrom(POSTS)).awaitSingle()
log.debug(" deletedPostsCount: $deletedPostsCount")
}
#Test
fun `query sample data`() = runTest {
log.debug(" add new sample data...")
val insertPostSql = dslContext.insertInto(POSTS)
.columns(POSTS.TITLE, POSTS.CONTENT)
.values("jooq test", "content of Jooq test")
.returningResult(POSTS.ID)
val postId = Mono.from(insertPostSql).awaitSingle()
log.debug(" postId: $postId")
val insertCommentSql = dslContext.insertInto(COMMENTS)
.columns(COMMENTS.POST_ID, COMMENTS.CONTENT)
.values(postId.component1(), "test comments")
.values(postId.component1(), "test comments 2")
val insertedCount = Mono.from(insertCommentSql).awaitSingle()
log.info(" insertedCount: $insertedCount")
val querySQL = dslContext
.select(
POSTS.TITLE,
POSTS.CONTENT,
multiset(
select(COMMENTS.CONTENT)
.from(COMMENTS)
.where(COMMENTS.POST_ID.eq(POSTS.ID))
).`as`("comments")
)
.from(POSTS)
.orderBy(POSTS.CREATED_AT)
Flux.from(querySQL).asFlow()
.onEach { log.info("querySQL result: $it") }
.collect()
val posts = postRepository.findByKeyword("test").toList()
posts shouldNotBe null
posts.size shouldBe 1
posts[0].commentsCount shouldBe 2
postRepository.countByKeyword("test") shouldBe 1
}
// other tests
My example project is based Postgres, R2dbc, And Spring Data R2dbc: https://github.com/hantsy/spring-r2dbc-sample/blob/master/jooq-kotlin-co-gradle
For my project I want to download from an API and store this information in a map. Furthermore I want to have the map as a bean in another class. I suspect the API to update regularly so I have set a #Schedule for downloading the XML file from the API.
To the problem... How can I update the map with the information from the API every time the XML is downloaded. I do not want to reboot the application each time.
I am very new to the Spring framework so if there is a more elegant method to do this please let me know.
data class DataContainer(val dictionary: MutableMap<String, String>)
#Configuration
#Component
class DownloadRenhold {
var dict: MutableMap<String, String> = xmlToDict("/renhold.xml")
val dataContainer: DataContainer
#Bean
get() = DataContainer(dict)
fun download(link: String, path: String) {
URL(link).openStream().use { input ->
FileOutputStream(File(path)).use { output ->
input.copyTo(output)
}
}
}
#Scheduled(fixedRate = 5000)
fun scheduledDL() {
download("www.link.com","src/main/resources/renhold.xml")
dict = xmlToDict("/renhold.xml")
}
class Controller {
#GetMapping(value = ["/{orgnummer}"]) // #RequestMapping(value="/",method=RequestMethod.GET)
fun orgNrRequest(#PathVariable("orgnummer") nr: String): String? {
var actx = AnnotationConfigApplicationContext(DownloadRenhold::class.java)
var dataContainer = actx.getBean(DataContainer::class.java)
return dataContainer.dictionary[nr]
}
```
I would suggest to not have DataContainer as a bean directly. Instead inject DownRenhold into Controller as a singleton bean. Something along these lines:
// No need to make this class a Configuration. Plain Component would suffice.
// #Configuration
#Component
class DownloadRenhold {
var _dataContainer: DataContainer = null
var dataContainer: DataContainer
get() = _dataContainer
#Scheduled(fixedRate = 5000)
fun scheduledDL() {
_dataContainer = // do your download thing and create a DataContainer instance.
}
}
class Controller {
#Autowired
var dataProvider: DownloadRenhold
#GetMapping(value = ["/{orgnummer}"])
#RequestMapping(value="/",method=RequestMethod.GET)
fun orgNrRequest(#PathVariable("orgnummer") nr: String): String? {
dataProvider.dataContainer // access the current data container
}
I'm trying to get a Kotlin function to operate transactionally in Spring Boot, and I've looked at several sources for information, such as https://codete.com/blog/5-common-spring-transactional-pitfalls/ and Spring #Transaction method call by the method within the same class, does not work?. I believe I have the prerequisites necessary for the #Transactional annotation to work - the function is public and being invoked externally, if my understanding is correct. My code currently looks like this:
interface CreateExerciseInstance {
operator fun invoke(input: CreateExerciseInstanceInput): OpOutcome<CreateExerciseInstanceOutput>
}
#Component
class CreateExerciseInstanceImpl constructor(
private val exerciseInstanceRepository: ExerciseInstanceRepository, // #Repository
private val activityInstanceRepository: ActivityInstanceRepository, // #Repository
private val exerciseInstanceStepRepository: ExerciseInstanceStepRepository // #Repository
) : CreateExerciseInstance {
#Suppress("TooGenericExceptionCaught")
#Transactional
override fun invoke(input: CreateExerciseInstanceInput): OpOutcome<CreateExerciseInstanceOutput> {
...
val exerciseInstanceRecord = ... // no in-place modification of repository data
val activityInstanceRecords = ...
val exerciseInstanceStepRecords = ...
return try {
exerciseInstanceRepository.save(exerciseInstanceRecord)
activityInstanceRepository.saveAll(activityInstanceRecords)
exerciseInstanceStepRepository.saveAll(exerciseInstanceStepRecords)
Outcome.Success(...)
} catch (e: Exception) {
Outcome.Failure(...)
}
}
}
My test currently looks like this:
#ExtendWith(SpringExtension::class)
#SpringBootTest
#Transactional
class CreateExerciseInstanceTest {
#Autowired
private lateinit var exerciseInstanceRepository: ExerciseInstanceRepository
#Autowired
private lateinit var exerciseInstanceStepRepository: ExerciseInstanceStepRepository
#Autowired
private lateinit var activityInstanceRepository: ActivityInstanceRepository
#Test
fun `does not commit to exercise instance or activity repositories when exercise instance step repository throws exception`() {
... // data setup
val exerciseInstanceStepRepository = mockk<ExerciseInstanceStepRepository>()
val exception = Exception("Something went wrong")
every { exerciseInstanceStepRepository.save(any<ExerciseInstanceStepRecord>()) } throws exception
val createExerciseInstance = CreateExerciseInstanceImpl(
exerciseInstanceRepository = exerciseInstanceRepository,
activityInstanceRepository = activityInstanceRepository,
exerciseInstanceStepRepository = exerciseInstanceStepRepository
)
val outcome = createExerciseInstance(...)
assert(outcome is Outcome.Failure)
val exerciseInstances = exerciseInstanceRepository.findAll()
val activityInstances = activityInstanceRepository.findAll()
assertThat(exerciseInstances.count()).isEqualTo(0)
assertThat(activityInstances.count()).isEqualTo(0)
}
}
The test fails with:
org.opentest4j.AssertionFailedError:
Expecting:
<1>
to be equal to:
<0>
but was not.
at assertThat(exerciseInstances.count()).isEqualTo(0). Is the function actually non-public or being invoked internally? Have I missed some other prerequisite?
This test doesn't say anything about your component not being transactional.
First, you create an instance yourself rather than using the one created by Spring. So Spring knows nothing about this instance, and can't possibly warp it into a transactional proxy.
Second, the component doesn't throw any runtime exception, So Spring doesn't rollback the transaction.
How to do unit testing of Room Database with the help of Roboeletric?
I don't want to do instrumentation testing.
From what I can tell it can be done like this
//#RunWith(AndroidJUnit4::class)
#RunWith(RobolectricTestRunner::class)
class WordDaoTest {
private lateinit var wordRoomDatabase: WordRoomDatabase
private lateinit var wordDao: WordDao
#get:Rule
var instantTaskExecutor = InstantTaskExecutorRule()
#Before
fun createDb() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
wordRoomDatabase = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java).allowMainThreadQueries().build()
wordDao = wordRoomDatabase.wordDao()
wordRoomDatabase.wordDao().insertAll(listOf<Word(Word("one"),Word("two"),Word("three"))
}
#After
fun closeDb() {
wordRoomDatabase.close()
}
#Test
fun testGetName() {
Assert.assertThat(getValue(wordDao.getAllLiveWords()).size, equalTo(3))
}
}
It seems though you need allowMainThreadQueries() in the build of the DB.
I am not sure as to why everyone tests the Dao in the instrumentation test when it can be done in the Unit Test and then be added to code coverage (maybe someone else has some insight)
This code is in Kotlin but I am sure it would translate to java just the same.
This was provided to me however as to why it's not considered to be best practice
https://developer.android.com/training/data-storage/room/testing-db
Note: Even though this setup allows your tests to run very quickly, it isn't recommended because the version of SQLite running on your device—and your users' devices—might not match the version on your host machine.
Robolectric can support such JVM unit tests with Room.
To get the required context, please add below dependency in your build.gradle:
testImplementation 'androidx.test:core:1.2.0'
Let's assume that we have a repository class which wraps the Room Dao. And here's a quick example:
#RunWith(RobolectricTestRunner::class)
#Config(sdk = [28]) // This config setting is the key to make things work
class FooRepositoryTest {
#get:Rule
var instantTask = InstantTaskExecutorRule()
private lateinit var database: MyDatabase
private lateinit var sut: FooRepository
#Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(context, MyDatabase::class.java)
.allowMainThreadQueries()
.build()
sut = FooRepository(database.fooDao())
}
#After
fun tearDown() {
database.close()
}
}
We currently have an application which uses multiple databases with the same schema. At the moment we're using a custom solution for switching between them based on the user's session. This works via
public final class DataSourceProxy extends BasicDataSource {
...
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
String targetUrl = details.get("database");
Connection c = super.getConnection();
Statement s = c.createStatement();
s.execute("USE " + targetUrl + ";");
s.close();
return c;
} else {
return super.getConnection();
}
}
Now we want to build a solution using AbstractRoutingDataSource. The problem is:
#Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
Environment env;
#Autowired
DbDetailsRepositoy repo;
public CustomRoutingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
for(DBDetails dbd : repo.findAll() {
// create DataSource and put it into the map
}
setTargetDataSources(new HashMap<Object, Object>());
}
#Override
protected Object determineCurrentLookupKey() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
return details.get("database");
}
return null;
}
}
Inside the constructor (or even via #PostConstruct) we have to fill the targetDataSources Map. But(!) for this we need the connection details which are stored in another database, which has its own DataSource and Entity Manager.
It seems like Spring can't determine the order of Bean construction, or maybe I'm just missing something. It always gives a NullPointerException when accessing the repository (which btw is a JpaRepository).
We're using Spring 3.2.3, Spring Data, Hibernate 4.2. Complete Annotation and Java-Code configuration of Spring and Spring Security.
Please help us!
Spring of course has to call the constructor before it can populate the properties. But that's not a Spring thing, that's basic Java 101 and one of the plenty downsides of using field injection.
To avoid this, simply add your dependencies to the constructor:
#Component
class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
public CustomRoutingDataSource(DbDetailsRepository repo, Environment environment) {
…
}
…
}