Spring Boot custom model on top of another - spring-boot

I'm new to Spring Boot and I don't know how to google this, but
I have this situation:
There is table tokens:
id INT
token VARCHAR
type VARCHAR
resource VARCHAR
attributes JSON
created_at DATETIME
And this tokens are multi-purpose: login codes, share codes etc
Login process:
E-mail provided by user
Server sent login code to e-mail
User provides code
Authorization
So in login context, I don't have Token with resource and attributes, but I have LoginCode model which properties code userId (stored as resource) and created_at
So i have model class and repository:
#Entity
class Token(
#Id #GeneratedValue
var id: Long? = null,
#Column(unique = true)
var token: String = generateToken(6),
var type: TokenType,
var resource: String,
#Type(type = "json")
var attributes: Map<String, Any>? = null,
var createdAt: LocalDateTime = LocalDateTime.now(),
)
interface TokenRepository : CrudRepository<Token, Long> {
fun findByTokenAndType(token: String, type: TokenType)
}
But is there a better solution for custom repository than:
interface LoginCodeRepository : TokenRepository {
fun findByCode(code: String): LoginCode {
val token = this.findByTokenAndType(
token = code,
type = TokenType.LOGIN_ATTEMPT,
)
return LoginCode.fromToken(token)
}
fun create(adminId: Long): LoginCode {
val token = this.save(Token(
type = TokenType.LOGIN_ATTEMPT,
resource = adminId.toString(),
))
return LoginCode.fromToken(token)
}
}
Without showing the whole TokenRepository api for public, and not having to call LoginCode.fromToken in each function?

You have entity Token. And you don't need special repository for each purpose using of token. You can make data class (model) for Login. And then you can make in separate utils file extension functions for conversion between Token entityi to different models like Login. I'll show you some example:
#Entity
data class Token(
#Id #GeneratedValue
var id: Long? = null,
#Column(unique = true)
var token: String = generateToken(6),
var type: TokenType,
var resource: String,
#Type(type = "json")
var attributes: Map<String, Any>? = null,
var createdAt: LocalDateTime = LocalDateTime.now()
)
data class LoginCode(
val code: String,
val userId: String,
val createdAt: LocalDataTime
)
fun Token.toLoginCode() = LoginCode(
code = this.token,
userId = this.resource,
createdAt = this.createdAt
)
Then in your service layer, if you will need LoginCode, you can do something like this:
tokenRepository.findByTokenAndType(code, TokenType.LOGIN_ATTEMPT).toLoginCode()
tokenRepository.save(Token(type = TokenType.LOGIN_ATTEMPT, resource = adminId.toString())).toLoginCode()

Related

Is there a way to make relation to Set<String> in Room?

Assume tables:
CREATE TABLE users (id INT, name VARCHAR(255))
CREATE TABLE user_roles (user_id INT REFERENCES users(id), role_name VARCHAR(255))
Entity in Room:
#Entity(tableName = "users")
data class User(
#PrimaryKey
val id: Int,
val roles: Set<String> // want it from role_name column
)
Second entity (if needed):
#Entity(tableName = "user_roles")
data class UserRole(
val user_id: Int,
val role_name: String
)
DAO
#Dao
interface UserDao {
#Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
}
This will not compile because SQLite doesn't support collections. Is there any way to map the role_name from user_roles to roles property of User entity? Can Room do it automatically?
I don't think Room has automatic support for that requirement. Instead you could do a custom query to achieve that.
Refer to this example of how to do one, and adapt according to your requirements.
getAllUsersWithRoles returns flow of list of UserWithRoles. Query joins users and user_roles tables and maps results to UserWithRolesResult class.
fromResult takes list of UserWithRolesResult and maps it to list of UserWithRoles
#Dao
interface UserDao {
#Transaction
#Query("SELECT * FROM users")
fun getAllUsersWithRoles(): Flow<List<UserWithRoles>>
companion object {
private fun fromResult(result: List<UserWithRolesResult>) : List<UserWithRoles> {
val map = result.groupBy { it.userId }.mapValues { it.value.map { it.roleName }.toSet() }
return map.map { UserWithRoles(it.key, it.value) }
}
}
}
data class UserWithRoles(
val userId: Int,
val roles: Set<String>
)
data class UserWithRolesResult(
val userId: Int,
val roleName: String
)
This will not compile because SQLite doesn't support collections.
Room however does.
Consider the following working Demo based upon your code
note the comments and modifications
run on the main thread for brevity
the POJO UserWithRoles is the main inclusion that uses a Set, which is populated/used
:-
#Entity(tableName = "users")
data class User(
#PrimaryKey
val id: Int,
val name: String
/* roles would be effectively trying to store what is stored elsewhere */
//val roles: Set<String> // want it from role_name column !! Wrong place see UserWithRoles
)
#Entity(
tableName = "user_roles",
foreignKeys = [
ForeignKey(
entity = User::class,
parentColumns = ["id"],
childColumns = ["user_id"]
)
]
)
data class UserRole(
#PrimaryKey
/* In Room and Entity MUST have a primary key */
/* if user_id were a primary key then many UserRoles per User would not be allowed as
a primary key is implicitly UNIQUE. Hence dummy_id
*/
val dummy_id: Long?=null,
val user_id: Int,
val role_name: String
)
data class UserWithRoles(
#Embedded
val user: User,
#Relation(entity = UserRole::class, parentColumn = "id", entityColumn = "user_id" )
val roles: Set<UserRole>
)
#Dao
interface UserDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(user: User): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(userRole: UserRole): Long
#Query("SELECT * FROM users")
fun getAllUsers(): /*Flow<*/List<UserWithRoles>/*>*/
}
#Database(entities = [User::class,UserRole::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getUserDao(): UserDao
companion object {
private var instance: TheDatabase?=null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance=Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries() /* for brevity of demo */
.build()
}
return instance as TheDatabase
}
}
}
and some Activity Code to demonstrate:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: UserDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getUserDao()
dao.insert(User(10,"Fred"))
dao.insert(User(11,"Mary"))
dao.insert(User(12,"Jane"))
dao.insert(UserRole(user_id = 10, role_name = "Role1"))
dao.insert(UserRole(user_id = 10, role_name = "Role2"))
dao.insert(UserRole(user_id = 10, role_name = "Role3"))
dao.insert(UserRole(user_id = 11, role_name = "Role4"))
for (uwr in dao.getAllUsers()) {
val sb = StringBuilder()
for (r in uwr.roles) {
sb.append("\n\tRole is ").append(r.role_name)
}
Log.d("DBINFO","User is ${uwr.user.name} and has ${uwr.roles.size} roles. They are ${sb}")
}
}
}
When run then the log includes:-
D/DBINFO: User is Fred and has 3 roles. They are
Role is Role3
Role is Role2
Role is Role1
D/DBINFO: User is Mary and has 1 roles. They are
Role is Role4
D/DBINFO: User is Jane and has 0 roles. They are

How can optimised the code for Derived Query Methods in Spring Data JPA Repositories

Entity Class:
#Document(collection = “personErasure")
class PersonErasureModel {
#Id
var id: UUID = UUID.randomUUID()
lateinit var personRequestId: UUID
var fName: String? = null
var validationState: PersonValidationState? = null
var erasureState: ErasureState? = null
}
//1. Enum class
enum class PersonValidationState {
PROCESS,
ELIGIBLE,
INELIGIBLE
}
//2. Enum Class
enum class ErasureState {
IN_PROGRESS,
COMPLETED
}
Repository:
#Repository
interface PersonErasureRepository : ReactiveMongoRepository<PersonErasureModel, UUID> {
fun countByPersonRequestIdAndValidationStateIn(
personRequestId: UUID,
statuses: List<PersonValidationState>
): Mono<Long>
fun countByPersonRequestIdAndErasureState(
personRequestId: UUID,
erasureState: ErasureState
): Mono<Long>
}
Here in PersonErasureRepository both methods are similar. But both PersonValidationState and ErasureState are two different properties in model class.
Can we use a single method which can handle both the use case? Then how can name the Derived query method ?

Spring Data R2dbc UUID #Id Persistable

I am not sure whether this is a bug or I am using Persistable incorrectly.
Aim: make the UserId non-null to avoid having to assert non-null when using in the code
User.kt
#Table(value = "main.user")
data class User(
#Id
#Column("id")
var userId: UUID = UUID(0,0),
val username: Username,
val isVerified: Boolean = false,
val updated: LocalDateTime,
val updatedBy: String
) : Persistable<UUID> {
override fun isNew(): Boolean = userId.equals(UUID(0,0))
override fun getId(): UUID = userId
}
UserRepository.kt
#Repository
interface UserModelRepository : CoroutineCrudRepository<User, UUID>
Test.kt
#DataR2dbcTest
internal class Test {
#Autowired
private lateinit var userModelRepository: UserModelRepository
#Test
fun `should save the user model`() = runBlocking {
val userModel = createUserModel()
val actual = UserModelRepository.save(userModel)
actual.userId shouldNotBe UUID(0, 0)
}
}
SQL
CREATE TABLE IF NOT EXISTS main.user
(
id UUID NOT NULL PRIMARY KEY DEFAULT GEN_RANDOM_UUID(),
username CHARACTER VARYING(70) NOT NULL UNIQUE,
is_verified BOOLEAN NOT NULL,
updated TIMESTAMP WITHOUT TIME ZONE NOT NULL,
updated_by CHARACTER VARYING(255) NOT NULL
);
Expected: The test to pass as the Id should be generated by the database
Actual: Id is "00000000-0000-0000-0000-000000000000"
Spring Data uses the same object reference to apply the update of the ID at. Since you set the ID to a final value Spring Data can't apply a new generated ID to it. The ID will also only be generated if the value is null otherwise the value which is already set will be used.
There is a workaround:
#Table("my_user")
data class MyUser(
#Id
#Column("id")
private var _id: UUID? = null,
val name: String = "blubb"
) {
val id: UUID
get() = _id ?: UUID(0, 0)
}
But it is basically bad practice as answered here.

How to use another variable name or How to flatten entity in JPA Projection for nested object

I'm making an api for querying nested entity with Spring Data JPA Projection.
My Code
The Entity:
#Entity
class User {
#Id
var userId: String
var name: String
var age: Int
#OneToOne
#JoinColumn(name = "userId")
var address: Address
}
#Entity
class Address {
var userId: String
var street: String
var city: String
var country: String
}
The Repository:
interface UserView {
val name: String
val address: AddressView
interface AddressView {
val city: String
val country: String
}
}
#Repository
interface UserRepository : JPARepository<User, String> {
fun findAll(): List<UserView>
}
Expected Response
{
"name": "example",
"city": "example-city",
"country": "example-country"
}
My code produces
{
"name": "example",
"address": {
"city": "example-city",
"country": "example-country"
}
}
I Tried
I tried another view to flatten object:
interface UserView {
val name: String
val addressCity: String
val addressCountry: String
}
But this case, the variable naming is too complicate.
I want to solve this problem with projection. How can I solve this problem?
In JPA, you can do this using #NamedNativeQuery only:
#NamedNativeQuery(
name = "getUser",
query = "SELECT u.name, a.city, a.country FROM User u and Address a where a.userId = u.id ", resultClass=UserView .class)
#Entity
class User {
...
}
For Reference hibernate-named-query
Try this:
data class UserView(name: String, city: String, country: String)
#Repository
interface UserRepository : JPARepository<User, String> {
#Query(value = "select new your.pkg.UserView(u.name, u.address.city, u.address.country) from User u")
fun findAllFlat(): List<UserView>
}
You can use #Value and combine more fields or even access the fields of objects.
From the spring-data-rest documentation:
You can create a projection that combines the two data fields in the preceding example together, as follows:
#Projection(name = "virtual", types = { Person.class })
public interface VirtualProjection {
#Value("#{target.firstName} #{target.lastName}")
String getFullName();
}
Spring’s #Value annotation lets you plug in a SpEL expression that takes the target object and splices together its firstName and lastName attributes to render a read-only fullName.
Flattening also works for me:
#Value("#{target.document.title}")
String getDocumentTitle();

#valid #requestBody kotlin with entity into entity

I have a problem with valid request in kotlin because I currently have an object composed of a list of integers and another entity called emailData, when I send incomplete or in error format emaildata, the validation does not happen and let me enter the controller. my code this and my request in postman these
fun sendMessage(#Valid #RequestBody notificationData: NotificationData) {
this.notificationManager.sendNotificationByType(notificationData)
}
data class NotificationData(
#get:NotEmpty
#get:NotNull
#field:NotNull
#Size(min = 2, max = 14)
#get:JsonProperty("notification_type")
var notificationType : List<Int> = listOf(),
#Valid
//# #field:NotEmpty(message = "SRTERST enter id")
#get:JsonProperty("email_data")
var emailData : EmailData = EmailData())
data class EmailData(
#NotNull
#get:NotEmpty
#get:JsonProperty("email_receiver")
var emailReceiver : List<String> = listOf(),
#NotNull
#get:NotEmpty
#get:JsonProperty("subject")
var subject : String = "",
#get:NotEmpty
#NotNull
#get:JsonProperty("template_id")
var templateId : String = "",
#get:NotEmpty
#NotNull
#get:JsonProperty("template_params")
var templateParams : HashMap<String, String> = HashMap())
when i send
{
"notification_type":["0"],
"email_data":{
"subject":"test",
"template_id":"d-1860fd6fa461449b88c578b124a0b331"
}
}
the validation for the emailData no work.

Resources