Switch on Exposed

This commit is contained in:
Sebastien Deleuze
2016-03-17 17:45:03 +01:00
parent 991e4b77f1
commit d2b2e12cd7
13 changed files with 163 additions and 123 deletions

View File

@@ -37,6 +37,7 @@ dependencies {
compile('org.springframework.boot:spring-boot-starter-jdbc')
compile('org.springframework.boot:spring-boot-devtools')
compile('cz.jirutka.spring:spring-data-jdbc-repository:0.5.1')
compile('org.jetbrains.exposed:exposed:0.4-SNAPSHOT')
compile("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
// Always use the latest "dash revision", here -2

View File

@@ -2,43 +2,44 @@ package io.spring.messenger.config
import io.spring.messenger.domain.Message
import io.spring.messenger.domain.User
import io.spring.messenger.repository.MessageRepository
import io.spring.messenger.repository.UserRepository
import io.spring.messenger.repository.*
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.exists
import org.springframework.boot.CommandLineRunner
import javax.sql.DataSource
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.jdbc.datasource.DataSourceTransactionManager
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.annotation.EnableTransactionManagement
@EnableTransactionManagement
@Configuration
open class DatabaseConfig {
@Bean
open fun transactionManager(dataSource: DataSource): PlatformTransactionManager {
return DataSourceTransactionManager(dataSource)
}
open fun db(dataSource: DataSource) = Database.connect(dataSource)
@Bean
open fun init(userRepository: UserRepository, messageRepository: MessageRepository) = CommandLineRunner {
open fun init(db: Database, userRepository: UserRepository, messageRepository: MessageRepository) = CommandLineRunner {
val swhite = User("swhite", "Skyler", "White")
val jpinkman = User("jpinkman", "Jesse", "Pinkman")
val walter = User("wwhite", "Walter", "White")
val sgoodman = User("sgoodman", "Saul", "Goodman")
db.transaction {
logger.addLogger(StdOutSqlLogger())
if(userRepository.count() == 0L) {
userRepository.create(swhite)
userRepository.create(jpinkman)
userRepository.create(walter)
userRepository.create(sgoodman)
}
if(!Users.exists()) {
create(Users)
userRepository.create(swhite)
userRepository.create(jpinkman)
userRepository.create(walter)
userRepository.create(sgoodman)
}
if(messageRepository.count() == 0L) {
messageRepository.create(Message("This is a test!", swhite.userName))
if(!Messages.exists()) {
create(Messages)
messageRepository.create(Message("This is a test!", swhite.userName))
}
}
}

View File

@@ -7,5 +7,6 @@ data class Message(
var content: String,
var author: String,
var location: Point? = null,
@Id var id: Long? = null
)
@Id var id: Int? = null
)

View File

@@ -1,11 +0,0 @@
package io.spring.messenger.domain
import org.postgis.Point
import org.springframework.data.annotation.Id
data class MessageWithAuthor(
var content: String,
var author: User,
var location: Point? = null,
@Id var id: Long? = null
)

View File

@@ -1,44 +1,36 @@
package io.spring.messenger.repository
import com.nurkiewicz.jdbcrepository.JdbcRepository
import com.nurkiewicz.jdbcrepository.RowUnmapper
import com.nurkiewicz.jdbcrepository.TableDescription
import io.spring.messenger.domain.Message
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.UpdateBuilder
import org.postgis.PGbox2d
import org.postgis.PGgeometry
import org.postgis.Point
import org.springframework.jdbc.core.RowMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Repository
@Repository
open class MessageRepository : JdbcRepository<Message, Long>(mapper(), unmapper(), TableDescription("messages", "id")) {
open class MessageRepository @Autowired constructor(val db: Database) {
open fun findByBoundingBox(box: PGbox2d): List<Message>
= jdbcOperations.query("""SELECT * FROM ${table.name}
WHERE location &&
ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}
, 4326)""", rowMapper)
override fun <S : Message> postCreate(entity: S, generatedId: Number?): S {
if (generatedId != null) entity.id = generatedId.toLong()
return entity
open fun create(m: Message) = db.transaction {
m.id = Messages.insert(map(m)).get(Messages.id)
m
}
}
private fun mapper() = RowMapper<Message> {
rs, rowNum -> Message(
rs.getString("content"),
rs.getString("author"),
(rs.getObject("location") as PGgeometry?)?.geometry as Point?,
rs.getLong("id"))
}
open fun findAll() = db.transaction {
unmap(Messages.selectAll())
}
private fun unmapper() = RowUnmapper<Message> {
m ->
val map = mutableMapOf(Pair("id", m.id), Pair("content", m.content), Pair("author", m.author))
if (m.location != null) {
m.location!!.srid = 4326
map["location"] = PGgeometry(m.location)
}
map
}
open fun findByBoundingBox(box: PGbox2d) = db.transaction {
unmap(Messages.select { Users.location within box })
}
private fun map(m: Message): Messages.(UpdateBuilder<*>) -> Unit = {
if (m.id != null) it[id] = m.id
it[content] = m.content
it[author] = m.author
it[location] = m.location
}
private fun unmap(rows: SizedIterable<ResultRow>): List<Message> =
rows.map { Message(it[Messages.content], it[Messages.author], it[Messages.location], it[Messages.id]) }
}

View File

@@ -0,0 +1,39 @@
package io.spring.messenger.repository
import org.jetbrains.exposed.sql.*
import org.postgis.PGbox2d
import org.postgis.PGgeometry
import org.postgis.Point
fun Table.point(name: String, srid: Int = 4326): Column<Point> = registerColumn(name, PointColumnType())
class PointColumnType(val srid: Int = 4326): ColumnType() {
override fun sqlType(): String = "GEOMETRY(Point, $srid)"
override fun valueFromDB(value: Any): Any {
if (value is PGgeometry) {
return value.geometry
}
return value
}
override fun notNullValueToDB(value: Any): Any {
if (value is Point) {
if (value.srid == Point.UNKNOWN_SRID) value.srid = srid
return PGgeometry(value)
}
return value
}
}
infix fun ExpressionWithColumnType<*>.within(box: PGbox2d) : Op<Boolean> {
return WithinOp(this, box)
}
class WithinOp(val expr1: Expression<*>, val box: PGbox2d) : Op<Boolean>() {
override fun toSQL(queryBuilder: QueryBuilder) = """${expr1.toSQL(queryBuilder)}
&& ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}, 4326)"""
}

View File

@@ -0,0 +1,17 @@
package io.spring.messenger.repository
import org.jetbrains.exposed.sql.Table
object Messages : Table() {
val id = integer("id").autoIncrement().primaryKey()
val content = text("content")
val author = reference("author", Users.userName)
val location = point("location").nullable()
}
object Users : Table() {
val userName = text("user_name").primaryKey()
val firstName = text("first_name")
val lastName = text("last_name")
val location = point("location").nullable()
}

View File

@@ -1,47 +1,42 @@
package io.spring.messenger.repository
import com.nurkiewicz.jdbcrepository.JdbcRepository
import com.nurkiewicz.jdbcrepository.RowUnmapper
import io.spring.messenger.domain.User
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.UpdateBuilder
import org.postgis.PGbox2d
import org.postgis.PGgeometry
import org.postgis.Point
import org.springframework.jdbc.core.RowMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Repository
@Repository
open class UserRepository : JdbcRepository<User, String>(mapper(), unmapper(), "\"users\"", "user_name") {
open class UserRepository @Autowired constructor(val db: Database) {
open fun updateLocation(userName:String, location: Point): Unit {
open fun updateLocation(userName:String, location: Point) = db.transaction {
logger.addLogger(StdOutSqlLogger())
location.srid = 4326
jdbcOperations.update("UPDATE ${table.name} SET location = '${PGgeometry(location)}' WHERE user_name = '$userName'")
Users.update({Users.userName eq userName}) { it[Users.location] = location}
}
open fun findByBoundingBox(box: PGbox2d): List<User>
= jdbcOperations.query("""SELECT * FROM ${table.name}
WHERE location &&
ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}
, 4326)""", rowMapper)
open fun create(user: User) = db.transaction {
Users.insert( map(user) )
}
}
open fun findAll() = db.transaction {
unmap(Users.selectAll())
}
private fun mapper() = RowMapper<User> {
rs, rowNum -> User(
rs.getString("user_name"),
rs.getString("first_name"),
rs.getString("last_name"),
(rs.getObject("location") as PGgeometry?)?.geometry as Point?)
}
open fun findByBoundingBox(box: PGbox2d) = db.transaction {
unmap(Users.select { Users.location within box })
}
private fun map(u: User): Users.(UpdateBuilder<*>) -> Unit = {
it[userName] = u.userName
it[firstName] = u.firstName
it[lastName] = u.lastName
it[location] = u.location
}
private fun unmap(rows: SizedIterable<ResultRow>): List<User> =
rows.map { User(it[Users.userName], it[Users.firstName], it[Users.lastName], it[Users.location]) }
private fun unmapper() = RowUnmapper<User> {
user ->
val map = mutableMapOf<String, Any>(
Pair("user_name", user.userName),
Pair("first_name", user.firstName),
Pair("last_name", user.lastName))
if (user.location != null) {
user.location!!.srid = 4326
map["location"] = PGgeometry(user.location)
}
map;
}

View File

@@ -21,8 +21,9 @@ class MessageController @Autowired constructor(val repository: MessageRepository
fun findMessages() = repository.findAll()
@GetMapping("/bbox/{xMin},{yMin},{xMax},{yMax}")
fun findByBoundingBox(@PathVariable userName:String, @PathVariable xMin:Double,
@PathVariable yMin:Double, @PathVariable xMax:Double, @PathVariable yMax:Double)
fun findByBoundingBox(@PathVariable userName:String,
@PathVariable xMin:Double, @PathVariable yMin:Double,
@PathVariable xMax:Double, @PathVariable yMax:Double)
= repository.findByBoundingBox(PGbox2d(Point(xMin, yMin), Point(xMax, yMax)))
@GetMapping("/subscribe")

View File

@@ -0,0 +1,25 @@
package io.spring.messenger.web
import org.springframework.http.MediaType
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
import java.util.*
import java.util.Collections.synchronizedSet
class SseBroadcaster {
private var sseEmitters = synchronizedSet(HashSet<SseEmitter>());
fun subscribe(): SseEmitter {
val sseEmitter: SseEmitter = SseEmitter()
sseEmitter.onCompletion({ this.sseEmitters.remove(sseEmitter) });
this.sseEmitters.add(sseEmitter);
return sseEmitter
}
fun send(o:Any) {
synchronized (sseEmitters) {
sseEmitters.iterator().forEach { it.send(o, MediaType.APPLICATION_JSON) }
}
}
}

View File

@@ -15,15 +15,16 @@ class UserController @Autowired constructor(val repository: UserRepository) {
fun findAll() = repository.findAll()
@PostMapping
fun create(@RequestBody user: User) = repository.create(user)
fun create(@RequestBody u: User) = repository.create(u)
@PutMapping("/{userName}/location/{x},{y}") @ResponseStatus(NO_CONTENT)
fun updateLocation(@PathVariable userName:String, @PathVariable x: Double, @PathVariable y: Double)
= repository.updateLocation(userName, Point(x, y))
@GetMapping("/bbox/{xMin},{yMin},{xMax},{yMax}")
fun findByBoundingBox(@PathVariable userName:String, @PathVariable xMin:Double,
@PathVariable yMin:Double, @PathVariable xMax:Double, @PathVariable yMax:Double)
fun findByBoundingBox(@PathVariable userName:String,
@PathVariable xMin:Double, @PathVariable yMin:Double,
@PathVariable xMax:Double, @PathVariable yMax:Double)
= repository.findByBoundingBox(PGbox2d(Point(xMin, yMin), Point(xMax, yMax)))
}

View File

@@ -1,22 +0,0 @@
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE TABLE IF NOT EXISTS "users" (
user_name text PRIMARY KEY,
first_name text,
last_name text,
location GEOMETRY(Point, 4326)
);
CREATE INDEX IF NOT EXISTS users_gix
ON "users"
USING GIST (location);
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
content text NOT NULL,
author text REFERENCES users(user_name),
location GEOMETRY(Point, 4326)
);
CREATE INDEX IF NOT EXISTS messages_gix
ON messages
USING GIST (location);

View File

@@ -38,7 +38,7 @@ var centerDefined = false;
geolocation.on("change:position", function () {
var coordinates = geolocation.getPosition();
$.ajax({
url: "/user/" + $('#select-user').val() + "swhite/location/" + coordinates[0] + "," + coordinates[1], type: "PUT"
url: "/user/" + $('#select-user').val() + "/location/" + coordinates[0] + "," + coordinates[1], type: "PUT"
});
if (!centerDefined) {
view.setCenter(coordinates);