当前位置: 首页 > 工具软件 > Squeryl > 使用案例 >

Scala + SpringMVC + Hibernate (Squeryl)

邵修诚
2023-12-01



最近在用 Scala + SpringMVC + CXF + Hibernate(或 Squeryl)做 Restful Web Service。
        Scala也出来十几年了,但是普及度还是很不够,所以Spring对Scala的特性支持,还好,Scala本来就可以和Java混合编程,所以只要些许地方做出改动,也可以跑的很欢畅。。。所以写一篇博客,把踩过的坑都写下来:

1> Jackson格式化

因为Scala  case class / 枚举类 等特殊类型的存在,jackson 默认 几乎不支持scala,可以在pom中加入以下依赖:

 

<dependency>
			<groupId>com.fasterxml.jackson.module</groupId>
			<artifactId>jackson-module-scala_2.11</artifactId>
                        <version>2.8.8</version>
		</dependency>

 
然后 自定义一个JacksonJsonProvider

@Provider
@Consumes(Array(&quot;*/*&quot;))
@Produces(Array(&quot;*/*&quot;))
class ScalaJacksonJsonProvider(mapper: ObjectMapper, annotationsToUse: Array[Annotations])
  extends JacksonJsonProvider(mapper, annotationsToUse) {

  mapper.registerModule(DefaultScalaModule)
  mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
  mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)


  def this() = this(new ObjectMapper with ScalaObjectMapper, JacksonJsonProvider.BASIC_ANNOTATIONS)

  def this(annotationsToUse: Array[Annotations]) = this(new ObjectMapper with ScalaObjectMapper, annotationsToUse)

  def this(mapper: ObjectMapper) = this(mapper, JacksonJsonProvider.BASIC_ANNOTATIONS)
}


以上代码中的DefaultScalaModule 就是 刚才添加的jackson-module-scala_2.11中的

以上代码的主要作用就是 注册 DefaultScalaModule, 这样就可以让jackson支持Scala的类型

枚举类型的JSON转化
Scala中的 枚举类型 和 Java中 完全不一样,格式化出来的内容 和 Java枚举类型格式化得出的 完全不一样,包含了很多 属性。
为了支持对枚举类型的格式化,首先需要定义一个TypeReference

 

 

/**
  * Sim卡状态<br/>
  * 1: 开户
  * 2:销户
  */
object Status extends Enumeration {

  type Status = Value

  val Enable = Value(1, "01")
  val Disable = Value(2, "02")

}

/**
  * JSON 格式化 Scala Enumeration时,@JsonScalaEnumeration 的参数
  */
class StatusType extends TypeReference[Status.type]

 

 


如上所示:StatusType就是 辅助类

然后在定义枚举属性时,加上@JsonScalaEnumeration注解即可

              @JsonScalaEnumeration(classOf[StatusType])
              var status: Status

 
这样,Jackson格式化后的字符串,还是有限制,是Status的 name 属性,也就是 “启用” / “禁用”


2> Hibernate Entity适配

Hibernate几乎不用做任何修改就可以支持Scala,和 使用Java Entity一样,

@Entity
@Table(name = &quot;tb_sim_info&quot;)
class SimInfo extends Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = &quot;sim_id&quot;)
  var id: java.lang.Long = _

  var msisdn: String = _

  var imsi: String = _

  var iccid: String = _

  @Temporal(TemporalType.TIMESTAMP)
  @Column(name = &quot;issue_date_time&quot;)
  var issueDate: Date = _

  @Type(`type` = &quot;com.cmdt.common.mno.service.sh.enums.StatusUserType&quot;)
  @JsonScalaEnumeration(classOf[StatusType])
  var status: Status = _

  @OneToOne(mappedBy=&quot;sim&quot;,fetch=FetchType.EAGER)
  var detail: SimDetailInfo = _

}

object SimInfo {
  def apply(msisdn: String, imsi: String, iccid: String, issueDate: Date = null): SimInfo = {
    val sim = new SimInfo()
    sim.msisdn = msisdn
    sim.imsi = imsi
    sim.iccid = iccid
    sim.issueDate = issueDate

    sim.status = Status.Enable

    sim
  }
}

 
如图所示,只有在涉及到枚举类 或 Option时,会有问题。

Option是因为JVM编译时会进行类型擦除,所以我在Entity里面也没有使用。
枚举类型, 用到了Hibernate的高级特性UserType,相当于 自定义控制枚举类型的Hibernate序列化及反序列化

/**
  * Hibernate PO 和 数据库表映射时的
  * Scala Enumeration 序列化/反序列化 UserType
  */
class StatusUserType extends EnumerationAbstractUserType(Status)


import org.hibernate.usertype.UserType
import java.io.Serializable
import java.sql.{Types, ResultSet, PreparedStatement}
import org.hibernate.engine.spi.SessionImplementor

/** Base class for persisting Enumerations as varchar, or enum in mysql */
private[enums]
abstract class EnumerationAbstractUserType(val et: Enumeration) extends UserType {

  override def sqlTypes() =  Array(Types.VARCHAR)

  override def returnedClass = classOf[et.Value]

  override def equals(x: Object, y: Object): Boolean =  x == y

  override def hashCode(x: Object) = x.hashCode()

  override def nullSafeGet(resultSet: ResultSet, names: Array[String], session: SessionImplementor, owner: Object): Object = {
    val value = resultSet.getString(names(0))
    if (resultSet.wasNull()) return null
    else {
      return et.withName(value)
    }
  }
  override def nullSafeSet(statement: PreparedStatement, value: Object, index: Int, session: SessionImplementor): Unit = {
    if (value == null) {
      statement.setNull(index, Types.VARCHAR)
    } else {
      val en = value.toString
      statement.setString(index, en)
    }
  }

  override def deepCopy(value: Object): Object = value
  override def isMutable() = false
  override def disassemble(value: Object) = value.asInstanceOf[Serializable]
  override def assemble(cached: Serializable, owner: Object): Object = cached.asInstanceOf[Object]
  override def replace(original: Object, target: Object, owner: Object) = original
}

/** Base class for persisting Enumerations as integers */
abstract class EnumerationAbstractIntUserType(val et: Enumeration) extends UserType {

  override def sqlTypes() =  Array(Types.INTEGER)

  override def returnedClass = classOf[et.Value]

  override def equals(x: Object, y: Object): Boolean =  x == y

  override def hashCode(x: Object) = x.hashCode()

  override def nullSafeGet(resultSet: ResultSet, names: Array[String], session: SessionImplementor, owner: Object): Object = {
    val value = resultSet.getInt(names(0))
    if (resultSet.wasNull()) return null
    else {
      return et(value)
    }
  }
  override def nullSafeSet(statement: PreparedStatement, value: Object, index: Int, session: SessionImplementor): Unit = {
    if (value == null) {
      statement.setNull(index, Types.INTEGER)
    } else {
      val en = value.asInstanceOf[et.Value]
      statement.setInt(index, en.id)
    }
  }

  override def deepCopy(value: Object): Object = value
  override def isMutable() = false
  override def disassemble(value: Object) = value.asInstanceOf[Serializable]
  override def assemble(cached: Serializable, owner: Object): Object = cached.asInstanceOf[Object]
  override def replace(original: Object, target: Object, owner: Object) = original
}


Option属性,也可以写一个类似的 UserType,太麻烦了,我就没有使用。。。

经过以上适配后,Hibernate也可以正常支持Scala了,其他使用 和 Java 没有什么不同。

 

 

 

3> Squeryl ORM

 

相比Hibernate,我觉得 Squeryl ORM 更适合Scala,毕竟这是原生的Scala ORM框架,使用起来 更简洁,而且基本上是以Scala的方式进行数据库映射,有兴趣的可以去百度,我就不介绍具体用法了。
Squeryl比较坑的一个地方是,需要自己维护Session,为了提高数据库的并发性,所以可以考虑用数据库连接池来维护Session,具体如下所示:

@Service
class MnoDB extends Logging{

  @Resource
  val cpds:ComboPooledDataSource = null

  @PostConstruct
  def configureDb() {

    SessionFactory.concreteFactory = Some(() =&gt; connection)

    def connection = {
      logInfo(&quot;Creating connection with c3po connection pool&quot;)
      Session.create(cpds.getConnection, new MySQLInnoDBAdapter)
    }
  }

  @PreDestroy
  def close(): Unit = {
    cpds.close()
  }
}

 
     Session管理解决后,还有一个 比较坑的地方是,Squeryl 默认把 java.lang.Date序列为2017-06-27 00:00:00这种格式,直接把Time擦除了,如果想格式化为 日期时间,则必须使用java.sql.Timestamp。

     要解决这个比较坑,
        >要么下源码,修改 org.squeryl.internals.FieldMapper,将initialize()方法中的 register(dateTEF) 注释掉;
        >也可以在工程中自定义一个  org.squeryl.internals.FieldMapper,同样的将register(dateTEF) 注释掉,但是自定义的FieldMapper的package必须为:org.squeryl.internals

然后 还需要自定义一个 Date 序列化/反序列化的类型定义

 

  implicit val jodaTimeTEF = new NonPrimitiveJdbcMapper[Timestamp, Date, TTimestamp](timestampTEF, this) {

    /**
      * Here we implement functions fo convert to and from the native JDBC type
      */

    def convertFromJdbc(t: Timestamp) = new Date(t.getTime)
    def convertToJdbc(t: Date) = new Timestamp(t.getTime())
  }

  /**
    * We define this one here to allow working with Option of our new type, this allso
    * allows the 'nvl' function to work
    */
  implicit val optionJodaTimeTEF =
    new TypedExpressionFactory[Option[Date], TOptionTimestamp]
      with DeOptionizer[Timestamp, Date, TTimestamp, Option[Date], TOptionTimestamp] {

      val deOptionizer = jodaTimeTEF
    }

  /**
    * the following are necessary for the AST lifting
    */
  implicit def jodaTimeToTE(s: Date) = jodaTimeTEF.create(s)

  implicit def optionJodaTimeToTE(s: Option[Date]) = optionJodaTimeTEF.create(s)

 

Squeryl感觉用起来很爽,但是 还是有很多坑,以下两个是我在Entity定义时碰到的
1>如果Entity中有 枚举类型属性,则 必须定义def this() 构造方法,并且在构造方法中,需要对枚举类型给出一个默认值进行实例化,

@JsonIgnoreProperties(Array(&quot;detail&quot;))
class SimInfo(@Column(name = &quot;sim_id&quot;)
              val id: Long,
              val msisdn: String,
              val imsi: String,
              val iccid: String,
              @JsonFormat(pattern = &quot;yyyy-MM-dd HH:mm:ss&quot;, timezone = &quot;GMT+8&quot;)
              @Column(name = &quot;issue_date_time&quot;)
              val issueDate: Date,
              @JsonScalaEnumeration(classOf[StatusType])
              @OptionType(classOf[Status])
              @JsonProperty(&quot;sim_status&quot;)
              var status: Option[Status]
             ) {

  def this() = this(0, &quot;&quot;, &quot;&quot;, &quot;&quot;, null, Some(Status.Enable))

  lazy val detail: OneToMany[SimDetailInfo] = MnoDB.simToDetails.left(this)
}

 
2>如果Entit 包含 Option属性,则必须在该属性上加@OptionType注释,告诉SquerylOption的实际类型,如上述代码中的 status属性


总体而言,Scala编写代码,可以使用函数编程的特性,写的代码更简洁易懂,而且基本上 可以完美继承Java的第三方插件,以后尽量用Scala来写后台服务,就是太小众了,碰到一点问题,必须Google,欢迎使用Scala的朋友 来交流
我的QQ: 174492779



 

 

 类似资料: