scala:类似地图的结构,在获取值时不需要转换?

发布于 2024-11-16 21:16:41 字数 1155 浏览 1 评论 0原文

我正在编写一个转换数据库查询结果的数据结构。原始结构是一个 java ResultSet,它将被转换为一个映射或类,允许通过命名方法调用或将字符串传递到 apply() 来访问该数据结构上的不同字段。显然不同的值可能有不同的类型。为了减轻该数据结构的客户端的负担,我的偏好是不需要转换数据结构的值,但获取的值仍然具有正确的类型。

例如,假设我正在执行一个查询,该查询获取两个列值,一个是 Int,另一个是 String。结果列的名称分别为“a”和“b”。一些理想的语法可能如下:

val javaResultSet = dbQuery("select a, b from table limit 1")

// with ResultSet, particular values can be accessed like this:
val a = javaResultSet.getInt("a")
val b = javaResultSet.getString("b")
// but this syntax is undesirable. 

// since I want to convert this to a single data structure, 
// the preferred syntax might look something like this:
val newStructure = toDataStructure[Int, String](javaResultSet)("a", "b")

// that is, I'm willing to state the types during the instantiation
// of such a data structure.

// then,
val a: Int = newStructure("a") // OR
val a: Int = newStructure.a

// in both cases, "val a" does not require asInstanceOf[Int].

我一直在尝试确定哪种数据结构可能允许这样做,但我无法找到绕过转换的方法。

另一个要求显然是我想定义用于所有数据库查询的单个数据结构。我意识到我可以轻松地为每次调用定义一个案例类或类似的案例类,从而解决打字问题,但是当编写许多数据库查询时,这样的解决方案不能很好地扩展。我怀疑有些人会建议使用某种 ORM,但让我们假设我的情况最好以字符串的形式维护查询。

有人有什么建议吗?谢谢!

I'm writing a data structure that converts the results of a database query. The raw structure is a java ResultSet and it would be converted to a map or class which permits accessing different fields on that data structure by either a named method call or passing a string into apply(). Clearly different values may have different types. In order to reduce burden on the clients of this data structure, my preference is that one not need to cast the values of the data structure but the value fetched still has the correct type.

For example, suppose I'm doing a query that fetches two column values, one an Int, the other a String. The result then names of the columns are "a" and "b" respectively. Some ideal syntax might be the following:

val javaResultSet = dbQuery("select a, b from table limit 1")

// with ResultSet, particular values can be accessed like this:
val a = javaResultSet.getInt("a")
val b = javaResultSet.getString("b")
// but this syntax is undesirable. 

// since I want to convert this to a single data structure, 
// the preferred syntax might look something like this:
val newStructure = toDataStructure[Int, String](javaResultSet)("a", "b")

// that is, I'm willing to state the types during the instantiation
// of such a data structure.

// then,
val a: Int = newStructure("a") // OR
val a: Int = newStructure.a

// in both cases, "val a" does not require asInstanceOf[Int].

I've been trying to determine what sort of data structure might allow this and I could not figure out a way around the casting.

The other requirement is obviously that I would like to define a single data structure used for all db queries. I realize I could easily define a case class or similar per call and that solves the typing issue, but such a solution does not scale well when many db queries are being written. I suspect some people are going to propose using some sort of ORM, but let us assume for my case that it is preferred to maintain the query in the form of a string.

Anyone have any suggestions? Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

烟沫凡尘 2024-11-23 21:16:41

要在不进行强制转换的情况下完成此操作,需要有关查询的更多信息,并且需要在编译时提供该信息。

我怀疑有些人会建议使用某种 ORM,但让我们假设我的情况最好以字符串的形式维护查询。

你的怀疑是正确的,你无法回避这一点。如果当前的 ORM 或 DSL(例如 squeryl)不符合您的喜好,您可以创建自己的。但我怀疑您是否能够使用查询字符串。

To do this without casting, one needs more information about the query and one needs that information at compiole time.

I suspect some people are going to propose using some sort of ORM, but let us assume for my case that it is preferred to maintain the query in the form of a string.

Your suspicion is right and you will not get around this. If current ORMs or DSLs like squeryl don't suit your fancy, you can create your own one. But I doubt you will be able to use query strings.

悟红尘 2024-11-23 21:16:41

基本问题是您不知道任何给定查询中会有多少列,因此您不知道数据结构应具有多少类型参数,并且不可能抽象出类型参数的数量。

然而,有一种数据结构存在于不同数量的类型参数的不同变体中:元组。 (例如 Tuple2、Tuple3 等)您可以为不同数量的参数定义参数化映射函数,这些参数返回元组,如下所示:

def toDataStructure2[T1, T2](rs: ResultSet)(c1: String, c2: String) =
  (rs.getObject(c1).asInstanceOf[T1],
  rs.getObject(c2).asInstanceOf[T2])


def toDataStructure3[T1, T2, T3](rs: ResultSet)(c1: String, c2: String, c3: String) =
  (rs.getObject(c1).asInstanceOf[T1],
  rs.getObject(c2).asInstanceOf[T2],
  rs.getObject(c3).asInstanceOf[T3])

您必须为表中期望的尽可能多的列(最多 22)定义这些函数。

这当然取决于使用 getObject 并将其转换为给定类型是否安全。

在您的示例中,您可以按如下方式使用生成的元组:

val (a, b) = toDataStructure2[Int, String](javaResultSet)("a", "b")

The basic problem is that you don't know how many columns there will be in any given query, and so you don't know how many type parameters the data structure should have and it's not possible to abstract over the number of type parameters.

There is however, a data structure that exists in different variants for different numbers of type parameters: the tuple. (E.g. Tuple2, Tuple3 etc.) You could define parameterized mapping functions for different numbers of parameters that returns tuples like this:

def toDataStructure2[T1, T2](rs: ResultSet)(c1: String, c2: String) =
  (rs.getObject(c1).asInstanceOf[T1],
  rs.getObject(c2).asInstanceOf[T2])


def toDataStructure3[T1, T2, T3](rs: ResultSet)(c1: String, c2: String, c3: String) =
  (rs.getObject(c1).asInstanceOf[T1],
  rs.getObject(c2).asInstanceOf[T2],
  rs.getObject(c3).asInstanceOf[T3])

You would have to define these for as many columns you expect to have in your tables (max 22).

This depends of course on that using getObject and casting it to a given type is safe.

In your example you could use the resulting tuple as follows:

val (a, b) = toDataStructure2[Int, String](javaResultSet)("a", "b")
孤独陪着我 2024-11-23 21:16:41

如果您决定走异构集合的路线,那么异构类型列表上有一些非常有趣的帖子:

例如,一个是

http://jnordenberg.blogspot.com/2008/08/hlist-in-scala.html

http://jnordenberg.blogspot.com/2008/09 /hlist-in-scala-revisited-or-scala.html

的实现位于
http://www.assembla.com/wiki/show/metascala

第二个精彩系列帖子以

http://apocalisp.wordpress.com/2010/07/06/type-level-programming-in-scala-part-6a-heterogeneous-list%C2%A0basics/

该系列继续从 a 部分链接的“b,c,d”部分

最后,Daniel Spiewak 的演讲涉及 HOMaps

http://vimeo.com/13518456

所以这一切都表明,也许您可​​以根据这些想法构建解决方案。抱歉,我没有具体的例子,但我承认我自己还没有尝试过这些!

if you decide to go the route of heterogeneous collections, there are some very interesting posts on heterogeneous typed lists:

one for instance is

http://jnordenberg.blogspot.com/2008/08/hlist-in-scala.html

http://jnordenberg.blogspot.com/2008/09/hlist-in-scala-revisited-or-scala.html

with an implementation at
http://www.assembla.com/wiki/show/metascala

a second great series of posts starts with

http://apocalisp.wordpress.com/2010/07/06/type-level-programming-in-scala-part-6a-heterogeneous-list%C2%A0basics/

the series continues with parts "b,c,d" linked from part a

finally, there is a talk by Daniel Spiewak which touches on HOMaps

http://vimeo.com/13518456

so all this to say that perhaps you can build you solution from these ideas. sorry that i don't have a specific example, but i admit i haven't tried these out yet myself!

走过海棠暮 2024-11-23 21:16:41

Joschua Bloch 引入了一种异构集合,可以用 Java 编写。我曾经稍微采用过它。它现在用作值寄存器。它基本上是两个地图的包装。这是代码,这是如何使用它。但这仅供参考,因为您对 Scala 解决方案感兴趣。

在 Scala 中,我会从使用元组开始。元组是一种异构集合。结果可以但不一定通过 _1、_2、_3 等字段访问。但你不想要那个,你想要名字。这就是为它们分配名称的方法:

scala> val tuple = (1, "word")
tuple: ([Int], [String]) = (1, word)

scala> val (a, b) = tuple
a: Int = 1
b: String = word

因此,正如前面提到的,我将尝试围绕元组构建一个 ResultSetWrapper

Joschua Bloch has introduced a heterogeneous collection, which can be written in Java. I once adopted it a little. It now works as a value register. It is basically a wrapper around two maps. Here is the code and this is how you can use it. But this is just FYI, since you are interested in a Scala solution.

In Scala I would start by playing with Tuples. Tuples are kinda heterogeneous collections. The results can be, but not have to be accessed through fields like _1, _2, _3 and so on. But you don't want that, you want names. This is how you can assign names to those:

scala> val tuple = (1, "word")
tuple: ([Int], [String]) = (1, word)

scala> val (a, b) = tuple
a: Int = 1
b: String = word

So as mentioned before I would try to build a ResultSetWrapper around tuples.

往事风中埋 2024-11-23 21:16:41

如果您想在普通 bean 实例上“按名称提取列值”,您可能可以:

  1. 使用您(和我)不喜欢的反射和 CAST。
  2. 使用大多数ORM库提供的ResultSetToJavaBeanMapper,它有点重且耦合。
  3. 写一个scala编译插件,太复杂控制不了。

因此,我想具有以下功能的轻量级 ORM 可能会满足您的需求:

  1. 支持原始 SQL
  2. 支持轻量级、声明式和自适应 ResultSetToJavaBeanMapper
  3. 没有别的。

我就这个想法做了一个实验项目,但请注意它仍然是一个 ORM,我只是认为它可能对你有用,或者可以给你带来一些提示。

用法:

声明模型:

//declare DB schema
trait UserDef extends TableDef {
  var name = property[String]("name", title = Some("姓名"))
  var age1 = property[Int]("age", primary = true)
}

//declare model, and it mixes in properties as {var name = ""}
@BeanInfo class User extends Model with UserDef

//declare a object.
//it mixes in properties as {var name = Property[String]("name") }
//and, object User is a Mapper[User], thus, it can translate ResultSet to a User instance.
object `package`{
  @BeanInfo implicit object User extends Table[User]("users") with UserDef
}

然后调用原始 sql,隐式 Mapper[User] 为您工作:

val users = SQL("select name, age from users").all[User] 
users.foreach{user => println(user.name)}

甚至构建类型安全的查询:

val users = User.q.where(User.age > 20).where(User.name like "%liu%").all[User]

有关更多信息,请参阅单元测试:

https://github.com/liusong1111/soupy-orm/blob/master/src/test/scala/mapper/SoupyMapperSpec.scala

项目主页:

https://github.com/liusong1111/soupy-orm

它使用“抽象类型”和“隐式”的大量使用使奇迹发生,您可以查看 TableDef、Table、Model 的源代码以了解详细信息。

If you want "extract the column value by name" on a plain bean instance, you can probably:

  1. use reflects and CASTs, which you(and me) don't like.
  2. use a ResultSetToJavaBeanMapper provided by most ORM libraries, which is a little heavy and coupled.
  3. write a scala compiler plugin, which is too complex to control.

so, I guess a lightweight ORM with following features may satisfy you:

  1. support raw SQL
  2. support a lightweight,declarative and adaptive ResultSetToJavaBeanMapper
  3. nothing else.

I made an experimental project on that idea, but note it's still an ORM, and I just think it may be useful to you, or can bring you some hint.

Usage:

declare the model:

//declare DB schema
trait UserDef extends TableDef {
  var name = property[String]("name", title = Some("姓名"))
  var age1 = property[Int]("age", primary = true)
}

//declare model, and it mixes in properties as {var name = ""}
@BeanInfo class User extends Model with UserDef

//declare a object.
//it mixes in properties as {var name = Property[String]("name") }
//and, object User is a Mapper[User], thus, it can translate ResultSet to a User instance.
object `package`{
  @BeanInfo implicit object User extends Table[User]("users") with UserDef
}

then call raw sql, the implicit Mapper[User] works for you:

val users = SQL("select name, age from users").all[User] 
users.foreach{user => println(user.name)}

or even build a type safe query:

val users = User.q.where(User.age > 20).where(User.name like "%liu%").all[User]

for more, see unit test:

https://github.com/liusong1111/soupy-orm/blob/master/src/test/scala/mapper/SoupyMapperSpec.scala

project home:

https://github.com/liusong1111/soupy-orm

It uses "abstract Type" and "implicit" heavily to make the magic happen, and you can check source code of TableDef, Table, Model for detail.

兰花执着 2024-11-23 21:16:41

几百万年前,我写了一个示例,展示如何使用 Scala 的类型系统从 ResultSet 中推送和提取值。一探究竟;它与你想要做的事情非常匹配。

implicit val conn = connect("jdbc:h2:f2", "sa", "");
implicit val s: Statement = conn << setup;
val insertPerson = conn prepareStatement "insert into person(type, name) values(?, ?)";
for (val name <- names) 
            insertPerson<<rnd.nextInt(10)<<name<<!;
for (val person <- query("select * from person", rs => Person(rs,rs,rs)))
            println(person.toXML);
for (val person <- "select * from person" <<! (rs => Person(rs,rs,rs)))
            println(person.toXML);

基元类型用于指导 Scala 编译器在 ResultSet 上选择正确的函数。

Several million years ago I wrote an example showing how to use Scala's type system to push and pull values from a ResultSet. Check it out; it matches up with what you want to do fairly closely.

implicit val conn = connect("jdbc:h2:f2", "sa", "");
implicit val s: Statement = conn << setup;
val insertPerson = conn prepareStatement "insert into person(type, name) values(?, ?)";
for (val name <- names) 
            insertPerson<<rnd.nextInt(10)<<name<<!;
for (val person <- query("select * from person", rs => Person(rs,rs,rs)))
            println(person.toXML);
for (val person <- "select * from person" <<! (rs => Person(rs,rs,rs)))
            println(person.toXML);

Primitives types are used to guide the Scala compiler into selecting the right functions on the ResultSet.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文