198 votes

La recherche d'une comparaison de la Scala de la persistance des cadres

Je voudrais utiliser Scala à persister des données dans une base de données relationnelle, donc ce que je suis à la recherche d'exemples d'opérations CRUD utilisation de Scala.

Je voudrais un code sur une baisse du niveau d'abstraction qu'un ORM comme Hibernate/Toplink (lire:JDBC), mais entre nous, j'aimerais voir des exemples de tous les types.

283voto

Daniel C. Sobral Points 159554

EDIT: Il y a maintenant une assez bonne wiki à propos de la Scala bibliothèques et frameworks ici.

Je sais de cinq utilisable non-ORM bibliothèques de base de données pour Scala. Il y a aussi un ORM que je mentionne ci-dessous, car il ne veut pas cacher SQL, ce qui peut faire un bon ajustement.

Slick (previsously nommé Scalaquery)

Le premier est Lisse. Il est le plus mature, et il essaie de faire des recherches à utiliser le même pour les-de la compréhension que Scala collections. Comme un exemple de la syntaxe de style (ce qui pourrait être légèrement en dehors de la date):

import java.lang.Integer
import com.novocode.squery._
import com.novocode.squery.Implicit._
import com.novocode.squery.session._
import com.novocode.squery.session.SessionFactory._

// Define table:
object Users extends Table[(Integer, String, String)]("users") {
  def id = intColumn("id", O.AutoInc, O.NotNull)
  def first = stringColumn("first")
  def last = stringColumn("last")
  def * = id ~ first ~ last
}

// Basic usage
val sf = new DriverManagerSessionFactory("org.h2.Driver", "jdbc:h2:mem:test1")
sf withSession {
  // Prepare a simple query
  val q1 = for(u <- Users) yield u

  // Print SQL statement to be executed:
  println(q1.selectStatement)  // displays SELECT t1.id,t1.first,t1.last FROM users t1

  // Print query result:
  for(t <- q1) println("User tuple: "+t)

  // Query statements can also be used with updates:
  val q = for(u <- Users if u.id is 42) yield u.first ~ u.last
  q.update("foo", "bar")
}

Comme le projet a été renommé récemment, certaines ressources sont encore sous scalaquery nom (site web, groupes). Slick va bientôt être inclus dans Typesafe pile qui peut être vu comme une preuve de sa maturité.

Querulous

Le second est Querulous, qui est un projet open source à partir de Twitter. Celui-ci vous donne un accès direct à SQL, tout en traitant avec un tas de jdbc désagréments. Voici un exemple simple:

import com.twitter.querulous.evaluator.QueryEvaluator
val queryEvaluator = QueryEvaluator("host", "username", "password")
val users = queryEvaluator.select("SELECT * FROM users WHERE id IN (?) OR name = ?", List(1,2,3), "Jacques") { row =>
  new User(row.getInt("id"), row.getString("name"))
}
queryEvaluator.execute("INSERT INTO users VALUES (?, ?)", 1, "Jacques")
queryEvaluator.transaction { transaction =>
  transaction.select("SELECT ... FOR UPDATE", ...)
  transaction.execute("INSERT INTO users VALUES (?, ?)", 1, "Jacques")
  transaction.execute("INSERT INTO users VALUES (?, ?)", 2, "Luc")
}

Squeryl

Le troisième est Squeryl. Côté Style, elle se situe à mi-chemin entre ScalaQuery -- qui cache SQL derrière Scala interprétations, autant que possible, -- et Querulous -- qui utilise des chaînes SQL directement.

Squeryl fournit un type SQL LIS, qui vous donne le type de la sécurité et de vous donner une forte probabilité que les états ne tombera pas en panne au moment de l'exécution si la compilation. Encore une fois, un exemple simple:

// Defining tables and a schema:
import org.squeryl.PrimitiveTypeMode._

class Author(var id: Long, 
             var firstName: String, 
             var lastName: String)

class Book(var id: Long, 
           var title: String,
           @Column("AUTHOR_ID") // the default 'exact match' policy can be overriden
           var authorId: Long,
           var coAuthorId: Option[Long]) {
  def this() = this(0,"",0,Some(0L))
}

object Library extends Schema {
  //When the table name doesn't match the class name, it is specified here :
  val authors = table[Author]("AUTHORS")
  val books = table[Book]
}

// Basic usage
Class.forName("org.postgresql.Driver"); 
val session = Session.create( 
  java.sql.DriverManager.getConnection("jdbc:postgresql://localhost:5432/squeryl", "squeryl", "squeryl"), 
  new PostgreSqlAdapter 
) 

//Squeryl database interaction must be done with a using block :  
import Library._
using(session) { 
  books.insert(new Author(1, "Michel","Folco"))            
  val a = from(authors)(a=> where(a.lastName === "Folco") select(a)) 
}

O/R Courtier

Le quatrième est O/R Courtier, qui, malgré son nom, n'est pas un ORM. Les Classes peuvent être conçus de toute manière souhaitée. Pas d'interfaces/traits à mettre en œuvre, pas de conventions à respecter, pas d'annotations nécessaires.

case class Song(id: Option[Long], title: String, seconds: Short)
case class Album(id: Option[Long], title: String, year: Short, songs: IndexedSeq[Song])
case class Artist(id: Option[Long], name: String, albums: Set[Album])

Les extracteurs sont déclaratives, écrit en Scala. Peuvent être réutilisés dans d'autres requêtes qui correspondent à l'attente de l'extracteur.

object SongExtractor extends JoinExtractor[Song] {
  val key = Set("SONG_ID")

  def extract(row: Row, join: Join) = {
    new Song(
          row.bigInt("SONG_ID"), 
          row.string("TITLE").get, 
          row.smallInt("DURATION_SECONDS").get
        )
  }
}

object AlbumExtractor extends JoinExtractor[Album] {
  val key = Set("ALBUM_ID")

  def extract(row: Row, join: Join) = {
    new Album(
          row.bigInt("ALBUM_ID"),
          row.string("TITLE").get,
          row.smallInt("YEAR_ISSUED").get,
          join.extractSeq(SongExtractor, Map("TITLE"->"SONG_TITLE"))
        )  
  }
}

object ArtistExtractor extends JoinExtractor[Artist] {
  val key = Set("ARTIST_ID")

  def extract(row: Row, join: Join) = {
    new Artist(
          row.bigInt("ARTIST_ID"),
          row.string("NAME"),
          join.extractSeq(AlbumExtractor)
        )
  }
}

On pourrait alors l'utiliser comme ceci:

val ds: javax.sql.DataSource = ...
val builder = new SQLFileBuilder(ds, new java.io.File("sql/"))
val broker = builder.build()

// Print all artists with their albums (if any)
val artists = broker.readOnly() { session =>
  session.selectAll[Artist]('selectArtist) // ' I wish they could fix the Scala Symbol formatting
}
for (ar <- artists) {
  println(a.name)
      if (ar.albums.isEmpty)
        println("\t<No albums>")
      else for (al <- ar.albums) {
        println("\t" + al.title)
        for (s <- al.songs) {
          println("\t\t" + (al.songs.indexOf(s)+1) + ". " + s.title)
        }
      }
}

Anorm

Anorm vient de Jouer de Cadre, et je ne sais pas si il peut être utilisé de façon autonome ou pas. Fondamentalement, il les fossés mappages et DSL complètement, vous donnant un accès direct à SQL. Une simple requête peut ressembler à ceci:

// Create an SQL query
val selectCountries = SQL("Select * from Country")

// Transform the resulting Stream[Row] as a List[(String,String)]
val countries = selectCountries().map(row => 
    row[String]("code") -> row[String]("name")
).toList

Il prend également en charge la correspondance de modèle pour la ligne d'extraction:

val countries = SQL("Select name,population from Country")().collect {
    case Row("France", _) => France()
    case Row(name:String, pop:Int) if(pop > 1000000) => BigCountry(name)
    case Row(name:String, _) => SmallCountry(name)      
}

La liaison des variables dans les requêtes utilise cette syntaxe:

SQL(
    """
        select * from Country c 
        join CountryLanguage l on l.CountryCode = c.Code 
        where c.code = {countryCode};
    """
).on("countryCode" -> "FRA")

Et il possède aussi un support pour l'utilisation d'analyser les combinators de traduire les requêtes ou même des schémas de table dans des structures de données. Vous pouvez soit définir l'analyseur de vous-même, ou l'utilisation de certaines conventions par défaut (comme une classe de cas de mappage de noms de champ pour les noms de colonne) et de le laisser faire le travail pour vous.

Circonflexe ORM

Enfin, il y a un accent Circonflexe ORM. Je suis la copie ici quelques exemples de leur site:

class Category extends Record[Category] {
  val id = field(Category.id)
  val name = field(Category.name)
  val books = oneToMany(Book.category)    // allows navigating between associations transparently
}

object Category extends Table[Category] with LongIdPK[Category] {
  val name = stringColumn("name")         // creates a column
      .notNull                            // creates NOT NULL constraint
      .unique                             // creates UNIQUE constraint
      .validateNotEmpty                   // adds NotEmpty validation
      .validatePattern("^[a-zA-Z]{1,8}$") // adds Pattern validation
}

class Book extends Record[Book] {
  val id = field(Book.id)
  val title = field(Book.title)
  val category = manyToOne(Book.category)
}

object Book extends Table[Book] with LongIdPK[Book] {
  val title = stringColumn("title")
      .notNull
      .validateNotEmpty
  val category = longColumn("category_id")
      .references(Category)     // creates an association with Category
      .onDeleteSetNull          // specifies a foreign-key action
      .onUpdateCascade
}

new DDLExport(Category, Book).create   // creates database schema

// find category by id
val c = Category.get(2l)
// find all books
val allBooks = Book.all
// find books for category
val cBooks = c.get.books
// find books by title
Book.criteria.add("title" like "a%").list

select()
      .from(Category as "c" join (Book as "b"), Category as "c1")
      .where("c1.name" like "a%")
      .addOrder(asc("c.name"))
      .list

select(count("b.id"), "c.name").from(Category as "c" join (Book as "b")).list

Si j'ai manqué aucun projet existant, il suffit de déposer un commentaire et je vais les ajouter à cette réponse. Ne vous embêtez pas avec les blogs, documents, wikis ou autres, si.

jOOQ

Bien que jOOQ est actuellement principalement solution Java, il rend encore SQL tout à fait bien dans Scala grâce à la Scala de capacités d'omettre des points et des parenthèses pour certains appels de méthode. L'exemple suivant a été prise à partir d'une autorité de blog. D'autres exemples ici.

// Fetch book titles and their respective authors into
// a result, and print the result to the console.
val result = (create
  select (
      BOOK.TITLE as "book title",
      AUTHOR.FIRST_NAME as "author's first name",
      AUTHOR.LAST_NAME as "author's last name")
  from AUTHOR
  join BOOK on (AUTHOR.ID equal BOOK.AUTHOR_ID)
  where (AUTHOR.ID in (1, 2, 3))
  orderBy (AUTHOR.LAST_NAME asc) 
  fetch)

et aussi

// Iterate over authors and the number of books they've written
// Print each value to the console
for (r <- (create
           select (AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, count)
           from AUTHOR
           join BOOK on (AUTHOR.ID equal BOOK.AUTHOR_ID)
           where (AUTHOR.ID in (1, 2, 3))
           groupBy (AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
           orderBy (AUTHOR.LAST_NAME asc)
           fetch)) {

  print(r.getValue(AUTHOR.FIRST_NAME))
  print(" ")
  print(r.getValue(AUTHOR.LAST_NAME))
  print(" wrote ")
  print(r.getValue(count))
  println(" books ")
}

18voto

Christopher Points 187

OMI JPA2.0 est toujours l'un des plus souples et des concepts de pointe (en particulier de l'utiliser avec BeanValidation, JTA, JNDI, existants ou complexes d'un schéma relationnel etc.). Il est vrai que JPA (ainsi que la plupart de Java et Java annotation en fonction des spécifications) ne rentre pas bien dans la Scala de concepts (surtout les collections qui doivent être converties). Néanmoins, il peut être utilisé facilement avec quelques wrapper de Classes et d'Objets.

quelques Avantages:

  • Enfichable Implémentations
  • Standard largement utilisé
  • Largement disponibles de l'expérience
  • Pris en charge par les fournisseurs de serveurs d'applications

Trois grandes implémentations JPA 2.0:

  • EclipseLink
  • OpenJPA
  • Hibernate

Exemples à l'aide de quelques simples d'emballage:

Gestionnaire d'entité et l'Entité Gestionnaire de l'Usine

class MyClass extends Something
    with SimpleEntityManagerFactory
    with ThreadLocalEntityManager {

 def getPersistenceUnitName = "mip"
 . . .
}

Élément Objet De L'Entité

Utilise la stratégie de l'héritage de joint et une séquence de clé primaire

@Entity
@Table(name = "obj_item")
@Inheritance(strategy = InheritanceType.JOINED)
@SequenceGenerator(name = "obj_item_id_seq", sequenceName = "obj_item_id_sequence",         allocationSize = 1)
class ObjectItem extends MIPEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "obj_item_id_seq")
  @Column(name = "obj_item_id", nullable = false, length = 20)
  @BeanProperty
  var id: BigInteger = _

  @Column(name = "cat_code", nullable = false, length = 6)
  @BeanProperty
  var objItemCatCode: String = _
}

À L'Aide De L'Id D'Identité De Classe

Plus complexes de l'association à l'aide de l'Id de Classe pour l'Entité de l'Identité des Champs.

@Entity
@Table(name = "org_struct")
@IdClass(classOf[OrganisationStructureId])
@SequenceGenerator(name = "org_struct_index_seq", sequenceName = "org_struct_index_sequence", allocationSize = 1)
class OrganisationStructure extends MIPEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "org_struct_index_seq")
  @Column(name = "org_struct_ix", nullable = false, length = 20)
  @BeanProperty
  protected var ix: BigInteger = _

  @Id
  @ManyToOne(fetch = FetchType.EAGER)
  @JoinColumn(name = "org_struct_root_org_id", nullable = false, updatable = false)
  @BeanProperty
  protected var orgStructRootOrg: Organisation = _

  . . .
}

Id de Classe pour l'Entité de l'Identité des Champs:

class OrganisationStructureId {
  @BeanProperty
  var orgStructRootOrg: BigInteger = _
  @BeanProperty
  var ix: BigInteger = _
. . .
}

Tout cela est fourni par ScalaJPA et JPA-pour-Scala (voir Github). Les deux sont plutôt petites wrapper autour d'habitude JPA classes. Ce dernier donne des idées pour les externaliser les chaînes de requête, de filtrer les objets et l'étendue de transaction des wrappers. F. e.:

Utilisation du Filtre et de l'exécution de la requête:

. . .
val filter: NameFilter = newFilterInstance(QueryId("FindObjectItemFromNameWithFilter"))
filter.name = "%Test%"

var i = 0
forQueryResults {
  oi: ObjectItem =>
    i = i + 1
} withQuery (filter)
i must_== 10
. . .

Supprimer un utilisateur:

withTrxAndCommit {
 findAndApply(id ) {
   u:User => remove(u)
 }
}

Exécuter un natif PostGIS requête SQL et de s'attendre à un résultat:

withTrxAndCommit {
 oneResultQueryAndApply {
  d: Double =>
   eStatRet.setDistance(d)
  } withNativeQuery (QueryId("DistancePointFromTextToLocID"), postGISPoint, user.getUsersLocation.getId)
}

10voto

Kazuhiro Sera Points 759

J'ai créé un nouveau nommé ScalikeJDBC.

C'est un simple JDBC bibliothèque d'encapsulation. Peut-être un "Exécutable SQL template' est la caractéristique unique.

https://github.com/seratch/scalikejdbc

Il a également générateur de code source. Surtout si l'accès existants, héritage de la base de données, c'est bien pratique.

https://github.com/seratch/scalikejdbc-mapper-generator

D'ailleurs, il est facile à intégrer avec Play20.

https://github.com/seratch/scalikejdbc-play-plugin

Veuillez prendre un coup d'oeil.

6voto

Voici une complète Scala + JDBC exemple, cela a fonctionné pour être la solution la plus simple que j'ai trouvé.

import java.sql.{Connection, DriverManager, ResultSet};

// Change to Your Database Config
val conn_str = "jdbc:mysql:/localhost:3306/DBNAME?user=DBUSER&password=DBPWD"

// Load the driver
classOf[com.mysql.jdbc.Driver]

// Setup the connection
val conn = DriverManager.getConnection(conn_str)
try {
    // Configure to be Read Only
    val statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)

    // Execute Query
    val rs = statement.executeQuery("SELECT quote FROM quotes LIMIT 5")

    // Iterate Over ResultSet
    while (rs.next) {
      println(rs.getString("quote"))
    }
}
finally {
    conn.close
}

J'ai écrit mon expérience complète ici: http://mkaz.com/archives/1259/using-scala-with-jdbc-to-connect-to-mysql/

6voto

Je suis heureux de vous annoncer la 1ère sortie d'un nouvel ORM bibliothèque pour Scala. MapperDao cartes de classes du domaine de tables de base de données. Il prend actuellement en charge mysql, postgresql, oracle pilote sera bientôt disponible), un-à-un, plusieurs-à-un, un-à-plusieurs, plusieurs-à-plusieurs relations, générée automatiquement les clés, les transactions et éventuellement s'intègre bien avec le framework spring. Il permet une liberté dans la conception des classes du domaine qui ne sont pas affectés par la persistance de détails, encourage l'immutabilité et de l'est de type sécurisé. La bibliothèque n'est pas basé sur la réflexion, mais plutôt sur la bonne Scala principes de conception et contient un DSL pour les données de la requête, qui ressemble étroitement à des requêtes select. Il ne nécessite pas la mise en œuvre de equals() ou hashCode() sont des méthodes qui peuvent être problématiques pour les entités persistantes. La cartographie est réalisée à l'aide de type coffre-fort Scala code.

Les détails et les instructions d'utilisation peuvent être trouvés à l'mapperdao du site:

http://code.google.com/p/mapperdao/

La bibliothèque est disponible pour téléchargement sur le site ci-dessus et aussi comme une dépendance maven (documentation contient des détails sur la façon de l'utiliser via maven)

Des exemples peuvent être trouvés à:

https://code.google.com/p/mapperdao-examples/

Très brève introduction de la bibliothèque via le code de l'échantillon:

class Product(val name: String, val attributes: Set[Attribute])
class Attribute(val name: String, val value: String)
...

val product = new Product("blue jean", Set(new Attribute("colour", "blue"), new Attribute("size", "medium")))
val inserted = mapperDao.insert(ProductEntity, product)
// the persisted entity has an id property:
println("%d : %s".format(inserted.id,inserted))

L'interrogation est très familier:

val o=OrderEntity

import Query._
val orders = query(select from o where o.totalAmount >= 20.0 and o.totalAmount <= 30.0)
println(orders) // a list of orders

J'encourage tout le monde à utiliser la bibliothèque et donner de la rétroaction. La documentation est actuellement assez vaste, avec le programme d'installation et les instructions d'utilisation. N'hésitez pas à commenter et à entrer en contact avec moi à kostas dot kougios à googlemail dot com.

Merci,

Kostantinos Kougios

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X