Spring Data Cassandra Naming Strategy - spring

Kotlin + Spring Boot Cassandra App is ignoring #Column("column_name") annotations. The below annotation
#Column("order_details")
val orderDetails: String
Is being mapped to orderdetails instead of order_details. As the column orderdetails doesn't exist in the table, it causes a run time error.
Why doesn't spring data Cassandra's #Column map to snake case by default?
Even after the value is being passed to #Column mapping, why is it being ignored?
Is there any other property that needs to be set in the app properties so that spring data Cassandra uses snake case for physical mapping?
#Table("order_by_customer")
data class CustomerOrder(
#PrimaryKeyColumn(name = "customer_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
val customerId: UUID,
#Column("order_details")
val orderDetails: String,
#Column("ordered_at")
val orderedAt: LocalDate
)
Generated insert query
INSERT INTO order_by_customer (customer_id,orderdetails,orderedat)
While annotation for PK work the ones for Column are being ignored

Per the resolution provided by spring-data-cassandra team. Data class need to be coded as shown below
#Table("order_by_customer")
data class CustomerOrder(
#PrimaryKeyColumn(name = "customer_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
val customerId: UUID,
#field:Column("order_details")
val orderDetails: String,
#field:Column("ordered_at")
val orderedAt: LocalDate
)
https://github.com/spring-projects/spring-data-cassandra/issues/1146
https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets

Related

Spring Doc Open API shows invalid field name on swagger ui

There is an invalid model shown on swagger UI when there is only one lowercase letter at the beginning of the field name.
My Kotlin model:
class TrendEvaluationModel(
val xAxisValue: Int,
val yAxisValue: Int,
val customValue: Int?
)
What is shown on swagger UI:
{
"customValue": 1,
"xaxisValue": 1,
"yaxisValue": 1
}
I've tried:
#Parameter annotation with specified name attribute but it does not work.
#Schema annotation with specified name attribute but it does not work.
#JsonProperty("xAxisValue") and it worked but not as expected - the model on swagger showed two fields then (xaxisValue and xAxisValue) but I need only one of them (xAxisValue).
Appreciate your help.
NOTE: There is no issue if there are two or more lowercase letters at the beginning of the field name
Applying the #Schema annotation to the constructor fields to change the field names in Swagger UI did not have an effect. So I made those fields private and added new fields that point to the private fields. I also added #Schema and #JsonProperty annotations to the new fields to change how they show up in Swagger UI and the API request/response respectively. The final class looked like below:
import com.fasterxml.jackson.annotation.JsonProperty
import io.swagger.v3.oas.annotations.media.Schema
class TrendEvaluationModel(
#Schema(hidden = true)
private val xAxisVal: Int,
#Schema(hidden = true)
private val yAxisVal: Int,
val customValue: Int?
) {
val xAxisValue: Int
#Schema(name = "xAxisValue")
#JsonProperty("xAxisValue")
get() = xAxisVal
val yAxisValue: Int
#Schema(name = "yAxisValue")
#JsonProperty("yAxisValue")
get() = yAxisVal
}
This class shows up like below, with the correct field names, in Swagger UI:
{
"customValue": 0,
"xAxisValue": 0,
"yAxisValue": 0
}
You can find a working sample app that uses this class on github

Spring Data JDBC Kotlin NoSuchMethod error: Dialect.getIdGeneration()

I'm writing a very simple Spring Data JDBC repository in Kotlin (using Postgres as the database):
data class Label(
#Id
#GeneratedValue
#Column( columnDefinition = "uuid", updatable = false )
val id: UUID,
val name: String
)
#Repository
interface LabelRepository: CrudRepository<Label, UUID> {}
When I do repository save:
val l = Label(id = UUID.randomUUID(), name = "name")
labelRepo.save(l)
It works fine. But since id is not null Spring Data JDBC will always treat it as an "update" to an existing label entity instead of creating a new one with generated ID.
So I changed id: UUID to id: UUID? And having val l = Label(id = null, name = "name")
But call the same save() method gives me:
java.lang.NoSuchMethodError: 'org.springframework.data.relational.core.dialect.IdGeneration org.springframework.data.relational.core.dialect.Dialect.getIdGeneration()'
I have tried a solution here: https://jivimberg.io/blog/2018/11/05/using-uuid-on-spring-data-jpa-entities/
But it didn't work, still gives me the same error
Wondering what's the cause of this and why this error pops up only when I change UUID to UUID??
nvm, turns out I have to use the implementation("org.springframework.boot:spring-boot-starter-data-jdbc") dependency instead of implementation("org.springframework.data:spring-boot-starter-data-jdbc:2.1.3")

Why doesn't Mybatis map a simple ENUM correctly?

I'm not doing anything out of the ordinary from what I can tell. I have a spring boot application using mybatis:
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
I have an application.properties config for mybatis that is pretty simple:
## MyBatis ##
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-statement-timeout=30
My database table looks like this:
CREATE TABLE workspace_external_references (
id CHAR(36) PRIMARY KEY,
workspace_id CHAR(36) NOT NULL,
site VARCHAR(255) NOT NULL,
external_id VARCHAR(255) NOT NULL,
created_at DATETIME(6) NOT NULL DEFAULT NOW(6),
updated_at DATETIME(6) NOT NULL DEFAULT NOW(6),
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE
)
With just a single entry like this:
'a907c0af-216a-41e0-b16d-42107a7af05f', 'e99e4ab4-839e-405a-982b-08e00fbfb2d4', 'ABC', '6', '2020-06-09 00:19:20.135822', '2020-06-09 00:19:20.135822'
In my mapper file I'm doing a select of all references like this:
#Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
List<WorkspaceExternalReference> findByWorkspace(#Param("workspaceId") final UUID workspaceId);
And the java object that this is supposed to map to looks like this:
public class WorkspaceExternalReference {
private UUID id;
private UUID workspaceId;
private Sites site;
private String externalId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public WorkspaceExternalReference(
final Sites site,
final UUID workspaceId,
final String externalId) {
this.site = site;
this.workspaceId = workspaceId;
this.externalId = externalId;
}
}
public enum Sites {
ABC, XYZ;
}
Sooooo why doesn't this work? I get this error back:
Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'id' from result set. Cause: java.lang.IllegalArgumentException: No enum constant com.acme.Sites.a907c0af-216a-41e0-b16d-42107a7af05f
When there is no default constructor, you need to let MyBatis know which columns to pass to the constructor explicitly (in most cases).
With annotations, it would look as follows.
You can use <resultMap> and <constructor> in XML mapper.
#ConstructorArgs({
#Arg(column = "site", javaType = Sites.class),
#Arg(column = "workspace_id", javaType = UUID.class),
#Arg(column = "external_id", javaType = String.class)
})
#Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
List<WorkspaceExternalReference> findByWorkspace(#Param("workspaceId") final UUID workspaceId);
Other columns (i.e. id, created_at, updated_at) will be auto-mapped via setters (if there are) or reflection.
Alternatively, you can just add the default (no-arg) constructor to the WorkspaceExternalReference class. Then all columns will be auto-mapped after the class is instantiated.
Note: To make it work, there needs to be a type handler registered for UUID, but you seem to have done it already (otherwise the parameter mapping wouldn't work).

Neo4jRepository saves empty node from Kotlin data class

I have a Kotlin data class node for Neo4j nodes:
#NodeEntity
data class MyNode (
#Id #GeneratedValue var dbId: Long? = null,
#Index(unique = true) val name: String,
val description: String
)
and a Spring repository:
interface MyNodesRepository : Neo4jRepository<MyNode, Long>
Then, when I save a node into the DB via this repository it is empty, without any properties:
val node = MyNode(null, "name 1", "lorem ipsum")
myNodesRepository.save(node)
after the save(node) call, the node.dbId is set to the Neo4j's internal id i.e. it is null before save() and has a value afterwards. I can also see the node in the Neo4j browser, but it does not have name and description properties.
After that, when I try to load all nodes the call crashes with InvalidDataAccessApiUsageException because it cannot deserialize/map the nodes with null/missing name and description:
val allNodes = myNodesRepository.findAll()
If I add a custom save method to my repository, where I manually create the node with CQL query, then everything works.
interface MyNodesRepository : Neo4jRepository<MyNode, Long> {
#Query(
"MERGE (mn:MyNode {name:{name}})\n" +
"ON CREATE SET m += {description:{description}}"
)
fun customSave(#Param("name") name: String, #Param("description") description: String)
}
Now the findAll() loads my newly created and/or updated nodes.
I am using org.springframework.boot:spring-boot-starter-data-neo4j:2.1.6.RELEASE and this is inside a Spring Boot CLI application so no web server and RestControllers.
What am I doing wrong?
EDIT: This is now solved in Neo4j OGM 3.1.13
EDIT: This is now solved in Neo4j OGM 3.1.13 and the workaround below is not needed anymore.
ORIGINAL ANSWER
After few days of debugging it looks like there is a bug in Neo4j OGM for Kotlin data classes where it does not save val properties -- both in data classes and normal classes. So change from val properties:
#NodeEntity
data class MyNode (
#Id #GeneratedValue var dbId: Long? = null,
#Index(unique = true) val name: String,
val description: String
)
to all var properties:
#NodeEntity
data class MyNode (
#Id #GeneratedValue var dbId: Long? = null,
#Index(unique = true) var name: String,
var description: String
)
works. Now both saving and loading works, but it is not idiomatic Kotlin.
So at least we have a workaround.

Unique Key Constraint in Spring Data Jpa

How to make a field unique in pojo using spring data jpa?I know how to do that using jpa
For reference: multi column constraint with jpa
If there is a way, is it possible to use with spring boot?
Use the #UniqueConstraint annotation to specify that a unique constraint is to be included in the generated DDL for a primary or secondary table.
Alternately, to ensure a field value is unique you can write
#Column(unique=true)
String myField;
With Spring Data JPA you are using JPA, so you specify the unique constraint using JPA. Nothing special from Spring Boot or Spring Data on that front.
Um exemplo em Kotlin:
Entity
#Table(name = "TBL_XXX",
uniqueConstraints = [
UniqueConstraint(name = "sessionid_uindex", columnNames = ["sessionId"])
]
)
data class XxxReturn(
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "XXX_ID")
var id: Long? = null,
var sessionId: String,
var msg: String
)
If you have a single column in the table that has UNIQUE KEY constraint then you can simply add the attribute unique=true in the annotation #Column
CODE SNIPPET:
#Column(name = "unique_key_field", nullable = false, unique = true)
String uniqueKeyFied;
In case if you have multiple Unique key constraints in the table then you have to simply follow with the JPA annotations as the spring-boot-data-starter does not provide any special annotations for the table constraints(KEY/UNIQUEKEY).
CODE SNIPPET:
#Entity
#Table(name = "table_name", uniqueConstraints={
#UniqueConstraint( name = "idx_col1_col2", columnNames ={"col1","col2"})
})

Resources