Spring Data JPA introduces a nice feature, "query by example" (QBE). You express your search criteria by constructing an instance of the entity.
You do not have to write JPQL. It uses less "magic" than does repository query derivation. The syntax is nice. It prevents explosions of trivial repository code. It survives refactors very well.
There's a problem though: QBE only works if you can partially construct an object.
Here's my entity:
#Entity
#Table(name="product")
data class Product(
#Id val id: String,
val city: String,
val shopName: String,
val productName: String,
val productVersion: Short
)
Here's my repository (empty! this is a nice thing about QBE):
#Repository
interface ProductRepository : JpaRepository<Product, String>
And here's how you would fetch a List<Product> — all the products that are sold in some shop, in some city:
productRepository.findAll(Example.of(Product(city = "London", shopName="OkayTea")))
Or at least, that's what I want to do. There's a problem. It's not possible to construct this object:
Product(city = "London", shopName="OkayTea")
This is because Product's constructor requires that all its fields be defined. And indeed: that's what I want most of the time.
The usual compromise in Java would be: construct entities using no-args constructor, make everything mutable, have no guarantees about completedness.
Is there a nice Kotlin pattern to solve this problem:
generally require that all args are instantiated on construction
provide also some mechanism to produce partially-constructed instances for use with Example API
Admittedly these look like totally conflicting goals. But maybe there's another way to approach this?
For example: maybe we can make a mock/proxy object, which appears to be a Product, but doesn't have the same construction constraints?
You can query by example using kotlin data classes with non-null fields, however it will not look as good as java code.
val matcher = ExampleMatcher.matching()
.withMatcher("city", ExampleMatcher.GenericPropertyMatcher().exact())
.withMatcher("shopName", ExampleMatcher.GenericPropertyMatcher().exact())
.withIgnorePaths("id", "productName", "productVersion")
val product = Product(
id = "",
city = "London",
shopName = "OkayTea",
productName = "",
productVersion = 0
)
productRepository.findAll(Example.of(product, matcher))
If you are using it for integration tests and you don't want to pollute your Repository interface with methods which are only used in said tests and also
you have a lot of fields in the database entity class, you can create an extension function that extracts fields which will be ignored in the query.
private fun <T : Any> KClass<T>.ignoredProperties(vararg exclusions: String): Array<String> {
return declaredMemberProperties
.filterNot { exclusions.contains(it.name) }
.map { it.name }
.toTypedArray()
}
and use it like this:
val ignoredFields = Product::class.ignoredProperties("city", "shopName")
val matcher = ExampleMatcher.matching()
.withMatcher("city", ExampleMatcher.GenericPropertyMatcher().exact())
.withMatcher("shopName", ExampleMatcher.GenericPropertyMatcher().exact())
.withIgnorePaths(*ignoredFields)
because the parameters on the primary constructor is not optional and not nullable. you can makes parameters nullable and set a default value null for each, for example:
#Entity
#Table(name = "product")
data class Product(
#Id val id: String? = null,
val city: String? = null,
val shopName: String? = null,
val productName: String? = null,
val productVersion: Short? = null
)
However, you must operates Product properties with safe-call ?., for example:
val product = Product()
// safe-call ---v
val cityToLowerCase = product.city?.toLowerCase()
Related
Can I have Nullable parameters in Scala object.
Similar to Nullable in C# and the syntactic sugar:
public int? Age {get; set;}
public string Name {get; set;}
public bool? isAdmin {get; set;}
Can I create scala class where the object can have some of these attributes set while others not?
I intend to have multiple constructors for different purposes. I want to ensure a null value implies the field is not set.
UPDATE
for example I have the following case class
case class Employee(age: Int, salary: Int, scaleNum : Int,
name: String, memberId: Int )
I am using GSON to serialize the class in JSON.
In some cases however, I don't want to pass values for some of the parameters such that the GSON serializer will only include the parameters that have non-NULL value.
How can I achieve that with Options or otherwise?
A common declaration in Scala for your example would be:
case class Employee(
age: Option[Int], // For int? Age
name: String // For string Name
// your other decls ...
)
Then you can use the type easily:
val john = Employee( age = Some(10), name = "John" )
While Scala 2 allows null values for references types (like String, etc) it is slowly changing starting with Scala 3 (https://dotty.epfl.ch/docs/reference/other-new-features/explicit-nulls.html).
JSON support
Java libraries (like GSON) don't know anything about Scala, so you should consider using other libraries for JSON support that support Scala:
circe
play json
jsoniter-scala
uPickle
etc
Those libraries not just aware of Option[] in your class definitions, but also have improved support for Scala collections, implicits, default values and other Scala language features.
It is really important to choose an appropriate library for this, because with the Java JSON libs you will end up with Java-style classes and code, compatibility issues with other Scala libs.
With the Circe your example would be:
import io.circe._
import io.circe.syntax._
import io.circe.parser._
import io.circe.generic.auto._
val john = Employee( age = Some(10), name = "John" )
val johnAsJson = john.asJson.dropNullValues
decode[Employee]( johnAsJson ) match {
case Right(johnBack) => ??? // johnBack now is the same as john
case Left(err) => ??? // handle JSON parse exceptions
}
Null coalescing operator
Now you might be looking where is the Null Coalescing Operator (?? in C#, ? in Kotlin, ...) in Scala.
The direct answer - there is none in the language itself. In Scala we work with Option (and other monadic structures, or ADT) in FP way.
That means, for example:
case class Address(zip : Option[String])
case class Employee(
address: Option[Address]
)
val john = Employee( address = Some( Address( zip = Some("1111A") )) )
you should avoid this style:
if (john.address.isDefined) {
if(john.address.zip.isDefined) {
...
}
}
you can use map/flatMaps instead:
john.address.flatMap { address =>
address.zip.map { zip =>
// your zip and address value
??
}
}
// or if you need only zip in a short form:
john.address.flatMap(_.zip).map { zip =>
// your zip value
??
}
or for-comprehension syntax (which is based on the same flatMaps):
for {
address <- john.address
zip <- address.zip
}
yield {
// doing something with zip and address
??
}
The important part is that idiomatic way to solve this in Scala mostly based on patterns from FP.
EDIT: For those who wonder how I plan to solve it according to the accepted answer, see Nested Values here.
I'm using Play Framework with Scala and Reactive Mongo.
Currently I'm creating my case classes and forms like this:
case class Person(
_id : Option[BSONObjectID],
name: string,
city: string)
object Person {
val form: Form[Person] = Form {
mapping(
"_id" -> optional(of[String] verifying pattern(
"""[a-fA-F0-9]{24}""".r,
"constraint.objectId",
"error.objectId")),
"name"-> text,
"city"-> text)
{ (id,name, city) => Person(id.map(new BSONObjectID(_)), name, city) }
{ person =>Some(person._id.map(_.stringify), person.name, person.city) }
}
}
If I was using a simple type in the _id property, like String, I could do something simpler, like:
object Person {
val form: Form[Person] = Form {
mapping(
"_id" -> text,
"name"-> text,
"city"-> text
)(Person.apply)(Person.unapply)
}
}
So I thought I could create my own apply method that would change the first parameter, using currying. I would define something like this:
def apply2(id: Option[String]) = {
val bsonid = id.map(new BSONObjectID(_))
(Person.apply _).curried(bsonid)
}
My theory, which implementation doesn't work, is that I would partially apply a BSONObjectID parameter to the Person.apply function, which value would come from the apply2 parameter called id. It doesn't work.
I'm a lazy guy who doesn't want to type a bunch of things just because now I have a new situation which isn't supported by default... The currying is one of my bets, but any solution that would make it easier to create a Form is acceptable.
I just need a way to identify the object so I can delete or update it after, but the current way is kinda boring to type, and I think that the _id field created by MongoDB is perfect.
Is there a way I could make things easier or I just need to stop being lazy?
You can make things easier by creating 2 case classes.
One to be used when data entry is in place
case class PersonData(name: String, city: String)
and another one to represent real Person in your model
case class Person(_id: BSONObjectID, name: String, city: String)
and in Person object create method:
def fromData(data: PersonData) = Person(
id = new BSONObjectID(),
name = data.name,
city = data.city)
then your mapping From[PersonData] can be simpler, and You avoid Option[BSONObjectID] flying around your model.
I'm currently having issues with Salat. Hope you guys can help me!
Here's the case class that is driving me crazy:
object UserDAO extends SalatDAO[User, ObjectId](
collection = DB("users") //Returns the "users" MongoCollection
)
case class User(
_id: ObjectId = new ObjectId,
firstName: String,
lastName: String,
screenName: String,
phoneNumber: PhoneNumber,
validated: Boolean = false)
PhoneNumber is an instance of type com.google.i18n.phonenumbers.Phonenumber$PhoneNumber (I'm using libphonenumber)
This is my custom transformer:
class PhoneNumberTransformer extends CustomTransformer[PhoneNumber, String] {
val phoneNumberUtils = PhoneNumberUtil.getInstance()
def deserialize(b: String) = phoneNumberUtils.parse(b, "UK")
def serialize(a: PhoneNumber) = phoneNumberUtils.format(a, PhoneNumberFormat.INTERNATIONAL)
}
This is my custom context:
package object model {
implicit val ctx = new Context {
val name = "Custom Salat Context"
}
ctx.registerCustomTransformer(new PhoneNumberTransformer)
}
If I try to insert a new User document using UserDAO, I get this exception:
project java.lang.IllegalArgumentException: can't serialize class com.google.i18n.phonenumbers.Phonenumber$PhoneNumber
project at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:284)
project at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:185)
project at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:131)
project at com.mongodb.DefaultDBEncoder.writeObject(DefaultDBEncoder.java:33)
[...]
Any idea on how to solve this?
Thanks
Salat developer here. I'm not familiar with libphonenumber but this is most likely breaking down because it looks like you're trying to serialize an inner class.
Something to try. If you copy pasted the PhoneNumber class to the top level of a local package (not inside an object, trait, or class), extending the relevant class/interface that bring the i18n goodness, and changed the type param to point at this class instead, does it work?
If so, the problem is that Salat doesn't support inner classes. If not, we'll have to look further.
You cannot serialize Java classes directly to Salat. You need to write either a custom salat serializer or write PhoneNumber as a case class
In Java, I do a lot of data integration work. One thing that comes up all the time is mapping data between multiple systems. So i'm constantly doing things like this
public enum DataField{
Field1("xmlField", "dbField", "system1Field";
private String xml;
private String db;
private String sys;
private DataField(String xml, String db, String sys){
this.xml = xml;
this.db = db;
this.sys = sys;
}
public getXml(){
return this.xml;
}
public static DataField valueOfXml(String xml){
for (DataField d : this.values()){
if (d.xml.equals(xml)){ return d;}
}
}
bla, bla bla
}
What this allows me to do is put the field name DataField in all my messaging and be able to map what that field is called in multiple systems. So in my XML, it may be firstname, in my database, it may be called first_name but in my external interface system, it may be called first. This pattern pulls all of that together very nicely and makes messaging in these types of systems very easy in a tight, type safe way.
Now I don't remember why Scala changed the enumeration implementation but I remember it made sense when I read it. But the question is, what would I use in Scala to replace this design pattern? I hate to lose it because it is very useful and fundamental to a lot of systems I write on a given day.
thanks
I managed to make up this kind-of replacement for your case:
sealed class DataField(val xml: String, val db: String, val sys: String)
object DataField {
case object Field1 extends DataField("xmlField1", "dbField1", "system1Field")
case object Field2 extends DataField("xmlField2", "dbField2", "system2Field")
case object Field3 extends DataField("xmlField3", "dbField3", "system3Field")
val values = List(Field1, Field2, Field3)
def valueOfXml(xml: String) =
values.find(_.xml == xml).get
}
The annoying thing is that we have to manually create the values list. In this case however, we can do some macro hacking to reduce the boilerplate a bit:
import scala.language.experimental.macros
import scala.reflect.macros.Context
object Macros {
def caseObjectsFor[T]: List[T] = macro caseObjectsFor_impl[T]
def caseObjectsFor_impl[T: c.WeakTypeTag](c: Context): c.Expr[List[T]] = {
import c.universe._
val baseClassSymbol = weakTypeOf[T].typeSymbol.asClass
val caseObjectSymbols = baseClassSymbol.knownDirectSubclasses.toList.collect {
case s if s.isModuleClass && s.asClass.isCaseClass => s.asClass.module
}
val listObjectSym = typeOf[List.type].termSymbol
c.Expr[List[T]](Apply(Ident(listObjectSym), caseObjectSymbols.map(s => Ident(s))))
}
}
Then we can do this:
val values = Macros.caseObjectsFor[DataField]
instead of manually listing all the case objects.
For this to work, it is essential that the base class is declared as sealed.
You could always do what I do, and keep writing the enums in Java.
Out of 62 .java files in my source tree, 61 are enums, and the other one is a package-info.java.
Schema.org is markup vocabulary (for the web) and defines a number of types in terms of properties (no methods). I am currently trying to model parts of that schema in Scala as internal model classes to be used in conjunction with a document-oriented database (MongoDB) and a web framework.
As can be seen in the definition of LocalBusiness, schema.org uses multiple inheritance to also include properties from the "Place" type. So my question is: How would you model such a schema in Scala?
I have come up with two solutions so far. The first one use regular classes to model a single inheritance tree and uses traits to mixin those additional properties.
trait ThingA {
var name: String = ""
var url: String = ""
}
trait OrganizationA {
var email: String = ""
}
trait PlaceA {
var x: String = ""
var y: String = ""
}
trait LocalBusinessA {
var priceRange: String = ""
}
class OrganizationClassA extends ThingA with OrganizationA {}
class LocalBusinessClassA extends OrganizationClassA with PlaceA with LocalBusinessA {}
The second version tries to use case classes. However, since case class inheritance is deprecated, I cannot model the main hierarchy so easily.
trait ThingB {
val name: String
}
trait OrganizationB {
val email: String
}
trait PlaceB {
val x: String
val y: String
}
trait LocalBusinessB {
val priceRange: String
}
case class OrganizationClassB(val name: String, val email: String) extends ThingB with OrganizationB
case class LocalBusinessClassB(val name: String, val email: String, val x: String, val y: String, val priceRange: String) extends ThingB with OrganizationB with PlaceB with LocalBusinessB
Is there a better way to model this? I could use composition similar to
case class LocalBusinessClassC(val thing:ThingClass, val place: PlaceClass, ...)
but then of course, LocalBusiness cannot be used when a "Place" is expected, for example when I try to render something on Google Maps.
What works best for you depends greatly on how you want to map your objects to the underlying datastore.
Given the need for multiple inheritance, and approach that might be worth considering would be to just use traits. This gives you multiple inheritance with the least amount of code duplication or boilerplating.
trait Thing {
val name: String // required
val url: Option[String] = None // reasonable default
}
trait Organization extends Thing {
val email: Option[String] = None
}
trait Place extends Thing {
val x: String
val y: String
}
trait LocalBusiness extends Organization with Place {
val priceRange: String
}
Note that Organization extends Thing, as does Place, just as in schema.org.
To instantiate them, you create anonymous inner classes that specify the values of all attributes.
object UseIt extends App {
val home = new Place {
val name = "Home"
val x = "-86.586104"
val y = "34.730369"
}
val oz = new Place {
val name = "Oz"
val x = "151.206890"
val y = "-33.873651"
}
val paulis = new LocalBusiness {
val name = "Pauli's"
override val url = "http://www.paulisbarandgrill.com/"
val x = "-86.713660"
val y = "34.755092"
val priceRange = "$$$"
}
}
If any fields have a reasonable default value, you can specify the default value in the trait.
I left fields without value as empty strings, but it probably makes more sense to make optional fields of type Option[String], to better indicate that their value is not set. You liked using Option, so I'm using Option.
The downside of this approach is that the compiler generates an anonymous inner class every place you instantiate one of the traits. This could give you an explosion of .class files. More importantly, though, it means that different instances of the same trait will have different types.
Edit:
In regards to how you would use this to load objects from the database, that depends greatly on how you access your database. If you use an object mapper, you'll want to structure your model objects in the way that the mapper expects them to be structured. If this sort of trick works with your object mapper, I'll be surprised.
If you're writing your own data access layer, then you can simply use a DAO or repository pattern for data access, putting the logic to build the anonymous inner classes in there.
This is just one way to structure these objects. It's not even the best way, but it demonstrates the point.
trait Database {
// treats objects as simple key/value pairs
def findObject(id: String): Option[Map[String, String]]
}
class ThingRepo(db: Database) {
def findThing(id: String): Option[Thing] = {
// Note that in this way, malformed objects (i.e. missing name) simply
// return None. Logging or other responses for malformed objects is left
// as an exercise :-)
for {
fields <- db.findObject(id) // load object from database
name <- field.get("name") // extract required field
} yield {
new Thing {
val name = name
val url = field.get("url")
}
}
}
}
There's a bit more to it than that (how you identify objects, how you store them in the database, how you wire up repository, how you'll handle polymorphic queries, etc.). But this should be a good start.