Scala does not get first class support as Kotlin in Spring.
I tried to create a Spring Boot API application with Scala.
Spring Boot 2.2.0.M5
Spring Data JPA
H2
Scala 2.13
I created a JPA Entity with case class like:
#Entity
case class Post(#BeanProperty title: String, #BeanProperty content: String) {
def this() {
this(null, null)
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#BeanProperty
var id: Long = _
#BeanProperty
val createdOn: LocalDateTime = LocalDateTime.now()
override def toString: String = s"Post[id:$id, title:$title, content:$content, createdOn:$createdOn]"
}
And create a Repository using trait, it works.
trait PostRepository extends JpaRepository[Post, Long]
I want to try bean validation.
class PostForm {
#NotNull
#NotEmpty
#BeanProperty var title: String = _
#BeanProperty var content: String = _
}
And in the controller, create a POST method like:
#PostMapping
def save(#RequestBody #Valid form: PostForm, errors: BindingResult) = errors.hasErrors match {
case true => {
badRequest().build()
}
case _ => {
val data = Post(title = form.title, content = form.content)
val saved = posts.save(data)
created(ServletUriComponentsBuilder.fromCurrentContextPath().path("/{id}").buildAndExpand(saved.id).toUri).build()
}
}
It works.
But the model classes are little tedious. I am trying to use a case class like the following:
case class PostForm(#NotNull #NotEmpty #BeanProperty title: String, #BeanProperty content: String)
The validation does not work.
When we are modeling for JPA etc, case class or generic class is better?
Why we can not apply the Bean Validation annotations as Kotlin data clase in the case class?
Update: Got this work like:
case class PostForm(#(NotNull#field) #(NotEmpty#field) #BeanProperty title: String, #BeanProperty content: String)
The source codes is hosted on my Github.
Case class fields are considered as vals by default, which means you can't set a new value to them. #BeanProperty, however, is to automatically generate field setters and getters.
You may try adding var keywords to the fields explicitly.
case class PostForm(
#NotNull #NotEmpty #BeanProperty var title: String,
#BeanProperty var content: String
)
Related
I'm having trouble using Amazon's DynamoDBMapper in Scala code. The main sticking point is getting the JVM to recognize #DynamoDBHashkey when it is used in a case class, like:
case class MyCoolCaseClass(#DynamoDBHashKey(attributeName = "my_id") myId: String) {}
Any pointers from someone who has integrated this client library into a Scala project? (I'm hoping to not simply fallback to the low-level API, though that may be a decent decision once exhausting my options with the Mapper).
I had to do this:
import annotation.meta.beanGetter
import beans.BeanProperty
import com.amazonaws.services.dynamodbv2.datamodeling._
#DynamoDBTable(tableName="DEMOTAB")
case class DemoItem( // it's a case class for the free stuff, but can be ordinary class
#(DynamoDBHashKey #beanGetter) // would not work without meta annotation
#BeanProperty var id:String, // must be var or mapper can't instantiate one
#BeanProperty var number:Integer
) {
def this() = this(null, null) // needed by DynamoDB Mapper to instantiate
}
The DynamoDB mapper uses reflection to find the getters and setters. The SDK assumes Java-style conventions, i.e. that your getters and setters start with "get" or "is", and setters start with "set". You can see the reflection code on github.
I've been able to get it working, but it feels just like writing Java :(
#DynamoDBTable(tableName = "awesome_table")
class TheBestClass {
private var id : Integer = _
#DynamoDBHashKey
def getId() = id
def setId(_id: Integer) = id = _id
}
This works for me, including the boolean
#DynamoDBTable(tableName = "User")
case class User(
#(DynamoDBHashKey #field)
#(DynamoDBAutoGeneratedKey #field)
#BeanProperty var id: String,
#(DynamoDBAttribute #field)
#BeanProperty var firstName: String,
#(DynamoDBAttribute #field)
#BeanProperty var lastName: String,
#(DynamoDBAttribute #field)
#BeanProperty var active: Boolean
)
{
def this() = this(null, null, null, false)
}
Im using Scala, Squeryl and MySql to build a web app.
I found it easy to persist simple data as strings or integers. But what about when i have relations between objects and i need to use foreign keys. In my app i have areas, and sub areas which have an attribute of type Area (Area where they belong) so my Area and Subarea are like these
class Area(
var idArea: String,
#BeanProperty
var name:String,
#BeanProperty
var letter: String,
#BeanProperty
var color: String
)
extends Idable {
def this() = this("","", "","")
}
class SubArea(var idSubArea: String,
#BeanProperty
var name: String,
#BeanProperty
var area:Area
) extends Idable {
def this() = this("","",null )
How do i define the schema, so my SubArea table has an Area id, foreign key to my Area Table??
For the time being my SubArea schema is like these
object SubAreaSchema extends Schema {
val subAreas=table[SubArea]
on(subAreas)(subArea => declare(
subArea.id is (autoIncremented),
subArea.name is (unique)
))
}
You can define the relation in your schema with:
val areaToSubAreas = oneToManyRelation(areas, subAreas).via((a,sA) => s.idArea === sa.areaId)
To make that work, you would want to modify your SubArea class load the foreign key's id directly, as below with the areaId:String.
class SubArea(var idSubArea: String,
#BeanProperty
var name: String,
#BeanProperty
var areaId: String)
and then in the method body, if you want to have access to the object, you can use:
def area = areaToSubAreas.right(this)
which will yield an ManyToOne[Area] which you query, or use headOption on to convert to an Option[Area].
Conversely, if you need to reference the subareas on Area, you can use:
def subAreas = areaToSubAreas.left(this)
which will yield an OneToMany[Area] which can be iterated over, or you can also call toList.
Is it possible to implement BeanProperty for Optional variables? It would be useful with JPA.
It would be great if:
#BeanProperty var status: Option[String]
would add the following methods to the class:
def setStatus(s: String) { status = Some(s) }
def getStatus: String = status.get
Unfortunately not, but there is a simple workaround that brings best of both worlds:
#BeanProperty
var status: String
def statusOption = Option(status)
Note that JPA does not understand Option[T]. BTW if you use field-access as opposed to getter/setter access in JPA, #BeanProperty isn't even needed - the JPA provider will scan Java fields instead.
I have scala class like:
#Entity("users")
class User(#Required val cid: String, val isAdmin: Boolean = false, #Required val dateJoined: Date = new Date() ) {
#Id var id: ObjectId = _
#Reference
val foos = new ArrayList[Foo]
}
If it was a Java class I would simply put implements java.io.Serializable but this does not work in scala. Also is foos as declared above is private or public?
How do I use a #serializable scala object?
foos is public unless marked otherwise
scala 2.9.x also have an interface named Serializable, you may extends or mixin this. before 2.9.x the #serializable is the only choice.
You can add Serialized annotation on your Scala Class (at JPA Entity for example):
Because Serializable is a trait, you can mix it into a class, even if
your class already extends another class:
#SerialVersionUID(114L)
class Employee extends Person with Serializable ...
Se more details at this link:
https://www.safaribooksonline.com/library/view/scala-cookbook/9781449340292/ch12s08.html
An example of my Entity (JPA) class writed in scala, using Serialized properties:
import javax.persistence._
import scala.beans.BeanProperty
import java.util.Date
#SerialVersionUID(1234110L)
#Entity
#Table(name = "sport_token")
class Token() extends Serializable {
#Id
#SequenceGenerator(name="SPORT_TOKEN_SEQ",catalog="ESPORTES" , sequenceName="SPORT_TOKEN_SEQ", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE , generator="SPORT_TOKEN_SEQ")
#BeanProperty
var id: Int = _
#BeanProperty
#Column(name="token")
var token: String = _
#BeanProperty
#Column(name="active")
var active: Int = _
}
Is it possible to use named arguments in a Scala constructor, and later on override getters and setters without breaking the constructor interface or making the code extremely ugly?
Take the following bit of scala code
class Person( var FirstName: String, var LastName: String )
Nice and clean. This would create a simple class called person, which we could use in the following way
val john = new Person( FirstName="John", LastName="Doe" )
john.FirstName = "Joe"
println( john.FirstName )
Later, we decide we want to add some validation to the FirstName setter. As such, we create a new private local variable and override the getter and setter methods
class Person( var _FirstName: String, var _LastName: String ) {
def FirstName = _FirstName
def FirstName_= (value:String) = _FirstName = value
}
Still somewhat clean, however in order to do this, we've had to change the constructor argument names, thus breaking the external interface.
The first solution to this problem I came up with was
class Person {
var _FirstName:String = null
var LastName:String = null
def FirstName = _FirstName
def FirstName_= (value:String) = _FirstName = value
def this( FirstName: String, LastName: String ){
this()
this._FirstName = FirstName
this.LastName = LastName
}
}
Which is somewhat ugly and inelegant, and removes most of the nice reasons I was using scala in the first place.
Is there a better way of doing this?
tl;dr How to override getters/setters for members defined in the default constructor without making the code ugly or changing the public interface?
Did you consider using an companion object?
class Person private (f: String, l: String ) {
var FirstName = f
var LastName = l
}
object Person {
def apply(FirstName:String, LastName:String) =
new Person(FirstName, LastName)
}
If you're not already using implicit conversions to create the arguments, you can do something like this:
def validateName(s: String) = {
if (s.length>0 && s(0).isUpper) s
else throw new IllegalArgumentException(s+" is not a name!")
}
object Example {
private[Example] class ValidatedName(val s: String) { }
class Person(var firstName: ValidatedName, var lastName: String) { }
implicit def string2valid(s: String) = new ValidatedName(validateName(s))
implicit def valid2string(v: ValidatedName) = v.s
}
scala> new Example.Person("Joe","Schmoe")
res17: Example.Person = Example$Person#51887dd5
scala> new Example.Person("ee","cummings")
java.lang.IllegalArgumentException: ee is not a name!
It's not binary compatible, but it is source compatible (again, if the names weren't already relying upon implicit conversions).
Another slightly longer possibility is to create a stealth ancestor:
class CheckedPerson(private var first: String, var lastName: String) {
def firstName = first
def firstName_=(s: String) { first = validateName(s) }
}
class Person(firstName: String, lastName: String) extends
CheckedPerson(validateName(firstName),lastName) { }
for which I'm not sure about binary compatibility, but will definitely give source compatibility.
No. There is currently no way to do that, it's currently not the focus of research.
It is one of my major pet peeves I have with the language: There is no sensible way to combine constructor arguments and self-defined getter/setter methods.
If you're not happy with the functionality class Person( var FirstName: String, var LastName: String ) provides, it basically means "back to Java's verboseness".