/
DatabaseComponent.scala
101 lines (85 loc) · 4.33 KB
/
DatabaseComponent.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package scala.slick.backend
import scala.util.DynamicVariable
import scala.slick.SlickException
import java.io.Closeable
import scala.util.control.NonFatal
/** Backend cake slice for the basic database and session handling features.
* Concrete backends like `JdbcBackend` extend this type and provide concrete
* types for `Database`, `DatabaseFactory` and `Session`. */
trait DatabaseComponent { self =>
/** The type of database objects used by this backend. */
type Database <: DatabaseDef
/** The type of the database factory used by this backend. */
type DatabaseFactory <: DatabaseFactoryDef
/** The type of session objects used by this backend. */
type Session >: Null <: SessionDef
/** The database factory */
val Database: DatabaseFactory
/** A database instance to which connections can be created. */
trait DatabaseDef {
/** Create a new session. The session needs to be closed explicitly by calling its close() method. */
def createSession(): Session
/** Run the supplied function with a new session and automatically close the session at the end.
* Exceptions thrown while closing the session are propagated, but only if the code block using the
* session terminated normally. Otherwise the first exception wins. */
def withSession[T](f: Session => T): T = {
val s = createSession()
var ok = false
try {
val res = f(s)
ok = true
res
} finally {
if(ok) s.close() // Let exceptions propagate normally
else {
// f(s) threw an exception, so don't replace it with an Exception from close()
try s.close() catch { case _: Throwable => }
}
}
}
/** Run the supplied thunk with a new session and automatically close the
* session at the end.
* The session is stored in a dynamic (inheritable thread-local) variable
* which can be accessed with the implicit function in
* Database.dynamicSession. */
def withDynSession[T](f: => T): T = withSession { s: Session => withDynamicSession(s)(f) }
/** Run the supplied function with a new session in a transaction and automatically close the session at the end. */
def withTransaction[T](f: Session => T): T = withSession { s => s.withTransaction(f(s)) }
/** Run the supplied thunk with a new session in a transaction and
* automatically close the session at the end.
* The session is stored in a dynamic (inheritable thread-local) variable
* which can be accessed with the implicit function in
* Database.dynamicSession. */
def withDynTransaction[T](f: => T): T = withDynSession { Database.dynamicSession.withTransaction(f) }
}
private[this] val dyn = new DynamicVariable[Session](null)
/** Run a block of code with the specified `Session` bound to the thread-local `dynamicSession`. */
protected def withDynamicSession[T](s: Session)(f: => T): T = dyn.withValue(s)(f)
/** Factory methods for creating `Database` instances. */
trait DatabaseFactoryDef {
/** An implicit function that returns the thread-local session in a withSession block. */
implicit def dynamicSession: Session = {
val s = dyn.value
if(s eq null)
throw new SlickException("No implicit session available; dynamicSession can only be used within a withDynSession block")
else s
}
}
/** A logical session of a `Database`. The underlying database connection is created lazily on demand. */
trait SessionDef extends Closeable {
/** Close this Session. */
def close(): Unit
/** Call this method within a `withTransaction` call to roll back the current
* transaction after `withTransaction` returns. */
def rollback(): Unit
/** Run the supplied function within a transaction. If the function throws an Exception
* or the session's `rollback()` method is called, the transaction is rolled back,
* otherwise it is committed when the function returns. */
def withTransaction[T](f: => T): T
/** Use this Session as the `dynamicSession` for running the supplied thunk. */
def asDynamicSession[T](f: => T): T = withDynamicSession[T](this.asInstanceOf[Session])(f)
/** Force an actual database session to be opened. Slick sessions are lazy, so you do not
* get a real database connection until you need it or you call force() on the session. */
def force(): Unit
}
}