2016-11-02 1 views
1

Je veux pouvoir entourer un bloc de code avec une transaction. Le code d'appel doit être aussi simple que cela:Comment rendre le code appelant agnostique de paramètre implicite passé sous le capot?

transactional { 
    save("something") 
} 

Je pensais à faire fonctionner transactional comme ceci:

def transactional(block: => Unit): Unit = { 
    implicit val conn: Connection = ??? 

    conn.begin() 
    try { 
    block 
    conn.commit() 
    } catch { 
    case ex: Exception => 
     conn.rollback() 
     throw ex 
    } finally { 
    conn.close() 
    } 
} 

Maintenant, la méthode save aura besoin de faire quelque chose avec la connexion, mais je veux pour rendre le code appelant agnostique de cela (voir ci-dessus). Je naïvement mis en œuvre comme ceci:

def save(operation: String)(implicit conn: Connection): Unit = { 
    println(s"saving $operation using $conn") 
} 

Bien sûr, je reçois une erreur de compilation, que la connexion ne peut être trouvé. Quelle partie me manque pour connecter la connexion de la fonction transactional à la fonction save?

Répondre

2

Modifiez votre transactional quelque chose comme ci-dessous (regardez l'extrait de code de transactional). Le problème ici est la connexion est disponible avec transactionnel mais il doit atteindre la fonction de sauvegarde implicitement. Ainsi, une fois que vous obtenez l'objet de connexion, passez à la fonction qui s'exécute dans la transaction, puis ce code (f) peut accéder à la connexion. Une fois que f obtient l'accès à la connexion, nous pouvons le faire implicit en utilisant le mot-clé implicite. Maintenant, des fonctions comme save qui prennent implicitement une connexion peuvent être appelées de façon transparente à l'intérieur de la transaction.

changements importants pour transactionnel

1) Au lieu de faire passer le bloc de code (bloc: pass => Unité) f (f: Connexion => Unité)

2) à l'intérieur des transactions appliquent f pour objet de connexion et donnez un accès f à l'objet de connexion.

def transactional(f: Connection => Unit): Unit = { 
    val conn = getConnectionFromDatabase() 
    conn.begin() 
    try { 
    f(conn) 
    conn.commit() 
    } catch { 
    case ex: Exception => 
     conn.rollback() 
     throw ex 
    } finally { 
    conn.close() 
    } 
} 

Maintenant, vous pouvez l'utiliser comme ceci

transactional { implicit conn => 
    save("something") 
} 

si vous fonction de sauvegarde est comme ce

def save(str: String): Connection => Unit = ??? 

Ensuite, vous pouvez aller sans connexion

transactional(save("foo")) 
+0

Merci, la version modifiée me permet de laisser la connexion hors du code d'appel. Cela améliore également la réponse de Tzach Zohar en ce sens que je peux toujours enrouler du code autour de la fonction de sauvegarde, et cela fonctionnera toujours. J'accepterai cette réponse pour cette raison. Si l'emballage n'est pas nécessaire, sa réponse est légèrement plus simple. –

1

Vous pouvez faites cela sans utiliser implicit, et sans changer le code d'appel, si saveretours une fonction attendant une connexion:

def transactional(block: Connection => Unit): Unit = { 
    val conn: Connection = ??? 

    // stuff.. 
    block(conn) 
    // stuff.. 
} 

def save(operation: String): Connection => Unit = { conn => 
    println(s"saving $operation using $conn") 
} 

transactional { 
    save("something") 
} 
+0

Merci, cela fonctionne. Maintenant, j'ai une autre question. Si je remplace 'save (" something ")' par 'for (i <- 1 à 3) {save (" something ")}', cela ne fonctionne pas. Des idées là-dessus? –

+0

Jeroen, dans le code ci-dessus 'transactional' attend un argument de type' Connection => Unit'. save() retourne exactement cela, la boucle 'for' ne le fait pas. –