Creating models

Here, we are going to model the data we have identified into suitable entity classes that can be introspected by Spring Boot to build a suitable database schema. The first model we will concern ourselves with is the user model. Create a package named models under the com.example.messenger.api package. Create a User.kt file within the package and input the following code:

package com.example.messenger.api.models

import org.hibernate.validator.constraints.Length
import org.springframework.format.annotation.DateTimeFormat
import java.time.Instant
import java.util.*
import javax.persistence.*
import javax.validation.constraints.Pattern
import javax.validation.constraints.Size

@Entity
@Table(name = "`user`")
@EntityListeners(UserListener::class)
class User(
@Column(unique = true)
@Size(min = 2)
var username: String = "",
@Size(min = 11)
@Pattern(regexp="^\(?(\d{3})\)?[- ]?(\d{3})[- ]?(\d{4})$")
var phoneNumber: String = "",
@Size(min = 60, max = 60)
var password: String = "",
var status: String = "",
@Pattern(regexp = "\A(activated|deactivated)\z")
var accountStatus: String = "activated"
)

We made use of a lot of annotations in the preceding code block. We will take a look at what each of them does in the order in which they appear. First up, we have the @Entity annotation, which indicates that the class is a Java Persistence API (JPA) entity. The use of the @Table annotation specifies a table name for the entity being represented by the class. This is useful during schema generation. In the case that an @Table annotation is not used, the name of the table generated will be the class name. A database table will be created in PostgreSQL with the name user@EntityListener, as the name implies, specifies an entity listener for the entity class. We have not yet created a UserListener class, but don't worry, we will do that in a little bit.

Now let's take a look at the properties of the User class. We added seven class properties in total. The first five are username, password, phoneNumber, accountStatus, and statuseach property represents a type of data we need for a user, as we earlier identified in the Identifying data section of this chapter. We have now created our user entity and are ready to proceed. But wait, there's a problem. We need a way to uniquely identify each user that is created. In addition, it is important for future reference to keep track of when new users are added to the messenger platform. After careful consideration, we realize that it is important to have id and createdAt properties in our entity. You may be wondering—why are we adding id and createdAt properties to the user entity? After all, we did not specify we needed them earlier on. This is true. But, as we are developing this backend incrementally, we are allowed to make changes and additions when the need arises. Let's go ahead and add these two properties:

@Entity
@Table(name = "`user`")
@EntityListeners(UserListener::class)
class User(
@Column(unique = true)
@Size(min = 2)
var username: String = "",
@Size(min = 8, max = 15)
@Column(unique = true)
@Pattern(regexp = "^\(?(\d{3})\)?[- ]?(\d{3})[- ]?(\d{4})$")
var phoneNumber: String = "",
@Size(min = 60, max = 60)
var password: String = "",
var status: String = "available",
@Pattern(regexp = "\A(activated|deactivated)\z")
var accountStatus: String = "activated",
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = 0,
@DateTimeFormat
var createdAt: Date = Date.from(Instant.now())
)

Perfect. Now we need to understand what each annotation does. @Column is used to specify a property representing a table column. In practice, all properties of entities represent a column in the table. We make use of @Column(unique = true) in our code specifically to place a uniqueness constraint on properties. This is useful when we do not want more than one record to have a particular attribute value. @Size, as you might have guessed, is used to specify the size of an attribute present in a table. @Pattern specifies a pattern that a table attribute must match for it to be valid.

@Id specifies a property that uniquely identifies the entity (the id property, in this case). @GeneratedValue(strategy = GenerationType.AUTO) specifies that we want the id value to be generated automatically. @DateTimeFormat  places a timestamp constraint on values to be stored in the created_at column of the user table.

It is time to create a UserListener class. Create a new package named listeners. Add the following UserListener class to the package:

package com.example.messenger.api.listeners

import com.example.messenger.api.models.User
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import javax.persistence.PrePersist
import javax.persistence.PreUpdate

class UserListener {

@PrePersist
@PreUpdate
fun hashPassword(user: User) {
user.password = BCryptPasswordEncoder().encode(user.password)
}
}

User passwords should never be saved as plain text in a database. For security reasons, they must always be appropriately hashed before being stored. The hashPassword() function performs this hashing procedure by replacing the string value held by the password property of a user object with its hashed equivalent using BCrypt. @PrePersist and @PreUpdate specifies that this function should be called before the persistence or update of a user record in the database.

Now let's create an entity for messages. Go ahead and add a Message class in the models package and add the following code to the class:

package com.example.messenger.api.models

import org.springframework.format.annotation.DateTimeFormat
import java.time.Instant
import java.util.*
import javax.persistence.*

@Entity
class Message(
@ManyToOne(optional = false)
@JoinColumn(name = "user_id", referencedColumnName = "id")
var sender: User? = null,
@ManyToOne(optional = false)
@JoinColumn(name = "recipient_id", referencedColumnName = "id")
var recipient: User? = null,
var body: String? = "",
@ManyToOne(optional = false)
@JoinColumn(name="conversation_id", referencedColumnName = "id")
var conversation: Conversation? = null,
@Id @GeneratedValue(strategy = GenerationType.AUTO) var id: Long = 0,
@DateTimeFormat
var createdAt: Date = Date.from(Instant.now())
)

We made use of some familiar annotations as well as two new ones. As we discussed earlier, every message has a sender as well as a recipient. Both message senders and message recipients are users on the messenger platform, hence the message entity has both sender and recipient properties of the User type. A user can be a sender of many messages as well as a recipient of many messages. These are relationships that need to be implemented. We make use of the @ManyToOne annotation to do this. The many-to-one relationships are not optional, thus we use @ManyToOne(optional = false)@JoinColumn specifies a column for joining an entity association or element collection:

@JoinColumn(name = "user_id", referencedColumnName = "id")
var sender: User? = null

The code snippet adds a user_id attribute that references the id of a user to the message table. 

Upon close inspection, you will notice that a conversation property was used in the Message class. This is because messages sent between users happen in conversation threads. Simply put, every message belongs to a thread. We need to add a Conversation class to our models package, representing the conversation entity:

package com.example.messenger.api.models

import org.springframework.format.annotation.DateTimeFormat
import java.time.Instant
import java.util.*
import javax.persistence.*

@Entity
class Conversation(
@ManyToOne(optional = false)
@JoinColumn(name = "sender_id", referencedColumnName = "id")
var sender: User? = null,
@ManyToOne(optional = false)
@JoinColumn(name = "recipient_id", referencedColumnName = "id")
var recipient: User? = null,
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = 0,
@DateTimeFormat
val createdAt: Date = Date.from(Instant.now())
) {

@OneToMany(mappedBy = "conversation", targetEntity = Message::class)
private var messages: Collection<Message>? = null
}

Numerous messages belong to a conversation, so we have a messages  collection in the body of the Conversation class.

We are almost done with the creation of entity models. The only thing left to do is to add appropriate collections for  a user's sent and received messages: 

package com.example.messenger.api.models

import com.example.messenger.api.listeners.UserListener
import org.springframework.format.annotation.DateTimeFormat
import java.time.Instant
import java.util.*
import javax.persistence.*
import javax.validation.constraints.Pattern
import javax.validation.constraints.Size

@Entity
@Table(name = "`user`")
@EntityListeners(UserListener::class)
class User(
@Column(unique = true)
@Size(min = 2)
var username: String = "",
@Size(min = 8, max = 15)
@Column(unique = true)
@Pattern(regexp = "^\(?(\d{3})\)?[- ]?(\d{3})[- ]?(\d{4})$")
var phoneNumber: String = "",
@Size(min = 60, max = 60)
var password: String = "",
var status: String = "available",
@Pattern(regexp = "\A(activated|deactivated)\z")
var accountStatus: String = "activated",
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = 0,
@DateTimeFormat
var createdAt: Date = Date.from(Instant.now())
) {
//collection of sent messages
@OneToMany(mappedBy = "sender", targetEntity = Message::class)
private var sentMessages: Collection<Message>? = null

//collection of received messages
@OneToMany(mappedBy = "recipient", targetEntity = Message::class)
private var receivedMessages: Collection<Message>? = null
}

That's it! We are done creating entities. To help you to understand the entities we have made as well as their relationships, here's an entity relationship diagram (E-R diagram). It shows the entities that we have made and their relationships:

According to the E-R diagram, a user has many messages, a message belongs to a user, a message belongs to a conversation, and a conversation has many messages. In addition, a user has many conversations.

Having created the necessary models, there's only one problem. We have no way to access the data stored by these entities. We need to create repositories to do this.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.133.54