Skip to content

Commit d36fd2e

Browse files
mp911deodrotbohm
authored andcommitted
Add Kotlin example for Spring Data Cassandra.
1 parent 2224264 commit d36fd2e

File tree

9 files changed

+463
-0
lines changed

9 files changed

+463
-0
lines changed

cassandra/kotlin/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Spring Data Cassandra - Kotlin examples
2+
3+
This project contains samples of Kotlin-specific features of Spring Data (Cassandra).
4+
5+
## Value defaulting on entity construction
6+
7+
Kotlin allows defaulting for constructor- and method arguments.
8+
Defaulting allows usage of substitute values if a field in the document is absent or simply `null`.
9+
Spring Data inspects objects whether they are Kotlin types and uses the appropriate constructor.
10+
11+
```kotlin
12+
@Table
13+
data class Person(@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED) val firstname: String? = "", val lastname: String = "White")
14+
15+
operations.cqlOperations.execute(QueryBuilder.insertInto("person").value("firstname", "Walter"))
16+
17+
val person = operations.query<Person>()
18+
.matching(query(where("firstname").isEqualTo("Walter")))
19+
.firstValue()!!
20+
21+
assertThat(person.lastname).isEqualTo("White")
22+
```
23+
24+
## Kotlin Extensions
25+
26+
Spring Data exposes methods accepting a target type to either query for or to project results values on.
27+
Kotlin represents classes with its own type, `KClass` which can be an obstacle when attempting to obtain a Java `Class` type.
28+
29+
Spring Data ships with extensions that add overloads for methods accepting a type parameter by either leveraging generics or accepting `KClass` directly.
30+
31+
```kotlin
32+
operations.getTableName<Person>()
33+
34+
operations.getTableName(Person::class)
35+
```
36+
37+
## Nullability
38+
39+
Declaring repository interfaces using Kotlin allows expressing nullability constraints on arguments and return types. Spring Data evaluates nullability of arguments and return types and reacts to these. Passing `null` to a non-nullable argument raises an `IllegalArgumentException`, as you're already used to from Kotlin. Spring Data helps you also to prevent `null` in query results. If you wish to return a nullable result, use Kotlin's nullability marker `?`. To prevent `null` results, declare the return type of a query method as non-nullable. In the case a query yields no result, a non-nullable query method throws `EmptyResultDataAccessException`.
40+
41+
```kotlin
42+
interface PersonRepository : CrudRepository<Person, String> {
43+
44+
/**
45+
* Query method declaring a nullable return type that allows to return null values.
46+
*/
47+
fun findOneOrNoneByFirstname(firstname: String): Person?
48+
49+
/**
50+
* Query method declaring a nullable argument.
51+
*/
52+
fun findNullableByFirstname(firstname: String?): Person?
53+
54+
/**
55+
* Query method requiring a result. Throws [org.springframework.dao.EmptyResultDataAccessException] if no result is found.
56+
*/
57+
fun findOneByFirstname(firstname: String): Person
58+
}
59+
```

cassandra/kotlin/pom.xml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>org.springframework.data.examples</groupId>
7+
<artifactId>spring-data-cassandra-examples</artifactId>
8+
<version>2.0.0.BUILD-SNAPSHOT</version>
9+
</parent>
10+
11+
<artifactId>spring-data-cassandra-kotlin</artifactId>
12+
<name>Spring Data Cassandra - Kotlin features</name>
13+
14+
<properties>
15+
<spring-data-releasetrain.version>Lovelace-BUILD-SNAPSHOT</spring-data-releasetrain.version>
16+
</properties>
17+
18+
<profiles>
19+
20+
<!-- Override property as the module always needs Lovelace -->
21+
22+
<profile>
23+
<id>spring-data-next</id>
24+
<properties>
25+
<spring-data-releasetrain.version>Lovelace-BUILD-SNAPSHOT</spring-data-releasetrain.version>
26+
</properties>
27+
</profile>
28+
29+
</profiles>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>org.jetbrains.kotlin</groupId>
34+
<artifactId>kotlin-stdlib-jdk8</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.jetbrains.kotlin</groupId>
38+
<artifactId>kotlin-reflect</artifactId>
39+
</dependency>
40+
41+
<dependency>
42+
<groupId>${project.groupId}</groupId>
43+
<artifactId>spring-data-cassandra-example-utils</artifactId>
44+
<version>${project.version}</version>
45+
<scope>test</scope>
46+
</dependency>
47+
</dependencies>
48+
49+
<build>
50+
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
51+
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
52+
<plugins>
53+
<plugin>
54+
<groupId>org.springframework.boot</groupId>
55+
<artifactId>spring-boot-maven-plugin</artifactId>
56+
</plugin>
57+
<plugin>
58+
<artifactId>kotlin-maven-plugin</artifactId>
59+
<groupId>org.jetbrains.kotlin</groupId>
60+
<configuration>
61+
<args>
62+
<arg>-Xjsr305=strict</arg>
63+
</args>
64+
<compilerPlugins>
65+
<plugin>spring</plugin>
66+
</compilerPlugins>
67+
</configuration>
68+
<dependencies>
69+
<dependency>
70+
<groupId>org.jetbrains.kotlin</groupId>
71+
<artifactId>kotlin-maven-allopen</artifactId>
72+
<version>${kotlin.version}</version>
73+
</dependency>
74+
</dependencies>
75+
</plugin>
76+
</plugins>
77+
</build>
78+
79+
</project>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.cassandra.kotlin
17+
18+
import org.springframework.boot.autoconfigure.SpringBootApplication
19+
import org.springframework.boot.runApplication
20+
21+
/**
22+
* @author Mark Paluch
23+
*/
24+
@SpringBootApplication
25+
class ApplicationConfiguration
26+
27+
fun main(args: Array<String>) {
28+
runApplication<ApplicationConfiguration>(*args)
29+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.cassandra.kotlin
17+
18+
import org.springframework.data.cassandra.core.cql.PrimaryKeyType
19+
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn
20+
import org.springframework.data.cassandra.core.mapping.Table
21+
22+
/**
23+
* An entity to represent a Person.
24+
*
25+
* @author Mark Paluch
26+
*/
27+
@Table
28+
data class Person(@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED) val firstname: String? = "", val lastname: String = "White")
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.cassandra.kotlin
17+
18+
import org.springframework.data.repository.CrudRepository
19+
20+
/**
21+
* Repository interface to manage [Person] instances.
22+
*
23+
* @author Mark Paluch
24+
*/
25+
interface PersonRepository : CrudRepository<Person, String> {
26+
27+
/**
28+
* Query method declaring a nullable return type that allows to return null values.
29+
*/
30+
fun findOneOrNoneByFirstname(firstname: String): Person?
31+
32+
/**
33+
* Query method declaring a nullable argument.
34+
*/
35+
fun findNullableByFirstname(firstname: String?): Person?
36+
37+
/**
38+
* Query method requiring a result. Throws [org.springframework.dao.EmptyResultDataAccessException] if no result is found.
39+
*/
40+
fun findOneByFirstname(firstname: String): Person
41+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
spring.data.cassandra.keyspace-name=example
2+
spring.data.cassandra.schema-action=recreate
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.cassandra.kotlin
17+
18+
import example.springdata.cassandra.util.CassandraKeyspace
19+
import org.assertj.core.api.Assertions.assertThat
20+
import org.assertj.core.api.Assertions.assertThatThrownBy
21+
import org.junit.Before
22+
import org.junit.ClassRule
23+
import org.junit.Test
24+
import org.junit.runner.RunWith
25+
import org.springframework.beans.factory.annotation.Autowired
26+
import org.springframework.boot.test.context.SpringBootTest
27+
import org.springframework.dao.EmptyResultDataAccessException
28+
import org.springframework.data.util.Version
29+
import org.springframework.test.context.junit4.SpringRunner
30+
31+
/**
32+
* Tests showing Kotlin usage of Spring Data Repositories.
33+
*
34+
* @author Mark Paluch
35+
*/
36+
@RunWith(SpringRunner::class)
37+
@SpringBootTest
38+
class RepositoryTests {
39+
40+
companion object {
41+
42+
@JvmField
43+
@ClassRule
44+
val CASSANDRA_KEYSPACE = CassandraKeyspace.onLocalhost()
45+
.atLeast(Version.parse("3.0"))
46+
}
47+
48+
@Autowired
49+
lateinit var repository: PersonRepository
50+
51+
@Before
52+
fun before() {
53+
repository.deleteAll()
54+
}
55+
56+
@Test
57+
fun `should find one person`() {
58+
59+
repository.save(Person("Walter", "White"))
60+
61+
val walter = repository.findOneByFirstname("Walter")
62+
63+
assertThat(walter).isNotNull()
64+
assertThat(walter.firstname).isEqualTo("Walter")
65+
assertThat(walter.lastname).isEqualTo("White")
66+
}
67+
68+
@Test
69+
fun `should return null if no person found`() {
70+
71+
repository.save(Person("Walter", "White"))
72+
73+
val walter = repository.findOneOrNoneByFirstname("Hank")
74+
75+
assertThat(walter).isNull()
76+
}
77+
78+
@Test
79+
fun `should throw EmptyResultDataAccessException if no person found`() {
80+
81+
repository.save(Person("Walter", "White"))
82+
83+
assertThatThrownBy { repository.findOneByFirstname("Hank") }.isInstanceOf(EmptyResultDataAccessException::class.java)
84+
}
85+
}

0 commit comments

Comments
 (0)