Slate Kit contains 3 modules for working with data and databases. These allow you to leverage existing code for low-level database calls, mid-level interfaces/abstractions for Entities/Repositories/Services or high-level ORM functionality.
Goal | Description |
1. Low-Level | Provide a low-level database API for JDBC and/or Android Sqlite |
2. Entities | Provide interfaces/defaults for Entities / Repositories / Services. |
3. ORM | Provide a light-weight ORM for both Android ( Sqlite ) and Server. |
This component is currently stable and works with MySql. Future versions will include support for:
Feature | Status | Description |
PostGres | In Progress | Support for PostGres database |
Android | In Progress | Implementations currently internal only but not yet open-sourced. |
Transactions | In Progress | For server side |
Exposed | Future | Integration with the JetBrains Exposed ORM instead of Slate Kit ORM |
repositories {
// other repositories
maven { url "http://dl.bintray.com/codehelixinc/slatekit" }
}
dependencies {
// other dependencies ...
// Common code for all components
compile 'com.slatekit:slatekit-common:1.0.0'
// For database calls to work with JDBC
compile 'com.slatekit:slatekit-db:1.0.0'
// Entities / Repositories / Service interfaces and defaults
compile 'com.slatekit:slatekit-entities:1.0.0'
// ORM functionality to map to/from models and records and query
compile 'com.slatekit:slatekit-orm:1.0.0'
}
Jar | slatekit.entities.jar |
Package | slatekit.entities |
Sources | slatekit-entities |
Example | Example_Entities.kt |
Version | |
License | |
Requires | See build.gradle for more info. |
fun quick_sample() {
}
Name | Description | More |
1. Connections | Setting up database connections | more |
2. Databases | Low-level database calls and operations | more |
3. Records | Working with records from the database | more |
4. Mappers | Setting up mappers for converting to/from models to records | more |
5. Repositories | Using Repositories for common CRUD operations on data | more |
6. Services | Entity Services for managing data models | more |
7. Vendors | Support for different databases and setup | more |
8. Schemas | Defining your data models as schema for auto-mapping | more |
9. ORM | Light-weight ORM layer to managing/querying databases | more |
10. Events | Observing data operations as events | more |
11. Migrations | Dealing with upgrades to schemas | more |
You can setup connections different ways from explicit creation to loading from configuration settings. This uses DbCon. The Connections is used to store multiple connections by name.
// Connection
// 1. Explicit creation
val con1 = DbConString(Vendor.MySql.driver, "jdbc:mysql://localhost/default", "user1", "pswd3210")
// 2. From config
val cfg = Config.of("env.dev.conf")
val con2 = cfg.dbCon("db")
// Connections ( collection of multiple connections )
// 1. From single connection
val cons1 = Connections.of(con1)
// 2. From config: Shortcut for Connections.of(conf.dbCon("db"))
val cons2 = Connections.from(cfg)
// 3. From multiple named connections
val cons3 = Connections.named(
listOf(
Pair("db1", con1),
Pair("db2", con2)
))
The IDb interface provides a thin abstraction over data access calls, particularly JDBC. This makes it useful for abstracting data access to both Server side database calls via JDBC and Android database calls to Sqlite. Another benefit is facilitating unit-testing/mocking of other data related components in Slate Kit. The interface and supporting implementation for JDBC Db support the typical operations to perform execute, update, query, scalar, stored proc calls.
val con1 = DbConString(Vendor.MySql.driver, "jdbc:mysql://localhost/default", "user1", "pswd3210")
val db = Db(con1)
// Inserts
val id1 = db.insert("insert into `city`(`name`) values( 'ny' )")
val id2 = db.insert("insert into `city`(`name`) values( ? )", listOf("ny"))
val id3 = db.insertGetId("insert into `city`(`name`) values( ? )", listOf("ny")).toLong()
// Updates
val updated1 = db.update("update `city` set `alias` = 'nyc' where id = 2")
val updated2 = db.update("update `city` set `alias` = 'nyc' where id = ?", listOf(id2))
// Deletes
val deleted1 = db.update("delete from `city` where id = 2")
val deleted2 = db.update("delete from `city` where id = ?", listOf(2))
// Procs
val procUpdate1 = db.callUpdate("dbtests_activate_by_id", listOf(2))
val procQuery1 = db.callQuery("dbtests_max_by_id",
callback = { rs -> rs.getString(0) }, inputs = listOf(id2))
// Queries ( mapOne, mapMany )
val city1 = db.mapOne<City>("select * from `city` where id = ?", listOf(1)) { rs ->
City(rs.getLong("id"), rs.getString("name"), rs.getString("alias"))
}
val city2 = db.mapAll<City>("select * from `city` where id < ?", listOf(2)) { rs ->
City(rs.getLong("id"), rs.getString("name"), rs.getString("alias"))
}
// Scalar calls
val total1 = db.getScalarBool("select isActive from users where userid = ?", listOf(1))
val total2 = db.getScalarInt("select age from users where userid = ?", listOf(1))
val total3 = db.getScalarLong("select account from users where userid = ?", listOf(1))
val total4 = db.getScalarFloat("select salary from users where userid = ?", listOf(1))
val total5 = db.getScalarDouble("select total from users where userid = ?", listOf(1))
val total6 = db.getScalarString("select email from users where userid = ?", listOf(1))
val total7 = db.getScalarLocalDate("select startDate from users where userid = ?", listOf(1))
val total8 = db.getScalarLocalTime("select startHour from users where userid = ?", listOf(1))
val total9 = db.getScalarLocalDateTime("select registered from users where userid = ?", listOf(1))
val total10 = db.getScalarZonedDateTime("select activated from users where userid = ?", listOf(1))
Because the Database abstracts over JDBC, there is also a corresponding interface Record to abstract over JDBC ResultSets ( this is particularly useful for Android ). The implementation for JDBC is RecordSet
// Sample connection/DB
val con1 = DbConString(Vendor.MySql.driver, "jdbc:mysql://localhost/default", "user1", "pswd3210")
val db = Db(con1)
// Record via wrapped JDBC ResultSet
val city = db.mapOne<City>("select * from `city` where id = ?", listOf(1)) { rs:Record ->
City( rs.getLong("id"),
rs.getString("name"),
rs.getString("alias")
)
}
// Simulating a Record from a list of key/value pairs
val record:Record = RecordMap(
ListMap(
listOf(
Pair("id", 1L),
Pair("uuid", "ABC"),
Pair("email", "kishore@abc.com"),
Pair("isActive", true),
Pair("age", 35),
Pair("status", Status.InActive),
Pair("salary", 400.5),
Pair("uid", UUID.fromString("ad6ec896-bc1e-4430-b13c-88e3d4924a6a")),
Pair("createdAt", DateTimes.of(2017, 1, 1, 12, 0, 0, 0))
)
)
)
// There are getX methods, getXOrNull, getXOrDefault
println(record.getBool("isActive"))
println(record.getBoolOrNull("isActive"))
println(record.getBoolOrElse("isActive", false))
// There are several methods for various types
println(record.getString("email"))
println(record.getBool("isActive"))
println(record.getInt("age"))
println(record.getLong("id"))
println(record.getDouble("salary"))
println(record.getUUID("uuid"))
println(record.getZonedDateTime("createdAt"))
A Mapper simply encodes/decodes a data model to/from records. The Values is simply a container for key/value pairs representing all the fields on the field that can be persisted/saved to a database. You can simply implement the mapping yourself ( explicitly without any reflection ) or use the OrmMapper ( see ORM section ) along with a schema for the Model to leverage automatic mapping.
// Mapper: manual field mapping
val mapper = object: Mapper<Long, User> {
override fun encode(model:User, action: DataAction): Values {
return listOf(
Value("id", model.id),
Value("firstname", model.firstName),
Value("lastname", model.lastName),
Value("email", model.email)
)
}
override fun decode(record: Record): User? {
return User(
id = record.getLong("id"),
firstName = record.getString("firstname"),
lastName = record.getString("lastname"),
email = record.getString("email")
)
}
}
With the databases, records, mappers in place, the next step is to have a Repo component that can handle the common CRUD operations on model. The implementations for these are SqlRepo
// Setup: This is boiler-plate that can be moved
// to a helper function/builder
// 1. connection
// 2. database
// 3. mapper
// 4. repo
// 5. service
val con = DbConString(Vendor.H2.driver, "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "", "")
val db = Db.open(con)
val mapper:Mapper<Long, City> = mapper()
val repo = Repo.h2<Long, City>(db, mapper)
// Common CRUD ( Create, Retrieve, Update, Delete ) operations available
val city = City(0, "Brooklyn", "bk")
// Create
val id = repo.create(city)
repo.save(City(0, "New York", "NYC"))
// Checks
repo.any()
repo.count()
// Gets
repo.getAll()
repo.getById(1)
repo.getByIds(listOf(1, 2))
repo.first()
repo.last()
repo.recent(10)
repo.oldest(10)
// Finds
val item1 = repo.findOneByField("name", "=", "Brooklyn")
val items2 = repo.findByField("name", "=", "Brooklyn")
val items3 = repo.findByFields(listOf(Pair("name", "Brooklyn")))
val items4= repo.findIn("name", listOf("Queens", "Brooklyn"))
repo.find(repo.query())
// Updates
val updated = city.copy(id = id, name = "Queens")
repo.update(updated)
repo.patch(id, listOf(Pair("name", "Queens City"), Pair("alias", "QCity")))
repo.updateByField("name", "Queens", "Queens City")
repo.updateField("tag", "test")
repo.updateByProc("update_alias", listOf(1, "QCity"))
// Deletes
repo.deleteAll()
repo.delete(city)
repo.deleteById(2)
repo.deleteByIds(listOf(1, 2))
repo.deleteByField(City::id.name, Op.Eq, 1)
repo.deleteByQuery(repo.query().where(City::id.name, Op.Eq, 1))
Entity Services expose CRUD operations on your data model in a “managed way” via Service layer. This is to avoid other code/services from directly modifying your models via the Repo/Repositories and for allow you to override various methods to add in your custom logic for CRUD operations. You can also pick and choose which features (Read only, Write only, full CRUD ) you want applicable for your service. This allows using Slate Kit for standardizing Repositories / Services
// Setup: This is boiler-plate that can be moved
// to a helper function/builder
// 1. connection
// 2. database
// 3. mapper
// 4. repo
// 5. service
val con = DbConString(Vendor.H2.driver, "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "", "")
val db = Db.open(con)
val mapper:Mapper<Long, City> = mapper()
val repo = Repo.h2<Long, City>(db, mapper)
val service = EntityService<Long, City>(repo)
// CRUD operations
val city = City(0, "Brooklyn", "bk")
// Create
val id = service.create(city)
service.save(City(0, "New York", "NYC"))
// Checks
service.any()
service.count()
// Gets
service.getAll()
service.getById(1)
service.getByIds(listOf(1, 2))
service.first()
service.last()
service.recent(10)
service.oldest(10)
// Finds
val item1 = service.findOneByField(User::email, Op.Eq,"Brooklyn")
val items2 = service.findByField(City::name, Op.Eq, "Brooklyn")
val items3 = service.findByFields(listOf(Pair("name", "Brooklyn")))
val items4= service.findIn(City::name, listOf("Queens", "Brooklyn"))
val items5 = service.findByQuery(Query().where(City::name.name, Op.Eq, "Brooklyn"))
// Updates
val updated = city.copy(id = id, name = "Queens")
service.update(updated)
service.patch(id, listOf(Pair("name", "Queens City"), Pair("alias", "QCity")))
service.updateByField(City::name, "Queens", "Queens City")
service.updateField(City::name, "test")
service.updateByProc("update_alias", listOf(1, "QCity"))
// Deletes
service.deleteAll()
service.delete(city)
service.deleteById(2)
service.deleteByIds(listOf(1, 2))
service.deleteByField(City::id, Op.Eq, 1)
service.deleteByQuery(Query().where(City::id.name, Op.Eq, 1))
You can set up auto-mapping of models to/from records using the Slate Kit Model Schemas. This leverages Kotlins Property references for safely get the name/type information and stores the definition of a model. This model is then supplied to a Mapper to auto-map the model to Values for inserts/updates, and for mapping Records back to the instance. There are 2 ways set up the schema, either explicitly construction or with annotations.
Explicitly setup of a Model/Schema
import slatekit.meta.Schema
// OPTION 1: Explicit Schema Setup
// Uses property references for strongly typed setup
// Also, can use KType to get if field is required ( via isMarkedNullabel )
object UserSchema : Schema<Long, User>(Long::class, User::class, "user") {
val id = id (User::id )
val email = field (User::email , min = 10, max = 50, indexed = true)
val first = field (User::first , min = 10, max = 50)
val last = field (User::last , min = 10, max = 50)
val age = field (User::age )
val salary = field (User::salary )
val active = field (User::active )
val registered = field (User::registered)
val createdAt = field (User::updatedAt , category = FieldCategory.Meta)
val updatedAt = field (User::updatedAt , category = FieldCategory.Meta)
}
// Access the model which stores all the fields
val model = UserSchema.model
// Iterate over the models
model.fields.forEach {
println("field: name=${it.name}, ${it.storedName}, ${it.isRequired}, ${it.dataTpe}")
}
You can also use annotations at the source of the data model.
data class User(
@property:Id()
override val id:Long = 0L,
@property:Field(length = 30)
val email:String = "",
@property:Field(length = 30)
val first:String = "",
@property:Field(length = 30)
val last:String = "",
@property:Field()
val isMale:Boolean = false,
@property:Field()
val age:Int = 35,
@property:Field()
val active:Boolean = false,
@property:Field()
val salary:Double = 100.00,
@property:Field()
val registered:DateTime? = null
) : EntityWithId<Long> {
override fun isPersisted(): Boolean = id > 0
}
// Access the model which stores all the fields
val model = ModelMapper.loadSchema(User::class)
// Iterate over the models
model.fields.forEach {
println("field: name=${it.name}, ${it.storedName}, ${it.isRequired}, ${it.dataTpe}")
}
By having the models defined with a schema, we can then set up the auto-mapping of models. This builds the Entity Service using all the individual parts shown from prior examples.
// Setup: This is boiler-plate that can be moved
// to a helper function/builder
// 1. connection
// 2. database
// 3. mapper
// 4. repo
// 5. service
val con = DbConString(Vendor.H2.driver, "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "", "")
val db = Db.open(con)
val model = UserSchema.model
val mapper:Mapper<Long, City> = OrmMapper<Long, City>(model, db, Long::class, City::class)
val repo = Repo.h2<Long, City>(db, mapper)
val service = EntityService<Long, City>(repo)
service.save(City(0, "New York", "NYC"))
// CRUD operations
service.any()
service.count()
service.getById(1)
Functionality available, docs coming soon.
// COMING SOON
Functionality available, docs coming soon.
// COMING SOON