Republiée de la liste de diffusion Lift pour la postérité (source here):
je peux jeter un peu de lumière sur la façon dont nous utilisons JPA. Je ne suis pas sûr quel type de conteneur vous travaillez avec, mais nous utilisons JBoss 4.2.2, et en utilisant ses installations de la piscine de connexion.
Nous utilisons la bibliothèque scalajpa pour initialiser la substance JPA et conserver une référence au gestionnaire d'entités dans une variable locale de thread. Nous spécifiquement n'utilisons pas l'ascenseur RequestVarEM, parce que le cycle de vie de un RequestVar est un peu plus compliqué qu'une demande HTTP régulière, et cela peut conduire à des connexions ne pas être retournées à la piscine dans un délai opportun.
La première étape consiste à créer le « modèle » et pointer au nom de l'unité de votre persistence.xml:
object MyDBModel extends LocalEMF("unitName", false) with
ThreadLocalEM
Et nous avons créé un peu de code pour faire des opérations simple. Chacune de nos classes persistantes se mélange dans un qui fournit quelques opérations de base JPA :
trait Persistent {
def persist = DBModel.persist(this)
def merge = DBModel.merge(this)
def remove = DBModel.remove(this)
}
Par exemple,
@Entity
@Table{val name="person"}
class Person extends Persistent {
@Id
var id:String = _
@Column {val name="first_name", val nullable = false, val
updatable=false}
var firstName:String = _
@Column {val name="last_name", val nullable = false, val
updatable=false}
var lastName:String = _
@OneToMany{ ... }
var roles:Set[Role] = new HashSet[Role]()
// etc.
}
Nous utilisons principalement les collections qui sont mappées pour naviguer dans le modèle d'objet, et mettre plus méthodes de base de données complexes sur l'objet compagnon, de sorte que nous n'avons pas de références à MyDBModel dispersés dans le code (comme vous l'avez noté, une pratique indésirable).Par exemple:
object Person {
def findByLastName = MyDBModel.createQuery[Person]
("...").findAll.toList
// etc.
}
Enfin, notre intégration avec ascenseur est sous la forme d'un morceau de code qui enveloppements chaque demande:
S.addAround(new LoanWrapper {
def apply[T](f: => T):T = {
try {
f
}
catch {
case e => MyDBModel.getTransaction.setRollbackOnly
}
finally {
MyDBModel.cleanup
}
}
})
J'ai omis une erreur de manipulation ici pour faire la idée plus claire, mais l'intention est que chaque demande HTTP s'exécute dans une transaction, qui réussit ou échoue dans son intégralité. Depuis MyDBModel est initialisé lors de son premier contact, dans votre code de test, vous pouvez gréer le EM comme vous le souhaitez, et les objets de données sont isolés de cette configuration .
Espérons que cela soit utile.
Sean
Comment tester du code qui dépend tellement des variables globales (comme 'MyDBModel' dans votre exemple). N'est-ce pas compliqué à faire sans impliquer toute la pile? – Theo