S2JDBC で UUID 型を利用する
テーブルの主キーに UUID を使いたい場面は結構あるとおもうのですが、S2JDBC は UUID を利用できるようにはなっていないようです。
ならばってことで、Dialect を拡張して、UUID を使えるようにしてみました。対象の DBMS は H2 です。H2 ならば UUID 型をサポートしているので、そのまま出し入れできます。MySQL 等の場合は、カラムの型を Binary(16) とし、 java.util.UUID のlong値x2をbyte[16] にして格納すればよいかと。
S2JDBC の Dialect を拡張
まずは、S2JDBC の Dialect を拡張。
import java.sql.{CallableStatement, PreparedStatement, ResultSet} import java.util.UUID import javax.persistence.TemporalType import org.seasar.extension.jdbc.{ValueType, PropertyMeta} import org.seasar.extension.jdbc.dialect.H2Dialect import org.seasar.extension.jdbc.types.AbstractValueType import org.seasar.extension.jdbc.util.BindVariableUtil /** * java.util.UUID 型を扱えるようにした H2 の Dialect クラスです。 */ class UUIDH2Dialect extends H2Dialect { import UUIDH2Dialect.{uuidClass, uuidValueType} override def getValueType(propertyMeta: PropertyMeta): ValueType = if (uuidClass.isAssignableFrom(propertyMeta.getPropertyClass)) uuidValueType else super.getValueType(propertyMeta) override def getValueType(clazz: Class[_], lob: Boolean, temporalType: TemporalType): ValueType = if (uuidClass.isAssignableFrom(clazz)) uuidValueType else super.getValueType(clazz, lob, temporalType) } object UUIDH2Dialect { val uuidClass = classOf[UUID] /** * UUID 型の情報を扱う ValueType クラスです。 */ object uuidValueType extends AbstractValueType(java.sql.Types.OTHER) { def getValue(resultSet: ResultSet, index: Int): AnyRef = resultSet.getObject(index).asInstanceOf[UUID] def getValue(resultSet: ResultSet, columnName: String): AnyRef = resultSet.getObject(columnName).asInstanceOf[UUID] def getValue(cs: CallableStatement, index: Int): AnyRef = cs.getObject(index).asInstanceOf[UUID] def getValue(cs: CallableStatement, parameterName: String): AnyRef = cs.getObject(parameterName).asInstanceOf[UUID] def bindValue(ps: PreparedStatement, index: Int, value: AnyRef) { value match { case null => setNull(ps, index) case other => ps.setObject(index, value) } } def bindValue(cs: CallableStatement, parameterName: String, value: AnyRef) { value match { case null => setNull(cs, parameterName) case other => cs.setObject(parameterName, value) } } def toText(value: AnyRef): String = value match { case null => BindVariableUtil.nullText() case other => BindVariableUtil.toText(value) } } }
拡張した Dialect を使用するように、s2jdbc.dicon を修正。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <include path="jdbc.dicon"/> <include path="s2jdbc-internal.dicon"/> <!-- UUIDH2Dialect のコンポーネント定義を追加 --> <component name="uuidH2Dialect" class="UUIDH2Dialect"/> <component name="jdbcManager" class="org.seasar.extension.jdbc.manager.JdbcManagerImpl"> <property name="maxRows">0</property> <property name="fetchSize">0</property> <property name="queryTimeout">0</property> <!-- dialect の指定を変更 --> <property name="dialect">uuidH2Dialect</property> </component> </components>
S2JDBC-Gen の GenDialect を拡張
また、S2JDBC-Gen も使うので、GenDialect を拡張。
import java.util.UUID import java.sql.{PreparedStatement, ResultSet, Types} import org.seasar.extension.jdbc.PropertyMeta import org.seasar.extension.jdbc.gen.dialect.GenDialect.ColumnType import org.seasar.extension.jdbc.gen.internal.dialect.H2GenDialect import org.seasar.extension.jdbc.gen.internal.dialect.StandardGenDialect import org.seasar.extension.jdbc.gen.internal.sqltype.AbstractSqlType import org.seasar.extension.jdbc.gen.provider.ValueTypeProvider import org.seasar.extension.jdbc.gen.sqltype.SqlType class UUIDH2GenDialect extends H2GenDialect { import UUIDH2GenDialect.{uuidClass, uuidColumnType, uuidDataType, uuidSqlType} override def getSqlType(valueTypeProvider: ValueTypeProvider, propertyMeta: PropertyMeta): SqlType = if (uuidClass.isAssignableFrom(propertyMeta.getPropertyClass)) uuidSqlType else super.getSqlType(valueTypeProvider, propertyMeta) override def getColumnType(typeName: String, sqlType: Int): ColumnType = if (typeName == uuidDataType) uuidColumnType else super.getColumnType(typeName, sqlType) } object UUIDH2GenDialect { val uuidClass = classOf[UUID] val uuidDataType = "uuid" object uuidSqlType extends AbstractSqlType(uuidDataType) { def getValue(resultSet: ResultSet, index: Int): String = resultSet.getObject(index) match { case null => null case other => other.toString } def bindValue(ps: PreparedStatement, index: Int, value: String) { if (value == null) ps.setNull(index, Types.OTHER) else ps.setString(index, value) } } object uuidColumnType extends StandardGenDialect.StandardColumnType(uuidDataType, uuidClass) }
S2JDBC-Gen の各タスクで、拡張した Dialect を使用するようにビルドファイルを修正。
<project name="example-s2jdbc-gen" default="gen-ddl" basedir="."> 〜(省略)〜 <property name="gendialectclassname" value="UUIDH2GenDialect"/> <target name="gen-ddl"> <!-- gendialectclassname 属性を指定する --> <gen-ddl classpathdir="${classpathdir}" rootpackagename="${rootpackagename}" entitypackagename="${entitypackagename}" env="${env}" jdbcmanagername="${jdbcmanagername}" classpathref="classpath" gendialectclassname="${gendialectclassname}" /> 〜(省略)〜 </target> 〜(省略)〜 <target name="migrate"> <!-- gendialectclassname 属性を指定する --> <migrate classpathdir="${classpathdir}" rootpackagename="${rootpackagename}" entitypackagename="${entitypackagename}" applyenvtoversion="${applyenvtoversion}" version="${version}" env="${env}" jdbcmanagername="${jdbcmanagername}" classpathref="classpath" gendialectclassname="${gendialectclassname}" /> <refresh projectName="${projectname}"/> </target> 〜(省略)〜 </project>
以上により、エンティティのプロパティ型として java.util.UUID が使えました。ただ、@GeneratedValue までは対応しなかったので、insert の前に、自分で UUID を設定しておく必要があります。
おまけ
ほかの DB 用の Dialect を作ることも想定して、共通部分をトレイトとして分離してみる。
UUID 対応 Dialect のトレイト
import java.util.UUID import javax.persistence.TemporalType import org.seasar.extension.jdbc.{DbmsDialect, PropertyMeta, ValueType} trait UUIDDialect extends DbmsDialect { import UUIDDialect.uuidClass protected def uuidValueType: ValueType abstract override def getValueType(propertyMeta: PropertyMeta): ValueType = if (uuidClass.isAssignableFrom(propertyMeta.getPropertyClass)) uuidValueType else super.getValueType(propertyMeta) abstract override def getValueType(clazz: Class[_], lob: Boolean, temporalType: TemporalType): ValueType = if (uuidClass.isAssignableFrom(clazz)) uuidValueType else super.getValueType(clazz, lob, temporalType) } object UUIDDialect { val uuidClass = classOf[UUID] }
UUID 対応 GenDialect のトレイト
import java.util.UUID import java.sql.{PreparedStatement, ResultSet} import org.seasar.extension.jdbc.{ValueType, PropertyMeta} import org.seasar.extension.jdbc.gen.dialect.GenDialect import org.seasar.extension.jdbc.gen.internal.dialect.StandardGenDialect import org.seasar.extension.jdbc.gen.internal.sqltype.AbstractSqlType import org.seasar.extension.jdbc.gen.provider.ValueTypeProvider import org.seasar.extension.jdbc.gen.sqltype.SqlType trait UUIDGenDialect extends GenDialect { import UUIDDialect.uuidClass import UUIDGenDialect.{UUIDColumnType, UUIDSqlType} protected def uuidDataType: String protected def uuidValueType: ValueType private val uuidSqlType = new UUIDSqlType(uuidDataType, uuidValueType) private val uuidColumnType = new UUIDColumnType(uuidDataType) abstract override def getSqlType(valueTypeProvider: ValueTypeProvider, propertyMeta: PropertyMeta): SqlType = if (uuidClass.isAssignableFrom(propertyMeta.getPropertyClass)) uuidSqlType else super.getSqlType(valueTypeProvider, propertyMeta) abstract override def getColumnType(typeName: String, sqlType: Int): GenDialect.ColumnType = if (typeName == dataType) uuidColumnType else super.getColumnType(typeName, sqlType) } object UUIDGenDialect { import UUIDDialect.uuidClass class UUIDSqlType(dataType: String, valueType: ValueType) extends AbstractSqlType(dataType) { def getValue(resultSet: ResultSet, index: Int): String = valueType.getValue(resultSet, index) match { case null => null case uuid: UUID => uuid.toString } def bindValue(ps: PreparedStatement, index: Int, value: String) { value match { case null => valueType.bindValue(ps, index, null) case other => valueType.bindValue(ps, index, UUID.fromString(value)) } } } class UUIDColumnType(dataType: String) extends StandardGenDialect.StandardColumnType(dataType, uuidClass) }
使用例は以下
class UUIDH2Dialect extends H2Dialect with UUIDDialect { protected val uuidValueType = UUIDH2Dialect.uuidValueType } class UUIDH2GenDialect extends H2GenDialect with UUIDGenDialect { protected val uuidDataType = "uuid" protected val uuidValueType = UUIDH2Dialect.uuidValueType } // UUIDH2Dialect.uuidValueType は上述のものと同様
いじょ。