最近在用 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("*/*"))
@Produces(Array("*/*"))
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 = "tb_sim_info")
class SimInfo extends Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "sim_id")
var id: java.lang.Long = _
var msisdn: String = _
var imsi: String = _
var iccid: String = _
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "issue_date_time")
var issueDate: Date = _
@Type(`type` = "com.cmdt.common.mno.service.sh.enums.StatusUserType")
@JsonScalaEnumeration(classOf[StatusType])
var status: Status = _
@OneToOne(mappedBy="sim",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(() => connection)
def connection = {
logInfo("Creating connection with c3po connection pool")
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("detail"))
class SimInfo(@Column(name = "sim_id")
val id: Long,
val msisdn: String,
val imsi: String,
val iccid: String,
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Column(name = "issue_date_time")
val issueDate: Date,
@JsonScalaEnumeration(classOf[StatusType])
@OptionType(classOf[Status])
@JsonProperty("sim_status")
var status: Option[Status]
) {
def this() = this(0, "", "", "", 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