CentOS5.5のセットアップ備忘録
インストール
インストール後の設定
- 初回起動時にネットワークやセキュリティ、デーモン起動有無の設定画面が表示される
- ネットワーク
- system-config-network
- 固定IPを設定
- ipv6 無効
- これだけじゃ足りない、 /etc/modprobe.conf の修正が必要
- セキュリティ
- キーボード
- インストール時に日本語にしてしまったので、英字配列にもどす
- /etc/sysconfig/keyboard を修正
- KEYTABLE="jp106" を KEYTABLE="us" に変更する
- 不要なサービスを停止する
- chkconfig xxxx off しまくる
- http://www.obenri.com/_minset_cent5/daemon_cent5.html この辺りを参考にしながら
- chkconfig --list | grep "3:on" の結果(postfix とかインストールしたあと)
- anacron 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- atd 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- crond 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- iptables 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- kudzu 0:off 1:off 2:off 3:on 4:on 5:on 6:off
- network 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- ntpd 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- postfix 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- sshd 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- syslog 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- vmware-tools 0:off 1:off 2:on 3:on 4:off 5:on 6:off
- yum-cron 0:off 1:off 2:on 3:on 4:on 5:on 6:off
- yum
- fastmirror を追加
- yum 更新デーモン(yum-updatesd)を停止/削除
- yum 更新cronを追加
- ↑http://centossrv.com/centos5-init.shtml を参考に
- レポジトリを追加
- CentOS Plus
- EPEL
- wget http://ftp.iij.ad.jp/pub/linux/fedora/epel/5/x86_64/epel-release-5-4.noarch.rpm
- rpm ivh epel-release-5-4.noarch.rpm
- http://linux4pro.seesaa.net/article/43879296.html の辺りを参考に
- postfix
- tripwire
- ファイルの改竄を検出できる
- EPEL から yum install tripwire でインストール
- 設定はなんだか大変なので、以下を参考にしながら
- /etc/tripwire/twcfg.txt
- MAILNOVIOLATIONS = false #問題ない場合にメール送らない
- /etc/tripwire/twpol.txt.new
- メール送信されるように、80行目あたりからのセクションをまるごと囲むセクションを作成
- mailto = root #メール送信先は root
- root へのメールは普段つかうメールアドレスに転送設定しておく
- /etc/cron.daily/tripwire-check
- LANG=C
- 念のため。LANG=ja_JP.UTF-8 だとリポートに日本語(日時周り)が含まれてしまい、データベースの更新に失敗するので。
- tripwire --check -M
- メール送信の -M を追加
- LANG=C
- screen コマンドの作るファイルとかも検出されてしまう、ちょっと邪魔くさい
- ポリシーの修正が必要、しばらく運用して検出しなくて良いものを選別する
- レポート内容でDB更新
- tripwire --update
- checkrootkit
- Clam AntiVirus
- EPEL から yum install clamav でインストール
- http://yokensaka.com/fedora/index.php?itemid=29 のとおりにしてみた
無線LANの設定変更
メモも兼ねて。
うちでは電波が届かないため、無線LANのアクセスポイントを一階と二階それぞれにおいています。ちなみに光回線を引いているのが二階なので、二階から一階まで外壁づたいにLANケーブルをたらしています。
いままではAPモードに設定した無線LANルータ(MZK-W04G)を一階においていたのですが、無線LANの子機して接続するノートPCやiPod touchにDHCP*1でのIPがうまく設定できていませんでした。ノートPCを有線LANでMZK-W04GのLANポートに接続すると、ちゃんとDHCPでIPが設定されていたのでDHCP自体は正しく機能しているようです。推測するに無線による接続先に対してDHCPのパケットを送れていないのではないかと。
そこで無線LANルータ自体が持っているDHCPサーバ*2でのIP付与はうまくできているので、機器を一階で使用する場合は、一階の無線LANルータからIPを振ってもらうことにしました。
AP モードのままでは同じネットワーク内に二つ DHCP サーバが存在することになってしまうので、ルータモードにして使用しています。
あたりまえといえば当たり前なのですが、ブロードバンドルータを PPPoE とかいっさい使わずに、普通のルータとして使用できるんですね・・・
おまけ
ついでに、良く使う範囲での Scala と C# のリスト操作対応表。
Scala | C# |
---|---|
map | Select |
flatMap | SelectMany |
filter | Where |
foldLeft | Aggregate *1 |
foldRight | なし |
reduceLeft | Aggregate *2 |
reduceRight | なし |
foreach | なし |
zip | Zip *3 |
すぐに思いつくのは、この辺りぐらい。Skip とか Take、TakeWhile あたりもあったはず。あとは Single とか First とかかな。
collect が無いのはわりと不便に感じることが多い。C# には PartialFunction が無いのでしょうがないのだろうけど。あと、partition も無い気がする。これは作ってもいいんじゃないかなぁ?と思う。遅延リストにするのは難しいのかもだけど。
そうそう、遅延リスト。これについては C# の方が数段便利だと感じてる。遅延リスト・・・というか、IEnumerable を実装するのがものすごい簡単。基本はメソッドから値を返すときに yield return すれば、あとはよしなにやってくれるので、実装の手間がものすごい少ないです。しかも、遅延リストになる。
たとえば、Select だけれども、自分で実装するなら以下のようなコードでよい。
public static class EnumerableExtensions { public static IEnumerable<U> Select<T, U>(this IEnumerable<T> src, Func<T, U> func) { foreach (var value in src) yield return func(value); } }
これだけで、yield return の各戻り値を要素として取り出せる IEnumerable のインスタンスを返すことが出来る。
ちなみに、yield return は繰り返し構文の中でなくとも使える。例えば、以下だと 1, 2, 3 を要素とする IEnumerable が得られる。
public IEnumerable<int> GetValues() { yield return 1; yield return 2; yield return 3; }
戻り値は IEnumerable でなくても IEnumerator でもOKだったような気もする。ちなみにどちらであっても、次の要素が必要となったときに、メソッドの実行が続行されるようです。なので、無限リストを作成するのも簡単にできると思います。
public IEnumerable<int> Start(int start) { for(var i=start;; i++) yield return i; }
Scala の場合、Stream をつかって、ほげほげしないといけないので、ちょっと面倒です。
追記
id:bleis-tift さんの指摘をうけて、対応表の Zip 他に注釈を追加しました。
Option的なものを作ろうとして絶望した
ものすごい久々にブログを書くのですが、あまり役に立たない情報です(汗
ここ半年ほど C# を使用するプロジェクトに携わっています。C# は 4.0 から?だと思うのですが、ジェネリクスの型パラメータに変位指定(in とか out とか)ができるようになっています。
共変が
また、いつのころからか導入されていた LINQ もあるのでリスト操作を非常にやりやすくなっています。しかも基本的に遅延リスト(というか、IEnumerable といイテレータを取得可能なインタフェース)なのも、慣れると嬉しいです。
しかしながら、LINQ を使っていればいるほど、SelectMany(Scala でいうところの flatMap) を使っていればいるほど、Scala でいうところの Option が無いのが耐え難いです。Option が無いために、以下のようなコードを頻繁に書いています。
entities.Select(x => x.Foo).Where(x => x != null).Select(x => x.Bar);
ちがうっ!ちがうんだ!!null 値かどうかに着目したいんじゃないです!!
Foo プロパティの先の Bar プロパティの値を取得したいだけなのですが、プロパティの設定有無をチェックするために null かどうかを調べる必要があります。
もし、Option 型があれば Foo プロパティの型自体を Option[Foo] にすることや、あるいは null の場合は None を返すような ToOption 拡張メソッドを用意することで、よりシンプルに記述できます。以下のような感じです。
entities.SelectMany(x => x.Foo.ToOption().Select(y => y.Bar));
そして、つい先日ついに我慢できなくなり、Scala の Option 的なものを作ろうとしたのですが・・・
public interface IOption<out T> : IEnumerable<T> { } public struct Some<T> : IOption<T> { // 省略 } public struct None<T> : IOption<T> { // 省略 }
↑ここまでは問題ありませんでした。
ところで、C# の LINQ には、SingleOrDefault というメソッドがあります。このメソッドは、要素が 1 件あればそれを、0 件ならデフォルト値を返すという振る舞いをします。しかし、デフォルト値は default(T) ときまっており、利用時に指定できません。この振る舞いが以外不便で、利用時に指定したかったので、IOption に GetOrElse を実装しようとしたのですが・・・
public interface IOption<out T> : IEnumerable<T> { T GetOrElse(T value); // コンパイルエラー }
T は
Scala だったら以下みたいな感じです。
def getOrElse[U>:T](value: U): U
・・・・・・・・C# では下限境界の指定が出来ない模様です orz
C# も 4.0 になって out とか in とか書けるのだから [U>:T] みたいな下限境界も指定できるだろうとおもっていたのですが、見つけられませんでした。C# で型パラメータに対する制約は where で書くのですが、書けるのは以下の 4 種類くらいなもので下限境界を指定する方法がありませんでした。
where T : <実際の型> where T : U where T : struct // 値型 where T : class // 参照型
というわけで、Option 的なものはいまだ作らず、where(x => x != null) と書いています orz
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 は上述のものと同様
いじょ。
Scala から S2JDBC を利用するときに気をつけること
Scala から S2JDBC を利用するときに気付いたこととかを、いくつか書いておきます。
タイプセーフ API
S2JDBC では Operations クラスや S2JDBC-Gen により生成した Names クラスを、以下のように利用することで型や名前の間違いをコンパイル時に気付けるようになっています。
// インポート import static org.seasar.extension.jdbc.operation.Operations.*; import static sample.entity.FooNames.*;
// eq は Operations, name() は FooNames のメソッド public List<Foo> findByName(String name) { return jdbcManager.from(Foo.class) .where(eq(name(), name)) .getResultList(); }
Scala の場合でも Java と同じように Operations クラスと Names クラスをスタティックインポートして使用すると、残念なことにコンパイルに失敗します。
以下はコンパイルに失敗する例です。
// インポート import scala.collection.jcl.Conversions._ // java.util.List<Foo> → Seq[Foo] import org.seasar.extension.jdbc.operation.Operations._ import sample.entity.FooNames._
// eq は Operations, name() は FooNames のメソッド def findByName(name: String): Seq[Foo] = jdbcManager.from(classOf[Foo]) .where(eq(name(), name)) .getResultList;
Scala の場合は AnyRef クラスで eq メソッドが定義されているため、eq(...) を呼び出すと this の eq が呼び出されてしまいます。また、メソッドと変数は同じ名前空間を使用するため、name() は FooNames.name() ではなくメソッド引数の name だと解釈されてしまいます。
このため、Scala からタイプセーフAPIを利用する場合は、インポート時に別名をつけるかスタティックインポートを使用しない等の方法をとる必要があります。私の場合、Operations クラスと Names クラスに別名をつけて使用しています。
// インポート import scala.collection.jcl.Conversions._ import org.seasar.extension.jdbc.operation.{Operations=>op} import sample.entity.{FooNames=>ns}
// op.eq は Operations, ns.name は FooNames のメソッド def findByName(name: String): Seq[Foo] = jdbcManager.from(classOf[Foo]) .where(op.eq(ns.name, name)) .getResultList;
エンティティのフィールド
S2JDBC/S2JDBC-Gen ではエンティティのパブリックフィールドに対して、テーブルのカラムをマッピングできるため、@BeanInfo や @BeanProperty をつける必要はありません。Scala でエンティティを書く場合は var をつかってカラムに対応するフィールドを定義します。また、アノテーションにパラメータを指定するには以下のように { } 内に記述します。
import javax.persistence.{Column, GeneratedValue, Entity, Id} @Entity class Foo { @Id @GeneratedValue var id: Int = _ @Column{val nullable = false, val unique = true} var name: String = _ }
トレイトの使用
振る舞いや状態を持つトレイトであっても、問題なく使用できるようです。
import javax.persistence.{Column, GeneratedValue, Entity, Id} trait Base { @Id @GeneratedValue var id: Int = _ } trait Named { @Column{val nullable = false, val unique = true} var name: String = _ } @Entity class Foo extends Base with Named
トレイトに @MappedSuperclass を指定する必要は無いようです。*1
Javaの場合、共通のカラムなどは @MappedSuperclass をつけた親クラスにまとめるしかないため、この辺りは Scala の利点が良く出ているかと思います。
serializable や cloneable を指定する場合
Scala で対象のクラスを直列化可能とするには @serializable アノテーションを、複製を可能とするには @cloneable アノテーションを指定します。*2
しかし、java で class Foo implements java.io.Serializable とした時と生成されるクラスが微妙に異なるらしく、S2JDBC-Gen が例外を吐いて失敗することがあります*3。S2JDBC や javax.persistence.* のアノテーションを利用している時点で、.NET で使えるクラスを出力するつもりは無いため、ここは java と同じように java.io.Serializable や java.lang.Cloneable を指定しておきます。
trait Base extends java.io.Serializable { ... }
型パラメータを使用する場合
関連クラスの型に型パラメータを使用すると、S2JDBC-Gen の時点で失敗します。例えば以下のような場合です。
trait Head[A<:Head[A, B], B<:Detail[B, A]] extends Base { self: A => @OneToMany{val mappedBy = "head"} var details: java.util.List[B] = _ } trait Detail[A<:Detail[A, B], B<:Head[B, A]] extends Base { self: A => @Column var headId: Int = _ @ManyToOne var head: B = _ } @Entity class Foo extends Head[Foo, Bar] with Named @Entity class Bar extends Detail[Bar, Foo] with Named
これはおそらく Java でも同様だと思うのですが、Head や Detail に対しては型パラメータの上限境界までしか型情報を取れず、それらの型は Entity ではないため失敗します。型パラメータを使用する場合は、トレイトやクラスでは抽象メンバにして、実装クラスで実際の型を指定したフィールドを作ったほうが良いようです。
trait Head[A<:Head[A, B], B<:Detail[B, A]] extends Base { self: A => @OneToMany{val mappedBy = "head"} var details: java.util.List[B] // 抽象メンバ } trait Detail[A<:Detail[A, B], B<:Head[B, A]] extends Base { self: A => @Column var headId: Int = _ @ManyToOne var head: B // 抽象メンバ } @Entity class Foo extends Head[Foo, Bar] with Named { @OneToMany{val mappedBy = "head"} var details: java.util.List[Bar] = _ // 具象型を指定 } @Entity class Bar extends Detail[Bar, Foo] with Named { @ManyToOne var head: Foo = _ // 具象型を指定 }
エンティティ取得結果
S2JDBC では getResultList で 0 件以上のエンティティを保持した java.util.List[T] が、getSingleResult で null か エンティティインスタンスが取得されます。これらの取得結果を Scala らしく扱えるように、java.util.List[T] は Seq[T] に、null かエンティティインスタンスは Option に変換します。
class FooService(jdbcManager: JdbcManager) { /** java.util.List[T] → Seq[T] への変換 */ import scala.collection.jcl.Conversions._ /** null/エンティティ → Option への変換 */ private implicit def entityToOption(entity: Foo): Option[Foo] = entity match { case null => None case _ => Some(entity) } def findAll: Seq[Foo] = jdbcManager.from(classOf[Foo]) .getResultList def findById(id: Int): Option[Foo] = jdbcManager.from(classOf[Foo]) .id(id.asInstanceOf[java.lang.Integer]) .getSingleResult }
ついでに共通部分を抽象クラスにまとめてしまいます。
/** * エンティティを操作するサービスの抽象親クラス */ abstract class AbstractService[T](jdbcManager: JdbcManager) (implicit manifest: scala.reflect.Manifest[T]) { protected val entityClass: Class[T] = manifest.erasure.asInstanceOf[Class[T]] protected def select: AutoSelect[T] = jdbcManager.from(entityClass) /** java.util.List[T] → Seq[T] への変換 */ import scala.collection.jcl.Conversions._ /** null/エンティティ → Option への変換 */ protected implicit def entityToOption(entity: T): Option[T] = entity match { case null => None case _ => Some(entity) } def findAll: Seq[T] = select.getResultList def findById(id: Int): Option[T] = select.id(id.asInstanceOf[java.lang.Integer]) .getSingleResult def insert(entity: T): Int = jdbcManager.insert(entity).execute def update(entity: T): Int = jdbcManager.update(entity).execute def delete(entity: T): Int = jdbcManager.delete(entity).execute } /** * Foo エンティティを操作するサービス */ */ class FooService(jdbcManager: JdbcManager) extends AbstractService[Foo](jdbcManager) { import scala.s2jdbc.entity.{FooNames => ns} override protected def select = super.select.leftOuterJoin(ns.details) } /** * Bar エンティティを操作するサービス */ class BarService(jdbcManager: JdbcManager) extends AbstractService[Bar](jdbcManager)
以上をふまえることで、Scala からも S2JDBC を問題なく扱えました。Scala も S2JDBC も便利です :-)
*1:トレイトの実装部分は、クラスのコードとして埋め込まれるためと推測されます
*2:Scala は 一応 .NET でも使用できるように意図していたためか、java.io.Serializable のような Java 固有なインタフェースを使う代わりに、@serializable のようなアノテーションを使用するということになっています。.NET 用のコンパイラでは、生成されるクラスに [serializable] 等の属性がつくようになるのだと思います
*3:再現する条件は調べきれていません、型パラメータと組み合わせたときに発生している気がします
*4:深く調査しきれていないため勘違いしている箇所もあるかもしれません :-p
トレイトの初期化処理で抽象 val にアクセスする
トレイトのメンバとして抽象 val を宣言した場合、初期化処理のタイミングでは、その val の値が null になっていることがあります。
以下の例では、Foo の初期化時には value2 が null になっています。
object Sample1 { def main(args: Array[String]) { val foo = new FooImpl println("[main]foo.value1 is "+foo.value1) println("[main]foo.value2 is "+foo.value2) } trait Foo { // value は実装クラスで設定する val value1: String val value2: String // Foo の初期化処理 println("[foo]value1 is "+value1) println("[foo]value2 is "+value2) } class FooImpl extends Foo { val value1 = "I love Perfume!" val value2 = new String("I love Perfume!") } }
実行結果は以下のとおりです。
インスタンス生成を伴う場合に、val の値が null になっているようです。
[foo]value1 is I love Perfume!
[foo]value2 is null
[main]foo.value1 is I love Perfume!
[main]foo.value2 is I love Perfume!
未初期化の場合に null となる振る舞いは抽象 val に限ったことではなく、単独のクラスでも同様です。
object Sample2 { def main(args: Array[String]) { val foo = new Foo println("[main]foo.value is "+foo.value) } class Foo { // value の初期化まえに参照している println("[foo]value is "+value) val value = "I love Perfume!" } }
実行結果は以下のとおりです。
[foo]value is null
[main]foo.value is I love Perfume!
これらの振る舞いを回避するには、以下のような方法が考えられます。
- val を参照している箇所よりも前に、val の定義を記述する
- val に lazy をつける
Sample2 のような場合であれば val の定義を前方に移動する方法ですみます。しかし、Sample1 の場合は、親(Foo)→子(FooImpl)の順に初期化がなされるため、lazy をつけることで回避します。
object Sample3 { def main(args: Array[String]) { val foo = new FooImpl println("[main]foo.value is "+foo.value) } trait Foo { val value: String println("[foo]value is "+value) } class FooImpl extends Foo { // lazy をつけて定義する lazy val value = new String("I love Perfume!") } }
実行結果は以下のとおりです。
[foo]value is I love Perfume!
[main]foo.value is I love Perfume!