yamicha.com's Blog - Presented by yamicha.com
Blog yamicha.com's Blog - 2017/09 の記事
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22] [23] [24] [25] [26] [27] [28] [29] [30]

yamicha.com's Blog
 諸事情により、現在更新休止中。ご了承ください。もし今後ブログを再開することがあるとすれば、その際にはこのブログスクリプトではなく、新しく開発したものによるかもしれません。
 当ブログ管理者についてはこちらをご参照。
開発魔法(737)
社会問題(733)
お知らせ(11)
質問・バトン回答(15)
ゲスト出演(8)
経済・知的財産(150)
ゲーム開発(182)
[Ada] 勝手に補足
- Note
- 金配りの次の一手


- Endless Ultimate Diary
- 銃世界

漢字バトン
- うるる雑記帳
- 漢字接力棒

ツキアイゲノムバトン
- ブログ@うにうに画像倉庫
- あぶ内閣

縺イ縺セ縺、縺カ縺励ヰ繝医Φ
- 月夜のボヤキ
- 騎士サーラバトン
パスワードを使う
名無し (2012/02/27)


開発者解放裁判
yamicha.com (2010/03/14)
Winnyに関しては、私も「純白」とまでは考えておりませんし、使用し..

開発者解放裁判
通りすがり (2010/03/08)
winnyに関しては「ダウンロードソフト板」なんてところを拠点に開発..

新型インフルエンザの恐怖
いげ太 (2009/11/03)
> C#などの「int Some(int , int)」は、F#では「(int * int) ->..

時効に関する思考
yamicha.com (2009/08/31)
>いげ太さんコメントありがとうございます。手元にドキュメントが少..
Homepage
Blog Top
Normal View
List View
Search
Calendar
Comment List
Trackback List
Blog RSS
Send Trackback
Phone Mode
Administrator
yamicha.com
Blog
るううるる。
Source
法令データ提供システム
FindLaw
Development
Java2 Platform SE 6
Java EE 6 API
MySQL Developer Zone
PHP Reference
MSDN Library
Ada Reference Manual
Objective Caml
Python Documentation
Erlang
Prolog Documents
カテゴリ表示
 カテゴリ ゲスト出演 に当てはまるもののみを表示します。

 存在する記事 8 件の中から 1-5 件を表示しています。
思いのツボ
2007/02/14(Wed)18:21:25
 北朝鮮問題。一言で表すなら「完全に北朝鮮ペースにはまった日本をはじめとする面々」といったところでしょう。
 今回の協議で明るみに出た、最も恐ろしいこととは何か。北朝鮮がエネルギーを手に入れることでも、核兵器を本当に廃棄する気があるのか分からないことでもありません。他の国にとっては所詮拉致などオプションに過ぎないことが証明された点です。
 これは明らかに日本の戦略ミスです。北朝鮮の拉致が発覚し、しかも北朝鮮が核開発などを始めて多くの国のひんしゅくを買ってからかなりの時間が過ぎているのに、「拉致は国際誘拐犯罪テロ行為であり、許されざる行為である」という意識を諸外国に浸透させることができなかった(あるいはそのような戦略すら取らなかった)のです。結果として他国にとっては「拉致などどうでも良い」ことになり、このような結末となったのです。
 日本にとっては拉致問題こそ最大の問題であり、核は二の次三の次です。そもそも核問題は日本が行動しなくても諸外国から非難されるはずであり、また本当に使用するしないという段階になれば米国が黙ってはいません。つまり、日本は核問題の解決を他国に全部丸投げしてでも拉致問題の解決に尽力することが可能であり、またその程度の気概で挑まなければなりません。
 仮にこのまま核問題が片付いたとしても、日本にとっては「多数ある懸念の1つが片付いたが、最大の懸念に関する状況は何ら変化していない」といった状態に過ぎず、はっきり言って何の解決にもなりません。核などどうでも良いですから、何をおいてもまず拉致です。拉致問題がある限り、核問題などはいくら後回しにしても構いません。そもそも今の時期に核で騒ぎ立てるのは、拉致問題をかすませる作戦であるとも考えられます。
 最悪なのは、このままエネルギー支援が行われ、核問題が一応の解決を見たにもかかわらず、日本は例によってそれまでに大した行動をするでもなく、国際的な拉致包囲網を作れなかった場合です。この場合、エネルギーを手に入れた北朝鮮が上手に出てくる可能性は十分考えられ、しかも諸外国は核問題の解決によって北朝鮮に関心を向けなくなり、日本はエネルギーを得た北朝鮮に孤立無援で挑まなければなりません。現状でも手玉に取られているのに、これで拉致問題を解決できるでしょうか。
 当然、最大の懸念でも何でもない核ごときの見返りにエネルギーを渡してやるいわれはありませんから、拉致がこのままなら日本は一切の援助を行うべきではありませんし、同時に諸外国を巻き込んで支援の量を減少させても良いでしょう。そして、諸外国を巻き込むためには、やはり拉致問題の重要性を諸外国に訴え、拉致包囲網を作るしかありません。
 エネルギー支援がおおむね決まってしまった以上、核問題が一応の解決を見るまでに拉致包囲網を作れなければ、日本は正真正銘の孤立無援となります。さらに言えば、エネルギーが手に入らずヒーヒー言っていた時の北朝鮮に迫れなかったのは非常に痛いです。喉が渇ききった相手に水をあげたも同然ですから、これでまたチャンスを逃したことになるでしょう。
 今すぐにでも次のリーダーが現れ、拉致問題の解決に向かって動き出さないものでしょうか。猶予はさほど残されていないというのに。

 次のリーダーといえば、またも腹立たしいことが。自民党が参院選に郷ひろみ氏を擁立しようとしているというのです。相手にも都合というものがあり、どうやら実際に擁立するのは難しいようですが。
 理由は「柳沢発言などで失った女性票を確保し、無党派層の票を集めるため」とのこと。言うまでもありませんが、郷ひろみ氏が自ら政党に出馬を打診するほどやる気になって、何か目新しい政策でも引っさげてきたというのならともかく、少しでも考える能力のある人であれば、有名人というだけのことで郷ひろみ氏に投票したりはしません。大学で勉強してマニフェストまで作って挑んだ「そのまんま」流とは違うのです。
 すなわち、「考えた末の無党派」の人であれば、自民党を支持しない限り郷ひろみ氏に入れたりはしません。逆に言えば、自民党の狙いは「何も考えていない無党派」、すなわち「低IQ層」ということです。そして、竹中氏の戦略曰く低IQ層である「若者」「老人」「主婦」のうち、狙いはズバリ「主婦層」でしょう。
 つまり、安倍氏で参院選は戦えないと見るやいなや、自民党は早速低IQ戦略を使おうとしているのです。郷ひろみ氏が応じない可能性が高そうなのが唯一の救いでしょうか。それにしても許せません。
 しかし、こういう戦略がなぜ成り立つかといえば、だまされる人間がいるためです。中身の全くないワンフレーズにだまされ、低IQ戦略に見事にだまされ、納豆ダイエットにだまされる。これを愚かと言わずして何と言うのでしょう。いい加減、こういうバカげたものに踊らされるのはやめましょう。

 後から後から湧いて出るねつ造。どこのテレビ局も似たようなことをやっているのでしょうが、目を覆うばかりです。
 菅総務相などは「法改正も含めて再発防止策を検討する」としていますが、これでは政府が報道に介入しかねず、不健全であることは事実です。しかし、メディア・スクラムなども含めて、これまでマスコミ連中がやってきた非道な行いを忘れてはいけません。いくら言っても聞かないから規制もやむを得なくなるのです。特に犯罪被害者や加害者周囲に対する報道などについては、法的な規制が絶対に必要です。報道の責任を完全無視する連中に報道の自由など必要ありません。
 それはともかく、最近は「考えれば分かる」ことにも簡単にだまされる傾向が強いようです。例えば納豆のようなありふれた食材がダイエットに効くなら、日本人の多くはガリガリにやせているはずですし、そもそも飢饉で全滅しているでしょう。レタスを食べて眠くなるようなら、車の説明書に「レタスを食べた後で運転しないでください」と表記されるはずです。このようなものは常識で考えれば分かるというものです。
 毎日新聞に「理系白書」というコーナーがあります。これによれば「水に対して良い言葉をかけるときれいな結晶になり、汚い言葉では結晶が崩れる」というニセ科学にだまされる人間がいるのだとか。「それならShineと書いた紙を貼ったらどうなる」とはもっともな指摘です。実際、「馬鹿」のような言葉であっても、単に「馬」と「鹿」を並べて書いただけであり、馬や鹿が不適切な言葉とは考えられません。このような子どもだましにどうすればだまされるのか、そちらに興味があります。
 そういえば、私の手元には「虹とひまわりの娘」という本があります(池田小事件遺族の手記。これは少年犯罪者の更生などにも使われているそうです)。内容は本題と関係ないため、これの内容についてのコメントは差し控えますが、「あとがき」に「雪の結晶」の話が記載されていました。内容としては、器に「天使」「ありがとう」などのラベルを貼った場合は結晶がきれいになり、「馬鹿」「悪魔」などでは崩れるのだとか。こういう影響力の強い手記にエセ科学が記載されたのは残念でなりません。
 実際にそういうことが起こるなら、「Shine」やら逆さの「Death」やら「東洋の魔女」などと書いたラベルを貼って実験してみたいものです。さあ、どうなるでしょうか。Shineは「輝いている」のか「死ね」なのか。タロットのDeathは逆位置だと意味が逆になりますが、果たして雪や水の結晶はそれを認識できるのか。魔女といえば「老婆」「妖女」「魔女狩り」などのイメージですが、「東洋の魔女」はこの上ない賛辞として用いられたものです。このようなことで中身が変わるわけないでしょう
 残念ながら私は理系ではないため、後の分析は理系の方に譲るとしまして。ただこの「理系白書」、どうも変といいましょうか。何でも「女性科学者はかっこいい!」のだそうで。お願いですから「かっこいい」という理由で科学者や開発者を名乗らないでください。胃に穴を開けてでも技術を習得するのがバカバカしくなってきます。

 3Dライブラリを気持ちばかりメインプロジェクトに格上げしつつ、JSFをやってみました。「JavaServer Faces」です。
 実のところ、私は「コードとデザインの分離」には極めて懐疑的です。これを用いる主な理由としては、「分離によって開発が容易になり、分かりやすくなり、各カテゴリの実装に集中できる」ことがあるようですが、それだというのにStrutsやJSFなど様々なものが出てはまた消えているのはなぜでしょう。一時期は「これで良い」と考えつつも、実は誰もこのような形態に真に満足していないとは考えられないでしょうか。
 「これだ」という技術はそう簡単にはさびれません。C++やPerlが未だに使われているのは言うに及ばず、StrutsやJSFに似たものが現れては消える中でもServletやJSPは延々と支持されてきました。特にPerlはコードとデザインに差をつけることが難しい言語の1つでしょうが、Web黎明期から現在まで使われている点は注目に値します。
 つまり、実のところ「コードとデザインの分離」などさほど望まれていないとは考えられないでしょうか。熱病のように「これは楽で簡単で使いやすい」と考えてみても、後から掛け値なしに見れば楽でも簡単でも使いやすくもなく、結局さびれてしまうのです。便利だ、便利ではない、といったことを論じるのは簡単ですが、実際にこうした技術が次々に現れては消えている現実は変えられません。
 ですから、JSFやStrutsがこの世からすべて消え去ったとしても、Servlet/JSPは残るでしょう。「旧言語」のはずのPerlが未だに扱われているのを見ての通り、真に望まれているものはなくならないのです。実際Perlは大抵何でもできますし、JSPはその軽さと自由度が極めて魅力的です。正直言ってPHPはどこまで健闘できるか分かりませんが、Perlほど長生きできるようには見えません。
 このように、いずれは消えてなくなりそうなJSFですが、おそらくしばらくは生き残ってくれるでしょう。使ってみるとしましょうか。
 まず「JavaServer Faces 1.1」をダウンロードしてきました。1.2も使ってみようとしたのですが、結局動きませんでした。ここは無難に1.1を使ってみようと考えた次第です。SunのサイトからダウンロードできるJSF 1.1のZIPにはサンプルも含まれているのですが、まずはこれを設置して動作を確認してみるとしましょう。
 といっても、「samples/jsf-guessNumber.war」をTomcatのwebappsに投げ入れるだけです。これで勝手に認識を行ってくれるため、後はブラウザから「/jsf-guessNumber」にアクセスすれば動作確認が行えます。何でも「数当てゲーム」なのだそうで。
 ではここからが本番です。JSFを使ったアプリケーションを自分でも作ってみましょう。JSFには次のファイルが必要だそうです。

・データの保持や受け渡しを行うBeanクラス
・JSPなどブラウザに表示する画面の実装
・Servletではおなじみのweb.xml
・faces-config.xml

 今回のファイル配置はこうなります。
/webapps
 /jsf
  /WEB-INF
   web.xml
   faces-config.xml
   /classes
    /com
     /yamicha
      /jsf
       TableBean.java
  search.jsp
  res.jsp
 JSFを使うにはいくつかのJARやタグライブラリが必要ですので、あらかじめTomcatの「common/lib」に「guessNumber/WEB-INF/lib」の中身のJARなどを全部投げ入れておきます。これでTomcatからJSFが使用できるようになります。念のため書いておきますが、JSFを使ったServletをコンパイルする際には、jsf-api.jarやjsf-impl.jarにクラスパスを通さなければなりません。
 ではまずまどろっこしいXMLから。といっても、これらをまともに記述していてもらちがあかないため、どちらもguessNumberのXMLを原本として作成しました。
// web.xml
<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE web-app PUBLIC
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <display-name>JSF Application</display-name>
  <description>JSF Application</description>

  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>

  <context-param>
    <param-name>com.sun.faces.validateXml</param-name>
    <param-value>true</param-value>
  </context-param>

  <context-param>
    <param-name>com.sun.faces.verifyObjects</param-name>
    <param-value>true</param-value>
  </context-param>

  <!-- Faces Servlet -->
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- Faces Servlet Mapping -->
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>

</web-app>
 一体何を設定しているのだか。とりあえず重要なのが「Faces Servlet」の部分です。url-patternを「/faces/*」にしていますが、この場合はアクセスURLに「faces/」を加えることでJSFが適用されることになります。逆に言えば、「faces/」なしではJSFの機構が適用されない点に注意しなければなりません。
// faces-config.xml
<?xml version="1.0" encoding="Shift_JIS"?>

<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>
  <application>
    <locale-config>
      <default-locale>en</default-locale>
      <supported-locale>de</supported-locale>
      <supported-locale>fr</supported-locale>
      <supported-locale>es</supported-locale>
    </locale-config>
  </application>

  <navigation-rule>
    <description>
	 入力を受け取ります。
    </description>
    <from-view-id>/search.jsp</from-view-id>
    <navigation-case>
      <description>
	   操作が成功した場合の動作です。
      </description>
      <from-outcome>success</from-outcome>
      <to-view-id>/res.jsp</to-view-id>
    </navigation-case>
  </navigation-rule>

  <managed-bean>
    <description>
	 テーブルのデータを保持します。
	</description>
    <managed-bean-name>TableBean</managed-bean-name>
    <managed-bean-class>com.yamicha.jsf.TableBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
      <property-name>table</property-name>
      <property-class>java.lang.String</property-class>
      <value>y_namelist</value>
    </managed-property>
  </managed-bean>

</faces-config>
 やたら色々書いてありますが、良く見ると意味は簡単です。まず「navigation-rule」ですが、今回は1つしか使っていませんが、複数記述することができます。「from-view-id」には処理を行うJSPを指定するようです。
 この「navigation-rule」には「navigation-case」を複数含めることができるようです。また、「from-outcome」と照合される文字列はJSP側で決めることができ、送られた文字列に応じて処理を分岐することができるようです。今回の場合、JSP側から「success」が送られれば、「to-view-id」で指定された「/res.jsp」に制御を送ります。
 その下ではBeanを登録しています。Bean名はTableBean、クラスはcom.yamicha.jsf.TableBeanです。「managed-property」を使用すれば、最初からBeanの特定の変数に値を代入しておくことができるようになっています。言うまでもありませんが、property-nameはBean名です。上記の場合だと「setTable("y_namelist")」が呼ばれることになるでしょう。
 それでは、TableBeanを実装します。
// TableBean.java
package com.yamicha.jsf;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
import java.util.*;

public class TableBean{
	private String table;
	private String search;

	private String name;
	private String creator;
	private String prefix;
	private String description;

	public TableBean(){
	}

	public String getTable(){
		return table;
	}
	public String getSearch(){
		return search;
	}
	public boolean getDatas(){
		name = null;
		creator = null;
		prefix = null;
		description = null;

		boolean r = false;
		try{
			Context ct = new InitialContext();
			DataSource ds = (javax.sql.DataSource)
				ct.lookup("java:/comp/env/jdbc/MySQLDB");
			Connection c = ds.getConnection();

			PreparedStatement ps = c.prepareStatement(
				"SELECT name , creator , prefix , " + 
				"description FROM " + table + 
				" WHERE name = ?");
			ps.setString(1 , search);
			ResultSet rs = ps.executeQuery();

			r = rs.next();
			if(r){
				name = rs.getString("name");
				creator = rs.getString("creator");
				prefix = rs.getString("prefix");
				description = rs.getString("description");
			}

			ps.close();
			c.close();
		}catch(Exception e){
		}
		return r;
	}

	public String getName(){
		return name;
	}
	public String getCreator(){
		return creator;
	}
	public String getPrefix(){
		return prefix;
	}
	public String getDescription(){
		return description;
	}

	public void setTable(String t){
		table = t;
	}
	public void setSearch(String s){
		search = s;
	}
}
 コードとビューの分離とはこういう意味ですか。ビューから処理を押し付けられて難儀するBean、といった印象を受けます。BeanはあくまでBeanであって、勝手気ままに処理を実装するわけにもいきませんし、コードとビューを分離するならJSPから処理用メソッドを好き放題に呼び出すこともできません。
 その肝心のJSPはこうなります。
// search.jsp
<%@page contentType="text/html;charset=Shift_JIS" %>

<html>
<head>
<title>Name Search</title>
</head>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<body>
<f:view>
<h:form id="jsfname">
 検索する人物の名前を入力してください。<br />
 <br />

 <h:inputText id="search" value="#{TableBean.search}" />
 <h:commandButton id="submit" action="success" value="Submit" />
</h:form>
</f:view>

</body>
</html>
 JSFを使っている限り、プログラマは慣れ親しんだform記述を使うことができません。JSFの機能を使いたければ、ほとんどのタグにおいてJSFで定義されたタグを使わなければならないのです。
 inputTextは「input type="text"」です。valueとして「#{TableBean.search}」が指定されていますが、これによってデータ送信時にTableBeanのsearchに対して入力されたテキストが代入されます。
 commandButtonではactionに「success」が指定されています。これで処理の分岐が可能なのでしょう。困ったことに、ボタンをクリックすればsuccess動作が実行されますが、テキストボックスでEnterしてもsuccess動作が実行されたとはみなされないらしく、何も起こりません
 actionには文字列の他、「#{BeanName.actionMethod}」のようにメソッドを指定できるようです。この場合、指定されたメソッドが呼び出され、返り値のStringがaction動作になるようです。今回はサンプルですから使いませんでしたが、通常はこの方が良いでしょう。
 結果を表示するJSPはこの通り。
<%@page contentType="text/html;charset=Shift_JIS" %>

<html>
<head>
<title>Name Search</title>
</head>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<body>
<b>Result</b><br />
<br />

<f:view>
<h:column rendered="#{TableBean.datas != true}">
 <h:outputText value="データは検出されませんでした。" />
</h:column>

<table border="0">
 <tr>
  <td bgcolor="#CCDDFF">名前</td>
  <td><h:outputText value="#{TableBean.name}" /></td>
 </tr>
 <tr>
  <td bgcolor="#CCDDFF">接頭辞</td>
  <td><h:outputText value="#{TableBean.prefix}" /></td>
 </tr>
 <tr>
  <td bgcolor="#CCDDFF">作者</td>
  <td><h:outputText value="#{TableBean.creator}" /></td>
 </tr>
 <tr>
  <td bgcolor="#CCDDFF">概要</td>
  <td><h:outputText value="#{TableBean.description}" /></td>
 </tr>
</table>

</f:view>

</body>
</html>
 これで完成です。ただし、このプログラムはテーブルがなければ動作しません。テーブルを作ってデータを登録するとしましょうか。
CREATE TABLE y_namelist(number INT AUTO_INCREMENT ,
name VARCHAR(256) , creator VARCHAR(256) ,
prefix VARCHAR(256) , description TEXT , PRIMARY KEY(number) ,
UNIQUE(name));

INSERT INTO y_namelist VALUES
(NULL , 'ルヴァ' , 'ruva' , '司祭' , 'ゲスト出演者。SQL編65話に登場') ,
(NULL , 'リサ' , 'ruva' , '僧侶' , 'ゲスト出演者。SQL編65話に登場') ,
(NULL , 'ファルシア' , 'ruva' , '聖騎士' , 'ゲスト出演者。SQL編65話に登場') ,
(NULL , 'クルード' , 'coolmint' , '剣士' , 'ゲスト出演者。SQL編101話に登場') ,
(NULL , 'フィオーナ' , 'coolmint' , '風術師' , 
'ゲスト出演者。SQL編101話に登場') ,
(NULL , 'ドロシー' , NULL , '数学者' , 
'SQL編99話に登場。語尾の伸ばす音を省略する癖がある') ,
(NULL , 'ミスティ' , NULL , '研究者' , 
'SQL編80話に登場。シェリーの友人で研究者') ,
(NULL , 'ニセサーラ' , NULL , '騎士' , 
'SQL編49話、86話に登場。「サラサーラ」かも。本物よりも知性は劣るらしい');
 この際どのようなデータでも構わないのですが、ゲスト様やマイナーキャラを忘れては困るため、備忘録ということで。
 後は次のURLにアクセスするのみです。

http://tomcat_host:tomcat_port/jsf/faces/search.jsp

 おそらく大抵は「http://localhost:8080/...」でしょうが、インストール時にポートの設定を変えた場合、あるいは他のPCからアクセスする場合には、これとは異なるURLになるでしょう。
 とにかくアクセスしてみると、名前を入力するフォームが表示されます。上記でデータベースに登録したキャラクターのうち、好きなキャラの名前を入力して「Submit」を押してみましょう。おそらくそのキャラの概要が表示されることでしょう。
 それにしても、数学者や科学者(の一部)は本当に「オイラ数」などと言うのでしょうか。気になって仕方がありません。固有名詞の伸ばす音は省略しないものと考えていたら、省略している人もいるようですし。「アーサ王物語」「ヴァルキリ」「イージェービ」「ソーサラ」「コモンロ」「キャッシュフロ」のようになるわけですが、不自然とは考えないのでしょうか。
 事実、「フロッピ」ではあんまりだということで「フロッピィ」なる表記が生まれたそうですが、「コモンロウ」や「イージェービィ」(EJB)辺りはまだ良いとして、「ヴァルキリィ」「ソーサラァ」ではレベルの低いメールの文体のようですし。
 とにかく、これでJSFも使ってしまったことになります。しかし上記のプログラム、JSPならファイル1つだけでごく短く簡単・簡潔に書けるのですが。おそらく10年後にもServlet(あるいはその後継)は存在しているでしょうが、JSFは自信ありません。
カテゴリ [開発魔法][社会問題][ゲスト出演] [トラックバック 0][コメント 0]

そのまんま手柄窃盗
2007/01/25(Thu)12:41:17
 あきれました。もうこの上なく。
 そのまんま東(東国原)氏当選。同氏としては「東国原」と呼んでほしいようですが、「そのまんま東」の方が区別がつけやすいですし、何より「そのまんま東」を通称名として使用し、それで当選した以上、当選した途端に「東国原」ではバランスを欠きます。通称名「そのまんま東」の呼称は免れません。
 とにかくそのまんま東氏は知事選で圧勝、当選しました。同氏も知名度先行とはいえそれなりの努力をしての当選であり、選挙前には「引退宣言」も行っています。芸能界は一般の会社に比べて異常なほど甘いため、引退宣言をしたところで後で復帰しようとすれば簡単にできるでしょうが、一応「退路を断つ」努力とみなすことはできるでしょう。また、マニフェストなどに関しても色々勉強を重ねてきたようです。当選の報が入るまで、相当緊張した時間を過ごされたことでしょう。
 しかし、大変嘆かわしいことなのですが、この事実を横取りしようとする浅ましすぎる人間が存在します。そういうハイエナのような人間が我が国に存在するというだけで頭が痛くなりますが、いくら嘆いても実在するのですからどうしようもありません。過去に存在したハイエナである「堀江」や「村上」などはオリの中ですが、このハイエナは今でも幅を利かせて堂々としていらっしゃいます。
 その名は「安倍」。何でも「氏は不祥事から立ち直り、大学を出て再チャレンジを果たした」そうです。何をバカげたことを。今回ばかりはこの不用意な発言に極めて強い怒りを覚えます。もはやあきれて物も言えませんが、あえて文字にするなら「バカを言うんじゃない、バラマキ安倍」の一言です。
 そのまんま東氏は以前に不祥事を犯しましたが、これは民間なら(たとえ冤罪でも)一発で終わりです。復帰など無理であり、ましてや出馬など夢のまた夢です。もし安倍氏が「そのまんま東氏のような売れっ子芸人には再チャレンジが認められるべきだ。一般国民に再チャレンジなど恐れ多いものであり、一切必要ない」という意味でこれを言ったのなら納得ですが。どちらにせよ、他人の手柄を横取りして、知事選で負けて苦い顔ながら「再チャレンジ」のアピールとは。確かに日本は美しい国ではなくなっているようです。首相からしてハイエナ同然なのですから。あまりに節度というものが不足しています。
 全く、愛国心や奉仕活動を押し付ける前に、自分の節度から何とかしてはいかがですか。しかもその目玉政策の「再チャレンジ」も単なるバラマキであり、また「ホワイトカラー〜」制度のように再チャレンジと完全に相反する制度の導入にも熱心です。このような首相がのさばる国など全く美しくありません。
 「私が愛国を口にしないのは、妻に愛を口にしないのと同じ」とは「バカの壁」の著者の言葉ですが(作者の年代が分かるような発言です)、この「バカの壁」によれば「愛憎は紙一重の関係にあり、愛も何もなければ全くの無関心になる」とのこと(発売から間もないころに読んだためうろ覚えですが)。ということは、日本の政治、教育、経済などについて論じている、あるいは思考している多くの人は、広義の「愛国」を持っている、とみなすことができそうです。
 つまり、私のような主張者ですら「愛国心の押し付けのようなバカげた教育を絶対に容認することができず、ハイエナ同然の首相が国のトップであることに強い怒りを覚える」程度の愛国を持つものと定義することができます。逆に言えば、こういうことが平気でなされる国は「愛されるなどもっての他の汚れ果てた国」ということです。
 事実、日本古来の文化である「礼」と「助け合い」はここまででもかなり弱っていたのに、小泉・安倍路線がトドメをさしてしまいました。逆に、日本政治の悪しき伝統である「バラマキ」を「再チャレンジ」の名の下に復活させようというのが安倍政権です。
 そのまんま東氏の手腕はまだ未知数ですが、少なくとも地元を盛り上げたい気持ちが存在するのは事実でしょう。そうでなければ、タレント活動に精を出していた方がリスクも少ないのですから。地元愛で新人として飛び込んだ東氏と、自党候補が氏にボロ負けしたくせにそれを「再チャレンジ」のアピールに使う安倍氏、見事なまでの好対照といえましょう。美しい日本を愛する多くの国民も、最近は「安倍不支持」に回っているようですし。

 ところで、宮崎知事選を少々分析してみたのですが、面白いことが分かりました。
 まず投票者を大きく4つに分類してみます。固定層、政党支持層、無党派層、低IQ層となるでしょう。これらのうちどの層が勝負を握るかは、選挙の状況によってかなり変わってきます。それでは宮崎の知事選ではこれらがどのように絡んできたのでしょうか。
 その前にこの分類の意味を簡単に述べておきましょう。まず固定層は利権者や公明党支持者などであり、変動が少ない特徴があります。政党支持層はそのまま、特定の政党を支持している人です。ただし、特定の政党支持層の人が必ず支持政党に投票するわけではありませんし、最近は支持政党も流動的になってきています。3つ目と4つ目は双方とも無党派層ではありますが、前者が政治を理解しているのに比べ、後者はそれを理解していないという点が違います。無党派層には棄権も多いと考えられますが、前者が棄権する理由は「支持できる政党がない」、後者は「政策の意味が分からないし、興味もない」が主な理由でしょう。
 ですから、「無党派に訴える」戦略を取る場合においても、「狭義の無党派層に訴える」のと「低IQ層に訴える」のとでは作戦が全く違ってきます。狭義の無党派層に訴えるのであれば、とにかく政策を十分に練り、主張・実績を積み重ねることです。逆に低IQ層に訴えるのであれば、政策を論じてはいけません。とにかくパフォーマンスに終始し、自分のみ正義で他が悪であると錯覚させ、マスコミに1秒でも多く取り上げられるようにすることです。
 では今回の「そのまんま選挙」の様相は。出口調査によれば、自民党支持者の3割、民主党支持者の4割強、無党派層の過半数が東氏に投票したそうです。低IQ層がそのまんま東氏の知名度のみで投票した分もかなり入っているのでしょうが、この結果はそれだけでは説明できません。
 ここで他の候補についても分析してみましょう。民主党が支援した候補は、自民支持層の4割、民主の4割強、無党派の3割から支持を受けています。自民党が支援した候補については、自民支持層で3割、民主で1割、無党派層で1割という有様です。自民支持層では3者が票を奪い合い、民主支持層では東氏と自党支援候補が奪い合い、無党派では東氏が圧倒的ではありますが、民主支援候補も3割を得ています。
 このいびつな構造はなぜ発生したのか。土地柄、前知事の不祥事、民主支援候補について自民の一部からも推す声があったことなども検討しなければなりませんが、簡単に言えば「自民支持層にも自民党に愛想をつかせている人が少なくない」「無党派層は案外民主党にも入れており、自民党を嫌っている」「民主支持層にも民主党に物申したい人が少なくない」ことが読み取れます。
 まず自民党ですが、私が総裁選以前から指摘していたように、安倍氏のあまりにひどい思想と政策は多くの国民の知るところとなりました。これがかなりのダメージとなっていることは言うまでもありません。特に無党派層から全く支持を受けられなかった点は注目に値します。(狭義の)無党派層が自民の横暴を嫌ったと考えるのが自然です。
 民主党は意外にも善戦していますが、民主党支持層でも東氏支持がかなり多かった点に留意する必要があります。つまりふぬけた小沢体制に怒る民主党支持層の人々が東氏を受け皿としたということです。小沢体制が許せないから民主には入れたくない、しかし自民党にはもっと入れたくない、それなら東氏に、というわけです。
 東氏の場合はどうでしょうか。低IQ層が選挙に行けばほぼ確実に東氏に入れることでしょうが、東氏は比較的抑え目の選挙活動を行っていました。また、無党派層において意外に民主支援候補が支持を得たのは、(狭義の)無党派層の一部が「タレント反発票」を投じたのと、低IQ層の投票が案外少なかったことが考えられます。低IQ層の投票がもっと多ければ、無党派層全体の母集団がかなり大きくなり、また低IQ層のほとんどが東氏に入れると考えられることから、相対的に無党派層における民主・自民支援候補の得票比率はもっと下がっていたはずです。
 結論としては、まず前知事の不祥事が根底にあり、さらに安倍氏の横暴的な行為によって多くの人が「自民離れ」を起こし、「自民党には絶対入れない」という状態に陥ったものと考えられます。しかしながら、民主党は小沢体制以後使い物になりません。だとすれば、この選挙が自民・民主の一騎打ちであれば民主党にするか棄権するかの選択になるところですが、今回は東氏がいました。つまり「東氏に入れる」ことがそのまま解になるのです。
 民主支援候補が意外にも広い層から票を得ているのは「自民は支持に値しない。しかしタレント候補は支持できない」といったタレント批判票によるものでしょうが、ここで東氏の作戦が奏功しました。アピールを抑え目にすることで、低IQ層の票を犠牲にする代わりに、(狭義の)無党派層と政党支持者を必要以上に「批判票」に走らせずに済んだのです。これがもしタレントの知名度を大々的に使った選挙であったなら、低IQ層から広く票を集める反面、「バカにするな」とばかりに他の層の大反発を招き、当選の事実は変わらないにしても勢力図はかなり違ったものになっていたでしょう。
 具体的にはどうなるのか。今選挙での無党派層の投票比率は「東5.5:民3:自1.3」程度のようですが、もし東氏がパフォーマンス選挙を行っていたら、「東5.5〜6.5:民2.5〜3:自0.5〜1」辺りになったのではというのが私の見立てです。低IQ層の分だけ無党派全体の母集団の数が増え、東氏の得票も増える代わりに、批判票として民主党の得票もある程度増え、狭義の無党派層からも低IQ層からもあまり投票されないであろう自民党はさらに埋没する格好です。
 他の政党では、自民支持層の比率はあまり変わらないか、東氏の比率が多少減ることが予想できます。民主支持層の投票比率では東氏と民主支援候補がほぼ互角なのですが、東氏がパフォーマンス選挙を行って反感を買っていれば、おそらく東氏の支持が減って民主支援候補の支持が増える形になっていたでしょう。
 では気になる参院選の行方は。残念ながら、この分析から参院選の動向を読むのはかなり困難です。参院選には「そのまんま東」がいないのですから。「メッキがはげた安倍政権、自民党は許せない」のではありますが、「民主党は小沢体制になってから劣悪になってしまった」ため、他に受け皿があればそこが躍進したことでしょうが、今の日本に受け皿はありません。もし民主党が前のままなら自民の受け皿になっていたはずですが、小沢体制では受け皿となることすらおぼつきません。何とも予想がつかない状態です。
 ただ、理想を言えば民主党に勝って欲しいです。もし自民党が勝ちでもすれば、無賃残業合法化制度が導入されてしまいかねません。同時に消費税を上げて法人税を下げる作戦にも出てくるでしょう。参院選まで共謀罪が留保されていたなら、これも成立させられることになります。ハイエナ首相の横暴をこれ以上許すことはできません。

 Persistence APIをTomcatで動かす。確かにPersistence APIはJava EE向けに作られたものですが、TomcatはJava EEというよりServletコンテナと表現する方が適切なものです。果たしてそのようなことが可能なのでしょうか。
 まずBluePrintsで習得した仕様を検討してみます。BluePrintsのサンプルのbuild.xmlによれば、Webアプリケーションはコンパイル後にwarファイル内で次のような配置を取るようです。
FileName.war
 /WEB-INF
  /classes
   /com
    /yamicha
     /package_name
      *.class
   /META-INF
    persistence.xml
 *.jsp
 興味深いのは、クラスファイルは「classes/com/yamicha...」内に配置され、persistence.xmlもJava SEで使用する場合と同じ位置に配置されるのですが、*.jspだけはWEB-INFの外に置かれている点です。Tomcat及びTomcatを内部で使用しているSun AppServerでは、JSPは自動的に「org.apache.jsp」に属するとみなされるようですが、上記の配置がSun AppServerで動くとされているからには、同じ機構を用いているTomcatでも動くでしょう(根拠のない自信)。
 ではまずアプリケーションをデザインするとしましょうか。以前に色々Servletを試した際、実験用ディレクトリとして「examples」を作成しましたので、今回はこれを利用します。ここにBluePrintsの完成系と同じようにファイルを配置していけば良いのです。たぶん。
 具体的にはこうなります。
/Tomcat_Dir
 /webapps
  /examples
   persist.jsp
   /WEB-INF
    /classes
     /META-INF
      persistence.xml
     /com
      /yamicha
       /toplink
        /characters
         Creator.class
         Element.class
         Name.class
         Prefix.class
         Sex.class
         SexList.class
 この配置やクラス名にピンと来た方、ご名答です。クラス及びパッケージ名はここで作成したものと同じなのです。ただし、今回は複合キーは使いません。カスケード構造も少々変更しています。おかげで、中身はほとんど同じというのに、無駄に長々とソースを記述することになりそうです。
 まずpersistence.xmlから。
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
 <persistence-unit name="persist" transaction-type="RESOURCE_LOCAL">
   <provider>
oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider
</provider>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
   <property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver" />
   <property name="toplink.jdbc.url" 
value="jdbc:mysql:///yamicha?useUnicode=true&characterEncoding=SJIS" />
   <property name="toplink.jdbc.user" value="yamicha" />
   <property name="toplink.jdbc.password" value="password" />
   <property name="toplink.ddl-generation" value="create-tables" />
   <property name="toplink.target-database" value="MySQL4" />
  </properties>
 </persistence-unit>
</persistence>
 以前までとほとんど同じです。TomcatではJNDIからのDBの取得ができないのか、<non-jta-data-source>を使うと例外を投げられました。
 次は実に長々としたソースです。
// Creator.java

package com.yamicha.toplink.characters;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_creator" , schema="yamicha") 
	public class Creator{
	private int number;
	private String url;
	private String creator;
	private List<Name> names;

	public Creator(){
		names = new ArrayList<Name>();
	}
	public Creator(String url , String creator){
		this();
		this.url = url;
		this.creator = creator;
	}

	@Id @GeneratedValue(strategy=GenerationType.SEQUENCE) 
		@Column(name="number") 
		public int getNumber(){
		return number;
	}

	@Column(name="url") public String getURL(){
		return url;
	}
	@Column(name="creator") public String getCreator(){
		return creator;
	}
	@OneToMany(cascade=CascadeType.ALL , mappedBy="creator" , 
		fetch=FetchType.EAGER) 
		public List<Name> getNames(){
		return names;
	}

	private void setNumber(int n){
		number = n;
	}
	public void setURL(String u){
		url = u;
	}
	public void setCreator(String c){
		creator = c;
	}
	public void setNames(List<Name> names){
		this.names = names;
	}

	public String toString(){
		return creator;
	}
}

// Element.java
package com.yamicha.toplink.characters;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_element" , schema="yamicha") 
	public class Element{
	private int number;
	private String element;
	private List<Name> names;

	public Element(){
		names = new ArrayList<Name>();
	}
	public Element(int number , String element){
		this();
		this.number = number;
		this.element = element;
	}
	public Element(List<Name> names , int number , String element){
		this();
		this.number = number;
		this.element = element;
		this.names = names;
	}

	@Id @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="element") public String getElement(){
		return element;
	}
	@ManyToMany(fetch=FetchType.EAGER) 
		@JoinTable(name="persist_name_element" , 
		joinColumns=@JoinColumn(name="element") , 
		inverseJoinColumns=@JoinColumn(name="name")) 
		public List<Name> getNames(){
		return names;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setElement(String e){
		element = e;
	}
	public void setNames(List<Name> n){
		names = n;
	}

	public String toString(){
		return element;
	}
}

// Name.java
package com.yamicha.toplink.characters;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_name" , schema="yamicha") 
	public class Name{
	private int number;
	private String name;
	private Creator creator;
	private List<Element> elements;
	private Prefix prefix;
	private Sex sex;

	public Name(){
		elements = new ArrayList<Element>();
	}
	public Name(Creator creator , String name){
		this();
		this.creator = creator;
		this.name = name;
	}

	@Id @GeneratedValue(strategy=GenerationType.SEQUENCE) 
		@Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@ManyToOne(fetch=FetchType.EAGER) 
		@JoinColumn(name="creator") 
		public Creator getCreator(){
		return creator;
	}
	@ManyToMany(cascade=CascadeType.ALL , fetch=FetchType.EAGER , 
		mappedBy="names") @OrderBy("number") 
		public List<Element> getElements(){
		return elements;
	}
	@OneToOne(cascade=CascadeType.ALL , fetch=FetchType.EAGER , 
		mappedBy="name") 
		@PrimaryKeyJoinColumn(name="number" , 
		referencedColumnName="number") 
		public Prefix getPrefix(){
		return prefix;
	}
	@OneToOne(cascade=CascadeType.ALL , fetch=FetchType.EAGER ,
		 mappedBy="name") 
		@PrimaryKeyJoinColumn(name="number" , 
		referencedColumnName="number") 
		public Sex getSex(){
		return sex;
	}

	private void setNumber(int number){
		this.number = number;
	}
	public void setName(String name){
		this.name = name;
	}
	public void setCreator(Creator c){
		creator = c;
	}
	public void setElements(List<Element> e){
		elements = e;
	}
	public void setPrefix(Prefix p){
		prefix = p;
	}
	public void setSex(Sex s){
		sex = s;
	}

	public String toString(){
		return name;
	}
}

// Prefix.java
package com.yamicha.toplink.characters;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_prefix" , schema="yamicha") 
	public class Prefix{
	private int number;
	private String prefix;
	private Name name;

	public Prefix(){
	}
	public Prefix(Name name , String prefix){
		this.prefix = prefix;
		this.name = name;
		number = name.getNumber();
	}

	@Id @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="prefix") public String getPrefix(){
		return prefix;
	}
	@OneToOne(fetch=FetchType.EAGER) 
		@PrimaryKeyJoinColumn(name="number" , 
		referencedColumnName="number") 
		public Name getName(){
		return name;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setPrefix(String p){
		prefix = p;
	}
	public void setName(Name n){
		name = n;
	}

	public String toString(){
		return prefix;
	}
}

// Sex.java
package com.yamicha.toplink.characters;

import javax.persistence.*;

@Entity @Table(name="persist_sex" , schema="yamicha") 
	public class Sex{
	private int number;
	private Name name;
	private SexList sex;
	public Sex(){
	}
	public Sex(Name name , SexList sex){
		number = name.getNumber();
		this.name = name;
		this.sex = sex;
	}

	@Id @Column(name="number") public int getNumber(){
		return number;
	}
	@Column(name="sex") @Enumerated(EnumType.ORDINAL) 
		public SexList getSex(){
		return sex;
	}
	@OneToOne(fetch=FetchType.EAGER) 
		@PrimaryKeyJoinColumn(name="number" , 
		referencedColumnName="number")
		public Name getName(){
		return name;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setSex(SexList sl){
		sex = sl;
	}
	public void setName(Name n){
		name = n;
	}

	public String toString(){
		return sex.getLabel();
	}
}

// SexList.java
package com.yamicha.toplink.characters;

public enum SexList{
	MALE("男性") , FEMALE("女性") , NEWTRAL("中性");

	private String label;
	private SexList(String label){
		this.label = label;
	}
	public String getLabel(){
		return label;
	}
}
 代わり映えしませんが、ソースです。これをまじめにServlet内で記述してコンパイルするより、どこか別の場所で作っておいて、動作テストをした上で配置した方が楽でしょう(私も実際にそうしています)。別の場所でコンパイルしておいて、クラスファイルのみを投げ入れるのでも構いません。
 それでは今回の主要部、persist.jspに参りましょう。
<%@ page import="javax.servlet.http.* , javax.servlet.* , java.util.* , 
javax.persistence.* , com.yamicha.toplink.characters.* , 
static com.yamicha.toplink.characters.SexList.*"
contentType="text/html;charset=Shift_JIS" %>

<html>
<head>
<title>Java Persistence JSP</title>
</head>

<body>

<%
EntityManagerFactory factory = 
	Persistence.createEntityManagerFactory("persist");
EntityManager em = factory.createEntityManager();

EntityTransaction et = em.getTransaction();

try{
et.begin();

// 繰り返し実行できるようにするため、まずデータを削除
List<Creator> creatorlist = (List<Creator>)
	em.createQuery("SELECT c FROM Creator c").getResultList();
for(Creator c : creatorlist)
	em.remove(c);

em.flush();

Creator creators[] = {
	new Creator("http://www.yamicha.com/" , "yamicha.com") , 
	new Creator(null , "hisame") , 
	new Creator("http://sousya.umu.cc/" , "ruva") , 
	new Creator(null , "coolmint") , 
	new Creator(null , "Shou")
};

for(Creator c : creators)
	em.persist(c);

Name names[] = {
	new Name(creators[0] , "イリアス") ,
	new Name(creators[0] , "サーラ") ,
	new Name(creators[0] , "シェイン") ,
	new Name(creators[1] , "うなぎ") ,
	new Name(creators[1] , "ネコ") ,
	new Name(creators[1] , "クロウ") ,
	new Name(creators[2] , "ルヴァ") ,
	new Name(creators[2] , "リサ") ,
	new Name(creators[2] , "ファルシア") ,
	new Name(creators[3] , "クルード") ,
	new Name(creators[3] , "フィオーナ") ,
	new Name(creators[4] , "アルヤ") ,
	new Name(creators[4] , "ルナン") ,
	new Name(creators[4] , "レイシス") ,
	new Name(creators[0] , "ハードゥン") ,
	new Name(creators[3] , "セシール") ,
	new Name(creators[4] , "セイル")
};

for(Name n : names)
	em.persist(n);

em.flush();

Element elements[] = {
	new Element(
		new ArrayList<Name>(Arrays.asList(new Name[]{
		names[0] , names[6] , names[11] , names[12] , 
		names[13] , names[15]})) , 1 , "火") ,
	new Element(
		new ArrayList<Name>(Arrays.asList(new Name[]{
		names[0] , names[3] , names[6] , names[10]})) , 2 , "水") ,
	new Element(
		new ArrayList<Name>(Arrays.asList(new Name[]{
		names[0] , names[3] , names[6] , names[11] , 
		names[12] , names[13]})) , 3 , "雷") ,
	new Element(
		new ArrayList<Name>(Arrays.asList(new Name[]{
		names[0] , names[4] , names[6] , names[12] , names[13]})) 
		, 4 , "土") ,
	new Element(
		new ArrayList<Name>(Arrays.asList(new Name[]{
		names[0] , names[5] , names[6] , names[10] , 
		names[12] , names[13]})) , 5 , "風") ,
	new Element(
		new ArrayList<Name>(Arrays.asList(new Name[]{
		names[0] , names[2] , names[6] , names[8] , 
		names[11] , names[14]})) , 6 , "回復") ,
	new Element(
		new ArrayList<Name>(Arrays.asList(new Name[]{
		names[0] , names[2] , names[6]})) , 7 , "聖") ,
	new Element(
		new ArrayList<Name>(Arrays.asList(new Name[]{
		names[0] , names[5]})) , 8 , "暗") ,
	new Element(
		new ArrayList<Name>(Arrays.asList(new Name[]{
		names[0]})) , 9 , "RDBMS")
};

for(Element e : elements)
	em.persist(e);

String prefixes[] = {
	"魔道士" , "騎士" , "聖騎士" , "デビル" , "デビル" ,
	"デビル" , "司祭" , "僧侶" , "聖騎士" , "剣士" ,
	"風術師" , null , null , null , "剣聖" , null , "勇者"
};

for(int i = 0; i < prefixes.length; i++)
	em.persist(new Prefix(names[i] , prefixes[i]));

SexList sexes[] = {
	FEMALE , NEWTRAL , MALE , FEMALE , FEMALE , FEMALE ,
	FEMALE , FEMALE , MALE , MALE , FEMALE , MALE ,
	FEMALE , FEMALE , MALE , FEMALE , MALE
};

for(int i = 0; i < sexes.length; i++)
	em.persist(new Sex(names[i] , sexes[i]));

et.commit();

for(Creator c : creators)
	em.refresh(c);
}catch(Exception e){
	out.println(e);
}

try{
// 作者ごとの属性一覧
out.println("<b>クリエイターごとの属性使用数</b>");

List<Creator> creators = (List<Creator>)em.createQuery(
	"SELECT c FROM Creator c").getResultList();

%>
<table border="1" cellspacing="0">
<tr><td bgcolor="#CCDDFF">Creator</td>
<td bgcolor="#CCDDFF">Element</td>
<td bgcolor="#CCDDFF">Value</td></tr>
<%

Query q_element = em.createQuery(
	"SELECT e.element , COUNT(e) FROM Element e " + 
	"JOIN e.names n JOIN n.creator c WHERE c.number = :number " + 
	"GROUP BY e.element ORDER BY e.number");
for(Creator c : creators){
	q_element.setParameter("number" , c.getNumber());
	List<Object[]> l = (List<Object[]>)
		q_element.getResultList();

	// クリエイター情報
	out.println("<tr><td valign=\"top\" rowspan=\"" + 
		(l.size() + 1) + "\">");
	out.println("<b>" + c.getCreator() + "</b>");
	if(c.getURL() != null)
		out.println("<br>" + c.getURL());
	out.println("<br>Character(" + c.getNames().size() + ")");
	out.println("</td>");

	// 属性一覧
	out.println("<td bgcolor=\"#DDDDDD\"><b>Kind</b>");
	out.println("</td><td bgcolor=\"#DDDDDD\">" + 
		l.size() + "</td></tr>");

	for(Object e[] : l){
		out.println("<tr>");
		out.println("<td>" + e[0] + "</td>");
		out.println("<td>" + e[1] + "</td>");
		out.println("</tr>");
	}
}
out.println("</table><br>");

// キャラクター一覧
%>
<b>キャラクター一覧</b>
<table border="1" cellspacing="0">
<tr><td bgcolor="#CCDDFF">Creator</td>
<td bgcolor="#CCDDFF">Prefix</td>
<td bgcolor="#CCDDFF">Name</td>
<td bgcolor="#CCDDFF">Sex</td>
<td bgcolor="#CCDDFF">Element</td></tr>
<%

for(Creator c : creators){
	for(Name n : c.getNames()){
		out.println("<tr>");
		out.println("<td valign=\"top\">" + 
			c.getCreator() + "</td>");
		out.println("<td valign=\"top\">" + 
			(n.getPrefix().getPrefix() != null ? 
			n.getPrefix().getPrefix() : "-") + "</td>");
		out.println("<td valign=\"top\">" + 
			n.getName() + "</td>");
		out.println("<td valign=\"top\">" + 
			n.getSex().getSex().getLabel() + "</td>");

		List<Element> elements = n.getElements();
		out.println("<td><b>Elements(" + elements.size() + 
			")</b>");
		for(Element e : elements)
			out.println("<br>" + e.getElement());
		out.println("</td>");

		out.println("</tr>");
	}
}
out.println("</table><br>");

// エレメントごとのキャラクター
out.println("<b>特定のエレメントを持つキャラクター一覧</b>");
List<Element> elements = (List<Element>)em.createQuery(
	"SELECT e FROM Element e").getResultList();
out.println("<table border=\"1\" cellspacing=\"0\">");
out.println("<tr><td bgcolor=\"#CCDDFF\">Element</td>");
out.println("<td bgcolor=\"#CCDDFF\">Name</td></tr>");
for(Element e : elements){
	List<Name> names = e.getNames();
	out.println("<tr>");
	out.println("<td valign=\"top\">" + 
		e.getElement() + "</td>");
	out.println("<td><b>Characters(" + 
		names.size() + ")</b>");
	for(Name n : names){
		out.println("<br>" + 
			(n.getPrefix().getPrefix() != null ? 
			n.getPrefix().getPrefix() : "") + n);
	}
	out.println("</td></tr>");
}
out.println("</table>");
}catch(Exception e){
	out.println(e);
}

em.close();
factory.close();
%>

</body>
</html>
 登録しているデータはこの前と同じです。紛れもなく同じです。なに、増えている?まさか。人間がそうそう簡単に増えるわけがないでしょう。以前に登録したデータでは14人でしたが、今回も14人に決まって
SELECT COUNT(*) FROM persist_name;

17
 いません。
 いい加減クリエイターの方からクレームが来そうで恐ろしいですが。いくらSQL編での使用許諾をもらっているとはいえ、これはもはや「目的外利用」に当たるわけですし、本来適切ではなさそうです。しかし、データが以前と全く同じでは問題ですし、かといって自分のキャラのみを野放図に登録すると他のクリエイター様の登録キャラ数とのバランスが取れなくなってしまいます。私のキャラだけ7件8件、他の人のキャラは2件3件では、相手を尊重していないといいましょうか。
 出力結果は次の通り。

クリエイターごとの属性使用数
Creator Element Value
Shou
Character(4)
Kind5
3
3
2
2
回復 1
yamicha.com
http://www.yamicha.com/
Character(4)
Kind9
1
1
1
1
1
回復 3
2
1
RDBMS 1
hisame
Character(3)
Kind5
1
1
1
1
1
coolmint
Character(3)
Kind3
1
1
1
ruva
http://sousya.umu.cc/
Character(3)
Kind7
1
1
1
1
1
回復 2
1

キャラクター一覧
Creator Prefix Name Sex Element
Shou - アルヤ 男性 Elements(3)


回復
Shou 勇者 セイル 男性 Elements(0)
Shou - ルナン 女性 Elements(4)



Shou - レイシス 女性 Elements(4)



yamicha.com 騎士 サーラ 中性 Elements(0)
yamicha.com 剣聖 ハードゥン 男性 Elements(1)
回復
yamicha.com 聖騎士 シェイン 男性 Elements(2)
回復
yamicha.com 魔道士 イリアス 女性 Elements(9)





回復


RDBMS
hisame デビル クロウ 女性 Elements(2)

hisame デビル ネコ 女性 Elements(1)
hisame デビル うなぎ 女性 Elements(2)

coolmint 剣士 クルード 男性 Elements(0)
coolmint - セシール 女性 Elements(1)
coolmint 風術師 フィオーナ 女性 Elements(2)

ruva 司祭 ルヴァ 女性 Elements(7)





回復
ruva 僧侶 リサ 女性 Elements(0)
ruva 聖騎士 ファルシア 男性 Elements(1)
回復

特定のエレメントを持つキャラクター一覧
ElementName
Characters(6)
司祭ルヴァ
アルヤ
ルナン
レイシス
セシール
魔道士イリアス
Characters(4)
司祭ルヴァ
デビルうなぎ
風術師フィオーナ
魔道士イリアス
Characters(6)
司祭ルヴァ
アルヤ
ルナン
レイシス
デビルうなぎ
魔道士イリアス
Characters(5)
司祭ルヴァ
ルナン
デビルネコ
レイシス
魔道士イリアス
Characters(6)
司祭ルヴァ
デビルクロウ
ルナン
レイシス
風術師フィオーナ
魔道士イリアス
回復 Characters(6)
司祭ルヴァ
剣聖ハードゥン
アルヤ
聖騎士シェイン
聖騎士ファルシア
魔道士イリアス
Characters(3)
司祭ルヴァ
聖騎士シェイン
魔道士イリアス
Characters(2)
デビルクロウ
魔道士イリアス
RDBMS Characters(1)
魔道士イリアス


 この通り、Tomcat JSPでもPersistenceが使えてしまいました。
 属性ごとの人数がおおむね同等になっているのが興味深いです。その他のデータとしては、
SELECT sex , COUNT(*) FROM persist_sex GROUP BY sex;

sex	COUNT(*)
0	6
1	10
2	1
 なぜか女性比率が多いです。余談ながら、支持率の統計などで「男性層の過半数が不支持に回りましたが、女性層の多くが支持しています」のような解説がなされることがありますが、これは実際のところエセ統計学なのでは。何といってもサンプルが2つしかないのですから。いわばテレビの「被験者が2人だけの実験」とほとんど変わりません(実際の統計学で仮説を証明するには100程度のサンプルは欲しいところ)。
 ついでに今回のソースでは、JSPでスタティックインポートを行っています。本当にできるのか自信ありませんでしたが、できてしまうものです。JSPの先頭部分にある「static com.yamicha.toplink.character.SexList.*」がそれで、これのおかげでFEMALEやらNEWTRALやらと書くだけで動くようになっています。
 その他、例えばCascadeTypeを多用するような場合は、「import static javax.persistence.CascadeType.*;」を記述しておくことで「PERSIST」や「ALL」だけでCascadeTypeを指定できるようになります。enum型の登場でstatic変数を使う機会も増えましたから、(特に「JSPでも使えてしまう」ことは)覚えておいて損はなさそうです。
 後は久々にEJBでも扱ってみたいところですが、あの重さを考えると。とはいえEntityはもともとEJBの一部ですし、どうしたものでしょうか。
カテゴリ [開発魔法][社会問題][ゲスト出演] [トラックバック 0][コメント 0]

教育解欠く
2007/01/19(Fri)21:31:01
 何かもうドタバタ喜劇にしか見えなくなってきた教育改革。まさか世間の人々に笑いを提供し、参院選で支持してもらおうとでも考えているのでしょうか。笑いを提供するのは結構ですが、何も100年の計である教育をネタに使わなくても。
 面白いことに、最近は「いじめ禁止を校則にする」などと言い出しています。そんなものがいじめに効果があるというなら、今ごろこの世にいじめなど存在しないのです。もう少し頭を使えないのでしょうか。
 いじめといえば、いじめ加害者の出席停止などの案が議論されたこともありました。教育再生会議の連中はこうした方法を認める気なのかもしれませんが、実に愚かです。いじめ行為は犯罪行為であり、的確に出席停止を下すことは被害者を守ることにもつながるのですが、それは理想論というものです。実際に出席停止処分を下すなど、何か事件が起こった後でもない限りまず不可能です。
 仮にどこかの学校でいじめが発覚したとしましょう。加害者は出席停止になってもおかしくないのですが、このいじめは多くの人が知っていながら見て見ぬ振りをしていました。さて、「見て見ぬ振り」は無罪でしょうか。これが無罪とすれば、「シカト」と呼ばれる集団無視行為は処分対象にならないのでしょうか(以前の教育再生会議案によれば「見て見ぬ振り」もいじめだそうですが)。
 さらに、集団によるいじめの場合、加害者のうちある人は出席停止、またある人は出席停止にしないとなれば、不公平が生じることは否めません。シカト行為や見て見ぬ振りに対して処分を下す場合はそれがより顕著になります。シカトなどはほぼ全員が加害者になるわけですが、一体誰を出席停止にするのでしょう。全員を出席停止にして学級閉鎖状態にするか、罪の重さは全員ほぼ同等なのに一部のみを出席停止にするか、さもなくば加害者が多いからといって無罪にするか。
 また、いじめには流動的な側面があります。被害者が加害者になったりすることもあるのです。例えば、ここに長年いじめられ続けている被害者Aがいるとしましょう。学校に相談してものれんに腕押し、まるで効果がありません。しかし、ここへ来ていじめ加害者Bが弱い立場に立たされたのです。今までの恨みか、こちらからやらなければいじめられ続けると見たか、現在までの過酷ないじめで精神的に参っていたAはBをいじめ始めました。しかし、それからほどなくいじめが発覚したのです。AからBへの加害行為はすべて発覚しましたが、「Aは以前、執拗にBからいじめられていた」事実については「加害者の主張」ということで信用されなかったり、証拠がなかったりして証明できませんでした。
 過去には、加害者が被害者に命じて授業を混乱させるなどの妨害行為を行わせ、加害者がそれを鎮める演技をすることで、加害者を模範生、被害者をワルであると教師らに印象付けるようないじめもあったそうです。この場合、被害者が「授業妨害」などとして処分を受ける可能性はあっても、加害者は安泰です。被害者が「加害者に命令された」と告白しても、教師はそれを信用しないでしょう。こうしたケースの場合、誰が出席停止となるでしょうか。こうしたことから、出席停止処分などを盛り込むのは理想論であると分かります。
 ちなみに、教育再生会議案によれば、いじめ加害者には「いじめを人間として恥ずべき愚かな行為だと認識させる」そうです。上記のような例においては、おそらくとんちんかんなことになってしまうでしょう。現場を見ておらず、さらに現場を理解しようとしなければ、こういう案をひねり出せるのですか。
 教師免許更新制にはこれまで個人的に賛成でしたが、安倍内閣による更新制には反対しなければなりません。子どものためになる教育を行う教師は評価し、ダメ教師を排除するのであれば、免許更新制は導入されても良いでしょう。しかし、安倍内閣主導の教育を荒廃させるとしか考えられない案を見ていると、「君が代を生徒に強制しない教師にはやめていただく」であったり「内閣の政策に反発したからやめていただく」のような使い方がなされる可能性が非常に高いです。
 それでなくても政府はゆとりと学力重視をコロコロ変えて現場を大混乱させているのです。国旗国歌については答弁で「強制しない」と語られているにもかかわらず、現実はこれです。政府のこうしたバカげた案に対して「そんなものは子どものためにならない」と反論する「真の教師」がまず犠牲になるのです。
 逆に、生徒に何が何でも国歌を歌わせたり、地毛が黒でない人の髪を強引に染めたりするなど、人権侵害もいとわぬ教師が評価されることにもなりかねません。私は教師免許更新制やダメ教師の排除には賛成ですが、安倍内閣のそれには絶対反対です。
 そしてさらに許せないのが「体罰の一部解禁」です。はっきり言っておきますが、現状でも体罰はモグリで散々行われています。それでも体罰禁止の規定は「ケガを負わせて騒ぎになるほどの体罰はまずい」という意識を生み、ひどい体罰をまがりなりにも食い止めています。これのタガを外すとは、気が狂っているとしか考えられません。
 以前にはヨットスクール事件などもありました。「教育か体罰か」で争われたこの事件ですが、概要を調べてみると見れば見るほど教育の名をかたった虐待行為であることが分かります。凶器はかなり殺傷力の高いものが用いられており、また衰弱した人間をいたぶったり、十分な治療を受けさせないなど、もし「教育」などという大義名分がなければ、場合によっては未必の故意が認定されていたかもしれません。石原氏などは加害者を支持していますが、実際にこの事件を可能な限り客観的に想像してみると、かなり凄惨であると言わざるを得ません。
 とはいえ、世の中には「正当防衛的」な体罰もあります。反省のないいじめ加害者への体罰など、一方的に責められないものもあるでしょう。これを「大した理由もなく振るわれる不当体罰」と混同するから意味が分からなくなるのであって、一律に「体罰を行った教師はこの処分」とするのがおかしいのです。
 しかし、体罰を容認するような文言は絶対に含めるべきではありません。世間で問題となる体罰は氷山の一角です。世間で問題になるような大きな体罰の裏には、報道されないほど軽い処分しかなされない体罰が多数存在し、学校や教育委員会にもみ消されて葬り去られた体罰がさらに多数存在し、そもそも全く学校なり親なりが把握しない体罰が星の数ほど存在することを忘れてはいけません。そして、それらの体罰のうち少なからぬものが「正当な理由もなく振るわれる体罰」なのです。
 体罰の制限が軽くなれば、こうした教師が喜ぶだけです。出席停止にしても、世の中には絶対「お前は生意気だから出席停止にしてやろうか」などといった使い方をする教師が1人2人は現れるものなのです。
 さらには「ゆとり脱却」だそうですが、何をバカなことを。学力重視とゆとり教育を何年刻みかでコロコロ変更するなどとは、現場(特に児童・生徒)をバカにしているとしか考えられません。そもそも結果が出るのは数十年後というのに。
 私に言わせれば、これは「ずっと東に理想の教育があり、それを目指してとりあえず北へ出航。寒くなってきたので南に針路変更。今度は暑くなってきたので北へ向かおう」と言っているようなものです。永久に理想の教育に近づくことはできません。針路変更自体が単なる気まぐれであり、しかも今回の針路変更の理由はといえば、船員(国民)の一部が「暑い。ガマンできない」と言い出したからなのです。
 教育バウチャーは今後の検討課題だとか。大学9月入学化とセットの奉仕活動も同様でしょう。バウチャーと学校選択制、さらに学力テストによって学校の戦いが泥沼化し、知識・情緒育成を完全無視して「テストの点を良くするための勉強」を重ねるなどした挙句、無償奉仕活動のせいで「自己利益優先」「他人など無関係」の「堀江人間」を量産しかねない最悪の戦略です。もう安倍内閣には教育に指一本触れて欲しくありません

 しかし、「最近の学生はレベルが低い」というのはおおむね間違っていません。正直なところ、これはゆとり以前からの現象という気がしています。「自民党以外の政党を知らない」「日本が米国と戦争したのを知らない」辺りは極端な例ですが、以前に成人式の話題を書いた通り、少なからぬ学生は向上心がゼロに近いです。自分を向上させる気概もなければ手段もなく、試験勉強で得た知識は終了後に全部忘れると来ています。これではどれほど高度な教育を行っても意味がありません。
 ではどうすれば良いのか。悲しいかな、私はどうも彼らの考えが理解できないため、何とも言いようがありません。私にとっては、彼らが「向上しない自分を嫌悪」しないらしいことが不思議でならないのですから。とにかく、あれが「郵政って分からないけど、小泉さんカッコイイから自民にします」とか称する若者やら、料亭に行きたいタイゾー君やらになるのかと、妙に納得した次第です。
 で、私がそれを目の当たりにした後、絶対に自分を磨くことだけは怠るまいと誓ったのは言うまでもありません。幸い、世界にはバカと同じ数ほど目指すべき人もいるのですから。

 無賃残業合法化制度について、経団連の連中が「残業ゼロ法案と名づけられた時点でダメだった」と言い出しました。すなわち名前さえ違っていれば成立できていただろうと言っているのです。あまりに国民をバカにしています。が、「バカにするな」と言えないのが辛いところです。なぜなら少なからぬ国民は実際そうなのですから
 今回、マスコミは頻繁に「残業代ゼロ法案」の名を用いていました。これは「ホワイトカラー・エグゼンプション」では見出しにしては長すぎ、直訳の「事務職除外」「適用除外」では意味が伝わらないため、「残業代ゼロ」を通称にしていたものと考えられます。仮にもっと短い通称が存在すれば、成立した可能性は少なからず存在します。
 こうなったからには、仮に自民党が参院選で勝ちでもすれば、違う名前を用いて同じものを出してくるでしょう。そして少なからぬ国民は、これらが同じものであると見抜くことができないのです。「ゆとり教育」よりも大きな問題が日本に横たわっているのが分かるでしょう。
 しかし、マスコミが良く用いていた「残業代ゼロ法案」は少々ニュアンスが違う上に意味も弱いです。「残業代ゼロ」では意味が良く伝わりませんし、いわゆるサービス残業のようなものと混同したりと混乱を招きます。こうしたことから、当ブログでは同制度の日本語訳として「無賃残業合法化制度」を当ててきました。「残業代ゼロ法案」よりも様態を的確に表した語です。
 ついでに「敵を知り、己を知れば」ということで、この制度が断念された理由を経団連らの立場からも分析してみましょう。まず「ホワイトカラー〜」というネーミング自体は成功でした。日本では労働者の色分けなど定着しているとは言いがたく、「ホワイトカラー」という言葉から連想されるのは純白や潔白などのプラスイメージのものばかりです。
 マスコミの多くは「white-color exemption」に「ホワイトカラー・エグゼンプション」のカタカナを当てていましたが、この「エグゼンプション」にしても語感は良いです。スペルはかなり違うのに、カタカナでは「エグゼクティブ」「エグゼキューター」といった言葉にも似ています。このように、言葉のイメージは良いのですから、この時点では制度が成立に向けて有利に進むと踏んでいたのでしょう。
 しかし、問題は正式名称があまりに長すぎることです。「エアー・コンディショナー」を「エアコン」と略すようなタイプの略し方はなく、かといって直訳では「白色除外」のような意味不明な言葉になるためとても使えません。マスコミが良く用いていたのは「適用除外」でしたが、やはり意味不明です。
 新聞という旧時代的メディアにおいては、見出しの文字数が多すぎたり、意味不明になるのは致命的です。そこで、内容を簡潔に表す言葉を作る必要に迫られ、生まれたのが「残業代ゼロ法案」や「残業代不払い法案」というわけです。余談ながら、昔は「ホップ・ステップ・ジャンプ」にも適切な翻訳語がなく、各社とも珍妙な略語を使うなどして苦労していたようですが、いつしか「三段跳び」という訳が生まれ、これが適切すぎて定着したようです。いわば、これと同じ現象が発生したのです。
 ですから、もし経団連などこの制度の導入をもくろむ人間が、「日本版ホワイトカラー・エグゼンプション制度」という正式名称と同時に、制度の概要を簡潔に表す上にマイナスイメージのない短い言葉を定め、これをマスコミが多用していたら、きっとこの制度は成立してしまっていたでしょう。せめて経団連などの連中が次にこの戦略を使ってきた時には絶対だまされないようにしたいものです。
 もちろん当ブログでは、連中が今後似たような制度を「美しい」略称付きで出してきたとしても、遠慮なく「無賃残業合法化制度」と呼んで差し上げます。

 複合キー。ああ、何と美しい響きでしょう(どこがと問われると困りますが)。どういうものかといいますと、
Table : type
PRIMARY(number)
number	name
1	資産
2	負債
...

Table : category
PRIMARY KEY(type , number) 
FOREIGN KEY(type) REFERENCES type(number)
type	number	name
1	1	現金
1	2	当座預金
1	3	受取手形
2	1	当座借越
2	2	買掛金
2	3	社債
2	4	支払手形
...
 こういうものです。普通、PRIMARY KEYされたカラムは同じ値を持つことができないのですが、複合キーの場合は「複合キーに指定されたカラムすべての値が一致」する場合でなければエラーになりません。意味はおおむね見ての通りで、この場合には「負債の勘定における登録番号2番の勘定は買掛金である」と表現することができます。
 複合キーはこれで結構便利ですので、私もたびたび使用しています。その名の通り複合キーにはインデックスが作られるのですが、このインデックスは特殊であり、例えば「PRIMARY KEY(k1 , k2)」とした場合、k2単体をキーにしてデータを探すなどした場合にはインデックスは使われません。k1だけ、またはk1とk2の両方を使ってデータを探した場合のみインデックスが使われます。
 私のつたない経験上、この場合にインデックスを用いない検索が発生する可能性が存在し、k2に対しても独立したインデックスを作らなければならないような場合は、複合キーを使用すべきではありません。そうでない場合には結構便利ではないでしょうか。ただ単にデータを登録するだけであれば複合キーである必要はありませんが、例えば上記のように勘定の種類ごとに番号を当てており、「資産の勘定のみを取得したい」のようなニーズが存在する場合には、複合キーの利便性は高いです。場合によっては複合キーにせず、PRIMARY KEYは通し番号に使い、別のインデックスカラムを使ってリレーする方法も考えられるべきですが(AUTO INCREMENTを使いたい時はこちらでしょうし)。
 ちなみに今回の場合、このcategoryテーブルのデータとリレーするようなテーブルを作ると、カラムを2つ持たせなければならないデメリットがあります。しかしながら、これも時として便利な場合があり、
Table : books
PRIMARY KEY(number)
number	type	category	price	description
1	1	2	90000	サーラストラップ@900 100個を小切手で仕入れ
2	2	2	78000	イリアスストラップ@780 100個を掛け仕入れ
3	2	4	42000	シェリーの経済概論@4200 10冊を約手で仕入れ
 この場合はこのテーブル自体がtypeカラムを持っているため、これらの仕入れにおける貸方の種類が一目瞭然です。無論、複合キーを使わない場合でも種類を特定することは可能ですが、booksからcategoryにリレーし、categoryからtypeにリレーすることになります。
 Persistence APIでは複合キーも使えるようです。今回はあまり複合キーを使うべき例はありませんが、せっかく使えるなら試してみないことには。複合キーを使う場合、まずキーを保持するクラスが必要のようです。本当に絶対必要かどうかは不明ですが、複合キーのために2つも3つも変数を作るよりはこの方が楽でしょう。
 今回は「Name」クラスを複合キーとしてみます。クリエイターとキャラ番号の複合です。それにしても非効率的な構造ですが、そこは言わないお約束です。
 それから、もう1つ「@Enumerated」なる面白そうなものを見つけましたから、こちらも試してみるとしましょう。何でもenum型をマップするものだそうで。良く意味が分かりませんが、使ってみれば分かるでしょう。ということで、まずはenum型から。今回からソースは「com\yamicha\toplink\characters」に格納し、当然パッケージも「com.yamicha.toplink.characters」になっています。
// SexList.java
package com.yamicha.toplink.characters;

enum SexList{
	MALE("男性") , FEMALE("女性") , NEWTRAL("中性");

	private String label;
	private SexList(String label){
		this.label = label;
	}
	public String getLabel(){
		return label;
	}
}
 enum型はいわゆる定数リストということで、何にしようか迷いましたが、無難に「性別」にしてみました。性別であれば男性か女性しかない、という固定観念は、サーラさんが見事に打ち破ってくださるのではありますが。
 そういうわけで「中性」も用意しました。しかし、現実に性別不明の人が存在する場合には、「中性」にするよりNULLにすべきでしょう。なお、enum自体には何のアノテーションも必要ないようです。
 他のクラスも見ていきましょう。
// NameKey.java
package com.yamicha.toplink.characters;

import javax.persistence.*;

@Embeddable public class NameKey implements Cloneable{
	private int creator;
	private int number;

	public NameKey(){
	}
	public NameKey(int c , int n){
		creator = c;
		number = n;
	}

	@Id @Column(name="creator") 
		public int getCreator(){
		return creator;
	}
	@Id @Column(name="number")
		public int getNumber(){
		return number;
	}

	public void setCreator(int c){
		creator = c;
	}
	public void setNumber(int n){
		number = n;
	}

	public String toString(){
		return "[" + creator + ":" + number + "]";
	}
	public Object clone(){
		try{
			return super.clone();
		}catch(CloneNotSupportedException e){
			return null;
		}
	}
}

// Name.java
package com.yamicha.toplink.characters;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_name" , schema="yamicha") 
	public class Name{
	private NameKey namekey;
	private String name;
	private Creator creator;
	private List<Element> elements;
	private Prefix prefix;
	private Sex sex;

	public Name(){
		namekey = new NameKey();
		elements = new ArrayList<Element>();
	}
	public Name(int number , Creator creator , String name){
		this();
		this.creator = creator;
		this.name = name;
		namekey.setCreator(creator.getNumber());
		namekey.setNumber(number);
	}

	@EmbeddedId public NameKey getNamekey(){
		return namekey;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@ManyToOne(fetch=FetchType.EAGER) 
		@JoinColumn(name="creator" , insertable=false , 
		updatable=false) 
		public Creator getCreator(){
		return creator;
	}
	@ManyToMany(fetch=FetchType.EAGER , mappedBy="names") 
		@OrderBy("number") 
		public List<Element> getElements(){
		return elements;
	}
	@OneToOne(cascade=CascadeType.ALL , fetch=FetchType.EAGER , 
		mappedBy="name") 
		@PrimaryKeyJoinColumn(name="number" , 
		referencedColumnName="number") 
		public Prefix getPrefix(){
		return prefix;
	}
	@OneToOne(cascade=CascadeType.ALL , fetch=FetchType.EAGER , 
		mappedBy="name") 
		@PrimaryKeyJoinColumn(name="number" , 
		referencedColumnName="number") 
		public Sex getSex(){
		return sex;
	}

	public void setNamekey(NameKey k){
		namekey = k;
	}
	public void setName(String name){
		this.name = name;
	}
	public void setCreator(Creator c){
		creator = c;
	}
	public void setElements(List<Element> e){
		elements = e;
	}
	public void setPrefix(Prefix p){
		prefix = p;
	}
	public void setSex(Sex s){
		sex = s;
	}

	public String toString(){
		return name;
	}
}
 今回は「NameKey.java」がプライマリキーを持っています。後はその型のフィールドまたはgetter/setterメソッドに「@EmbeddedId」アノテーションをつけます。これでNameKey.javaのnumber及びcreatorがPRIMARY KEYであるとみなされます。
 ポイントはgetCreatorメソッドの「@JoinColumn(name="creator" , insertable=false , updatable=false) 」アノテーションです。NameKey.javaではcreatorが@Idに登録されています。しかし、getCreatorメソッドのJoinColumnでも同じ名前のカラムにリンクしようとしています。これを違う名前にした場合、同じ値のカラムが2つ存在することになってしまいます。では一体どうしましょうか。insertable及びupdatableをfalseに設定し、こちら側からは更新を行わないことを明示する必要があるのです。今回に限らず複数の変数が同じカラムを参照している場合、insertable/updatableをtrueにできるのは1つだけです。他ではこれらを明示的にfalseに指定してやる必要があります。
 そして、今回のもう1つの目玉である@Enumeratedを使ったクラスがこちらです。
// Sex.java
package com.yamicha.toplink.characters;

import javax.persistence.*;

@Entity @Table(name="persist_sex" , schema="yamicha") 
	public class Sex{
	private NameKey namekey;
	private Name name;
	private SexList sex;
	public Sex(){
		namekey = new NameKey();
	}
	public Sex(Name name , SexList sex){
		namekey = (NameKey)name.getNamekey().clone();
		this.name = name;
		this.sex = sex;
	}

	@EmbeddedId public NameKey getNamekey(){
		return namekey;
	}
	@Column(name="sex") @Enumerated(EnumType.ORDINAL) 
		public SexList getSex(){
		return sex;
	}
	@OneToOne(fetch=FetchType.EAGER) 
		@PrimaryKeyJoinColumns({
			@PrimaryKeyJoinColumn(name="creator" , 
			referencedColumnName="creator") ,
			@PrimaryKeyJoinColumn(name="number" , 
			referencedColumnName="number")
		})
		public Name getName(){
		return name;
	}

	public void setNamekey(NameKey k){
		namekey = k;
	}
	public void setSex(SexList sl){
		sex = sl;
	}
	public void setName(Name n){
		name = n;
	}

	public String toString(){
		return sex.getLabel();
	}
}
 ここでは「@Enumerated(EnumType.ORDINAL)」アノテーションが使われています。ORDINALはデフォルト値で、使用しているenum型について0から順番に数値が割り当てられることを表します。具体的には、SexList.javaにおける記述順に数値が割り当てられます。MALEなら0、FEMALEなら1、NEWTRALなら2です。これをSTRINGにした場合、enumの定数名がそのまま文字列として("FEMALE"とか)保存されます。
 どちらにせよ地味に便利です、これ。
// Prefix.java
package com.yamicha.toplink.characters;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_prefix" , schema="yamicha") 
	public class Prefix{
	private NameKey namekey;
	private String prefix;
	private Name name;

	public Prefix(){
		namekey = new NameKey();
	}
	public Prefix(Name name , String prefix){
		namekey = (NameKey)name.getNamekey().clone();
		this.prefix = prefix;
		this.name = name;
	}

	@EmbeddedId public NameKey getNamekey(){
		return namekey;
	}
	@Column(name="prefix") public String getPrefix(){
		return prefix;
	}
	@OneToOne(fetch=FetchType.EAGER) 
		@PrimaryKeyJoinColumns({
			@PrimaryKeyJoinColumn(name="creator" , 
			referencedColumnName="creator") ,
			@PrimaryKeyJoinColumn(name="number" , 
			referencedColumnName="number")
		})
		public Name getName(){
		return name;
	}

	public void setNamekey(NameKey k){
		namekey = k;
	}
	public void setPrefix(String p){
		prefix = p;
	}
	public void setName(Name n){
		name = n;
	}

	public String toString(){
		return prefix;
	}
}

// Creator.java
package com.yamicha.toplink.characters;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_creator" , schema="yamicha") 
	public class Creator{
	private int number;
	private String url;
	private String creator;
	private List<Name> names;

	public Creator(){
		names = new ArrayList<Name>();
	}
	public Creator(int number , String url , String creator){
		this();
		this.number = number;
		this.url = url;
		this.creator = creator;
	}

	@Id @Column(name="number") @GeneratedValue 
		public int getNumber(){
		return number;
	}

	@Column(name="url") public String getURL(){
		return url;
	}
	@Column(name="creator") public String getCreator(){
		return creator;
	}
	@OneToMany(cascade=CascadeType.ALL , mappedBy="creator" , 
		fetch=FetchType.EAGER) 
		public List<Name> getNames(){
		return names;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setURL(String u){
		url = u;
	}
	public void setCreator(String c){
		creator = c;
	}
	public void setNames(List<Name> names){
		this.names = names;
	}

	public String toString(){
		return creator;
	}
}

// Element.java
package com.yamicha.toplink.characters;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_element" , schema="yamicha") 
	public class Element{
	private int number;
	private String element;
	private List<Name> names;

	public Element(){
		names = new ArrayList<Name>();
	}
	public Element(int number , String element){
		this();
		this.number = number;
		this.element = element;
	}
	public Element(List<Name> names , 
		int number , String element){
		this();
		this.number = number;
		this.element = element;
		this.names = names;
	}

	@Id @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="element") public String getElement(){
		return element;
	}
	@ManyToMany(fetch=FetchType.EAGER) 
		@JoinTable(name="persist_name_element" , 
		schema="yamicha" , 
		uniqueConstraints=@UniqueConstraint(columnNames=
			{"creator" , "number"}) , 
		joinColumns=@JoinColumn(name="element") , 
		inverseJoinColumns={
			@JoinColumn(name="creator" , 
			referencedColumnName="creator") ,
			@JoinColumn(name="name" , 
			referencedColumnName="number")
		}) 
		public List<Name> getNames(){
		return names;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setElement(String e){
		element = e;
	}
	public void setNames(List<Name> n){
		names = n;
	}

	public String toString(){
		return element;
	}
}
 Prefix、Creatorについては大きな変更はありません。複合キーにするに当たって、必要な部分を修正しただけです。それに対して何かと変更点が多いのがElement.java。注意点としては「uniqueConstraints=@UniqueConstraint(columnNames={"creator" , "number"})」があります。これをしなければ、勝手にcreatorカラムのみをPRIMARY KEYとみなしてしまうため(元が複合キーだというのに)、当然ながらDuplicateエラーが発生します。
 後はデータを登録するだけです。
// Main.java
package com.yamicha.toplink.characters;

import javax.persistence.*;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;

public class Main{
	public static void main(String args[]) throws Exception{
		EntityManagerFactory factory = 
			Persistence.createEntityManagerFactory("persist");
		EntityManager em = factory.createEntityManager();

		EntityTransaction et = em.getTransaction();
		et.begin();

		Creator creators[] = {
			new Creator(1 , "http://www.yamicha.com/" , 
				"yamicha.com") , 
			new Creator(2 , null , "hisame") , 
			new Creator(3 , "http://sousya.umu.cc/" , "ruva") , 
			new Creator(4 , null , "coolmint") , 
			new Creator(5 , null , "Shou")
		};

		for(Creator c : creators)
			em.persist(c);

		Name names[] = {
			new Name(1 , creators[0] , "イリアス") ,
			new Name(2 , creators[0] , "サーラ") ,
			new Name(3 , creators[0] , "シェイン") ,

			new Name(1 , creators[1] , "うなぎ") ,
			new Name(2 , creators[1] , "ネコ") ,
			new Name(3 , creators[1] , "クロウ") ,

			new Name(1 , creators[2] , "ルヴァ") ,
			new Name(2 , creators[2] , "リサ") ,
			new Name(3 , creators[2] , "ファルシア") ,

			new Name(1 , creators[3] , "クルード") ,
			new Name(2 , creators[3] , "フィオーナ") ,

			new Name(1 , creators[4] , "アルヤ") ,
			new Name(2 , creators[4] , "ルナン") ,
			new Name(3 , creators[4] , "レイシス")
		};

		for(Name n : names)
			em.persist(n);

		Element elements[] = {
			new Element(
				new ArrayList<Name>(
				Arrays.asList(new Name[]{
				names[0] , names[6] , names[11] , 
				names[12] , names[13]})) 
				, 1 , "火") ,
			new Element(
				new ArrayList<Name>(
				Arrays.asList(new Name[]{
				names[0] , names[3] , names[6] , names[10]})) 
				, 2 , "水") ,
			new Element(
				new ArrayList<Name>(
				Arrays.asList(new Name[]{
				names[0] , names[3] , names[6] , names[11] , 
				names[12] , names[13]})) , 3 , "雷") ,
			new Element(
				new ArrayList<Name>(
				Arrays.asList(new Name[]{
				names[0] , names[4] , names[6] , names[12] , 
				names[13]})) , 4 , "土") ,
			new Element(
				new ArrayList<Name>(
				Arrays.asList(new Name[]{
				names[0] , names[5] , names[6] , names[10] , 
				names[12] , names[13]})) , 5 , "風") ,
			new Element(
				new ArrayList<Name>(
				Arrays.asList(new Name[]{
				names[0] , names[2] , names[6] , names[8] , 
				names[11]})) , 6 , "回復") ,
			new Element(
				new ArrayList<Name>(
				Arrays.asList(new Name[]{
				names[0] , names[2] , names[6]})) 
				, 7 , "聖") ,
			new Element(
				new ArrayList<Name>(
				Arrays.asList(new Name[]{
				names[0] , names[5]})) , 8 , "暗") ,
			new Element(
				new ArrayList<Name>(
				Arrays.asList(new Name[]{
				names[0]})) , 9 , "RDBMS")
		};

		for(Element e : elements)
			em.persist(e);

		Prefix prefixes[] = {
			new Prefix(names[0] , "魔道士") ,
			new Prefix(names[1] , "騎士") ,
			new Prefix(names[2] , "聖騎士") ,

			new Prefix(names[3] , "デビル") ,
			new Prefix(names[4] , "デビル") ,
			new Prefix(names[5] , "デビル") ,

			new Prefix(names[6] , "司祭") ,
			new Prefix(names[7] , "僧侶") ,
			new Prefix(names[8] , "聖騎士") ,

			new Prefix(names[9] , "剣士") ,
			new Prefix(names[10] , "風術師") ,

			new Prefix(names[11] , null) ,
			new Prefix(names[12] , null) ,
			new Prefix(names[13] , null)
		};

		for(Prefix p : prefixes)
			em.persist(p);

		Sex sexes[] = {
			new Sex(names[0] , SexList.FEMALE) ,
			new Sex(names[1] , SexList.NEWTRAL) ,
			new Sex(names[2] , SexList.MALE) ,

			new Sex(names[3] , SexList.FEMALE) ,
			new Sex(names[4] , SexList.FEMALE) ,
			new Sex(names[5] , SexList.FEMALE) ,

			new Sex(names[6] , SexList.FEMALE) ,
			new Sex(names[7] , SexList.FEMALE) ,
			new Sex(names[8] , SexList.MALE) ,

			new Sex(names[9] , SexList.MALE) ,
			new Sex(names[10] , SexList.FEMALE) ,

			new Sex(names[11] , SexList.MALE) ,
			new Sex(names[12] , SexList.FEMALE) ,
			new Sex(names[13] , SexList.FEMALE)
		};

		for(Sex s : sexes)
			em.persist(s);

		et.commit();

		em.close();
		factory.close();
	}
}
 目を引くのが性別の登録部分。この上ないほどに一目瞭然です。@Enumeratedはなかなか便利といいましょうか、かゆいところに手が届く機能です。やはりサーラさんはニュートラル。
 ちなみにキャラクターデータはこのように登録されています。
SELECT n.creator , n.number , p.prefix , n.name , s.sex , c.creator 
FROM persist_name n 
INNER JOIN persist_creator c ON n.creator = c.number 
INNER JOIN persist_prefix p ON n.creator = p.creator AND n.number = p.number 
INNER JOIN persist_sex s ON n.creator = s.creator AND n.number = s.number 
ORDER BY n.creator , n.number;

creator	number	prefix	name	sex	creator
1	1	魔道士	イリアス	1	yamicha.com
1	2	騎士	サーラ	2	yamicha.com
1	3	聖騎士	シェイン	0	yamicha.com
2	1	デビル	うなぎ	1	hisame
2	2	デビル	ネコ	1	hisame
2	3	デビル	クロウ	1	hisame
3	1	司祭	ルヴァ	1	ruva
3	2	僧侶	リサ	1	ruva
3	3	聖騎士	ファルシア	0	ruva
4	1	剣士	クルード	0	coolmint
4	2	風術師	フィオーナ	1	coolmint
5	1	NULL	アルヤ	0	Shou
5	2	NULL	ルナン	1	Shou
5	3	NULL	レイシス	1	Shou
 creator及びnumberが複合キーですので、双方が一致しない限りエラーにはなりません。sexカラムは0が男性、1が女性、2が中性です。分かりづらい場合には、SELECT節の「s.sex」を「ELT(s.sex + 1 , '男性' , '女性' , '中性') AS 'sex'」に書き直してみると分かりやすくなるでしょう。
 それにしても、@Enumeratedは非常に便利なのですが、複合キーは失敗でした。Persistence APIで複合キーを使うのは異常に辛いです。番号の自動生成が難しくなりますし、複合キーとJoinColumnがかぶるような場合は地獄です。可能なら避けるべきでしょう。
 Persistence APIもこれで主要な機能はおおむね使ってしまいましたか。何にしても、エラーを出しては書き直す作業を行わなければならない関係上、再起動に10分かかるJava EEでは限界がありますから、SEで使えるのはありがたい限りです。
カテゴリ [開発魔法][社会問題][ゲスト出演] [トラックバック 0][コメント 0]

軽語の話
2007/01/17(Wed)19:11:13
 旬の話題からは少々遅れましたが、「敬語5分類案」について。
 世間ではやたら騒がれているようですから、おそらく敬語を抜本的に改革するものなのでしょう。内容だけでも把握しようと、文化庁のサイトにアクセスしてみました。それにしてもどうして政府系のサイトはあれほど分かりづらいのでしょう。スマートに目的の情報にたどり着けたためしがありません。
 とにかくあまりに分かりづらかったため、これは庁のサイトを堂々巡りするより検索をかけた方が早いと考え、Google検索。何とか文書を手に入れることができました。私の経験上、最も分かりづらいのが政府系のサイト、次に分かりづらいのが日本企業のサイトであり、外国企業サイト(英語)は解読が難しいにもかかわらず、日本の企業サイトよりも大変分かりやすい場合が多いです。もし外国企業サイトまで日本企業並みにひどかったら、開発関連ソフトウェアを入手したりドキュメントを読んだりすることが非常に困難になっていたでしょう。
 それで肝心の「5分類案」ですが、実際には想像したほど大きな変更はありません。一体何を騒いでいるのだか。「大山鳴動ミジンコ一匹」ですか。具体的には、これまでの3分類(丁寧語、尊敬語、謙譲語)のうち丁寧語と謙譲語をさらにそれぞれ2つに分割するだけのものであり、しかも丁寧語は「(新)丁寧語」と「美化語」に分けるだけですから、実質謙譲語を種類分けするだけと考えて良いでしょう。謙譲語は「謙譲語1」と「謙譲語2(丁重語)」に分けるそうです。
 まず丁寧語ですが、これはそのまま「ですます調」です。また、従来の丁寧語から「美化語」が切り離されることになりますが、これはご丁寧にも「切り離す」とまで表現する必要もないものです。様々な言葉の頭に「お」や「ご」を付けるのが「美化語」なのです。「お料理」「お花」などがこれに当たります。
 それから尊敬語ですが、一見美化語に見える(相手からこちらへの)「お手紙」「ご連絡」などは「相手を立てる」役割があるため美化語ではなく尊敬語に含まれるようです(つまり美化語はあくまで丁寧語からの分割であり、他の分類にはかかわらない)。提言されたことによれば、基本的にこれまでの尊敬語から変更はないとお考えになっても間違いないでしょう。これはあまりお気になさる必要はありません。
 重要なのは謙譲語の分割です。まず謙譲語1ですが、これは「相手を立てる」形の謙譲語が該当します。やはり一見美化語に見える(こちらから相手への)「お手紙」「ご連絡」などは、こちらでも美化語ではなく謙譲語1として扱います。つまり、相手からこちらへの行為に対する敬語が尊敬語であり、こちらから相手への行為に対する敬語が謙譲語1に当たると考えて良いと申し上げましょう。「おっしゃる」は相手からの行為ですから「尊敬語」、「申し上げる」はこちらからの行為ですから「謙譲語1」に当たります。
 それでは謙譲語2(丁重語)とは。これはいわゆる「へりくだる」言い方に当たります。つまり「自分が引く」のが謙譲語2です。ですから、「申し上げる」は謙譲語1なのに、「申す」は謙譲語2、という一見おかしな現象が起こります。
 かなり混乱するのではありますが、ざっと「自分から第三者への行為」の敬語が謙譲語1、「自己完結行為」が謙譲語2である場合が多いといえそうです。私はそのように理解いたしております。これらの敬語の性質上、おおむね間違いないと存じます。
 実用上の違いとしては、謙譲語1は相手を立てるもので、謙譲語2は自分が引くものですから、他人に対しては「そちらに参ります」「そちらにうかがいます」のどちらも使うことができます。ただし、他人との会話中において「今から(自分や身内の)自宅に参ります」と言うことは(自分が引いているため)可能ですが、「今から(自分や身内の)自宅にうかがいます」と言うのは(自分・身内の自宅を立てているため)不可能です。無論「(相手の)ご自宅にうかがいます」というのは(相手の自宅を立てているため)可能です。
 まとめると、行為を丁寧に述べる際に「相手(から)の行為」を丁寧に述べるのが尊敬語、「自分から第三者への行為」を丁寧に述べるのが謙譲語1であり、「自分(身内)の行為」をへりくだって述べるのが謙譲語2となります。尊敬語と謙譲語1は「from」と「to」の立場が逆転する以外の点で似通っており、「ご連絡感謝します」の場合は尊敬語、「ご連絡差し上げます」の場合は謙譲語1といった具合に、言葉が同じでもカテゴリが変わる場合があります。
 この分類の短所としては、謙譲語1と2の分類がなかなか難しい点があるでしょうか。逆に長所としては、相手を立てるか自分が引くかをはっきりさせることで、うっかり身内や第三者を立ててしまうことを回避できます。
 どちらにせよ、分類の賛否については「どうでも良い」というのが私の立場です。はっきり言って普通の人はいちいち敬語の分類など考えて使ってはいません。文書や会話で敬語を使うたびに「この敬語の分類は」などと考えていては効率悪いことこの上ありませんし、会話なら受け答えが滑らかではなくなり、文書なら少々の挨拶文作成に丸1日かかってしまうでしょう。実用の段になれば、いちいち分類など気にしてはいられないのです。
 後は「〜でよろしかったでしょうか」のような言い方は何とかならないものでしょうか。敬語の分類よりこちらの方が余程問題では。英語でも過去形は区別されるというのに(WouldやShouldは特殊な意味を持つ場合もありますが)。これは明らかに間違った例として提起すべき問題でしょう。
 例えば品物Aを購入するような場合、売り手が「よろしいでしょうか」と聞いてくるのは「品物Aを購入することで間違いないでしょうか」という意味になります。逆に「よろしかったでしょうか」の場合には、「私は品物Aを手に取ったり取り寄せたりしたか、あるいはすでに売り渡しましたが、これで大丈夫でしょうか」という意味になります。全く意味が違ってしまうのです。もっと言えば、スポーツ選手が「私は勝ちます」と言う代わりに「私は勝ちました」と言うようなものであり、言語として破綻しています。
 これも突き詰めると結構難しいのですが、まず確認時に「ご注文は商品Aでよろしいでしょうか」とは言えますが、「商品Aでよろしかったでしょうか」とは言えません。進行形であるためです。逆に、以前に迷いながら商品Aを購入した客がまた来た場合、確認として「商品Aでよろしかったでしょうか」と言うことはできますが、「商品Aでよろしいでしょうか」とは言えません。商品の取引は過去に成立しており、進行形では述べられません。
 以前に商品Aの取り寄せを頼んだ人が後日訪れた場合には、「よろしいでしょうか」と「よろしかったでしょうか」の両方を使用できると考えられます。しかし、これは現在形と過去形を混同するわけではありません。「(商品はこれで)よろしいでしょうか」「(取り寄せた商品はこれで)よろしかったでしょうか」ということで、前者は商品について述べたものであり、後者は取り寄せについて述べたものです。つまり、商品の取り引きは進行形であるため「よろしいでしょうか」が使え、商品の取り寄せは過去に終わったため「よろしかったでしょうか」が使えるのです。
 無論、注文の復唱時に「以上でよろしかったでしょうか」は間違いです。復唱の時点ではまだ注文は確定されていないのですから、「よろしいでしょうか」でなければなりません。注文を受けたものの、その内容を忘れてしまい、後から「すみません、ご注文は〜でよろしかったでしょうか」と確認を求めるのであれば使えるでしょうが。こういうロールバックできないトランザクションのようなマネはやめて欲しいですね。いつの間にか過去形になっているのですから。
 他には「〜の方」なども耳につくことがあります。もともと「〜の方」は相対的なもの、比較するものを表す言葉であり、「AよりBの方が良い」であったり「そこより右の方」といった使い方をします。「AとBがあり、Aが〜であるのに対し、Bの方は〜」といった使い方も相対的な使い方といえます。で、「ご注文の方」といった言葉は注文を一体何と比較しているのでしょうか
 国語に関しては、とりあえず「自分や関係者を持ち上げない」「過去形を現在進行形で使わない」「相対的に使う言葉を絶対的に使わない」ことだけはしっかり教えて欲しいものです。
 ついでに謙譲語1の面白い例についても。「小社」も自分の会社を控えめに言っているため、謙譲語1に含まれるそうです。おそらく「小生」や「愚息」なども同じでしょう。その他、自分の会社を表すものとしては「弊社」もありますが、「弊」は「害のある」「ボロボロの」といった意味を持ちます。実際、弊害、疲弊などにこの字を見ることができます。対して「当社」「我が社」辺りは普通の言い方でしょう。
 相手社を丁寧に表す語としては、「貴社」などが当てはまるでしょうか。「御社」はそれより表現を弱めたものだそうです。「御」の文字は「御手紙」「御連絡」のように使うことができ、また御社が相手のことを表す言葉であることを考えても、これは尊敬語のカテゴリに含まれそうです。
 ところで、この敬語調査ではアンケートもなされており、それによれば「むかつく」という言葉はかなり市民権を得ているようで。しかし「むかつく」という言葉はアンケートの項目にしなければならないほどの誤用ではない気がするのですが。要は「ムカッとする」「胃がむかつくほど腹が立つ」わけですから。後は「全然」が本来肯定的な意味に使われていたことは有名な事実です。
 「だらしない」はもともと「しだらない」の言葉遊びの結果であり、ヤクザが「しょば代」(場所代)と称して脅迫を行った事件もありました。言語とは流動的なものです。

 ここでホットニュースです。安倍殺人内閣がついに合法無賃残業制度を提出断念しました。これを成立させようものなら、制度のなし崩しによりあっという間に国民の大半が対象にされるところまで広がっていたでしょうから、とにかく良いことです。
 事実、この手の制度が厳しい条件の元で運用されることなど絶対あり得ないのです。条件が厳しければ、適用者が少なくなります。適用者が少なければ、企業の経費削減にはほとんど影響を及ぼしません。すなわち、適用者が少ない状態では制度そのものに存在意義がほとんどないことになり、適用者が少ないままで済むわけがありません。
 ただし、参院選で自民党が勝つと極めて危険です。「国民はこの制度を支持した」などと堂々と言い放ち、平然と成立させてしまう恐れがあります。言うまでもなく大ブーイングが発生するでしょうが、衆院選のころには国民の多くはそれを忘れてしまうのです。本当、日本人はどうしてこうなのでしょう。弱者を邪魔者扱いしたり、自分以下の人間を見つけなければ安心できなかったり、何をされても数ヶ月もすれば忘れてしまったり。
 私の理想は何といっても「参院選で自民大敗、衆院選では民主党が振るわず政界再編。安倍・小沢が党首の座を去る」ことです。この2人は国民にとってためになることは何もしません。安倍氏を野放しにしておけば国民ばかりが貧乏くじを引かされ、ひたすら辛酸をなめることになりますし、小沢氏はせっかくの「政策を掲げた真正面からの選挙」という民主党の長年の積み重ねをすべて潰しており、これでは緊張感のある政治はおろか、政策を争う選挙までもが遠のくばかりです。民主党が小沢体制にならなければ、参院選は確実に勝てると言い切ることができましたが、代表が小沢氏になったばかりにそれも分からなくなりました。
 自民党は先の衆院選で低IQ選挙を仕掛け、その時の大勝と引き換えに政策を論じる層に嫌われました。その際に愚直に政策を訴えた民主党は、低IQ戦略によって敗れはしましたが、政策を論じる層には良い印象を与えたはずです。低IQ層の特徴として「支持政党も支持政策もない」「普段は政策の意味が分からないので棄権または適当に投票」「パフォーマンスのみは理解できるので、それを支持する」といったものがあり、逆に政策論争を重視する人は「政策本位で支持政党を変更」「政策の意味を理解している。投票する人も棄権する人もいる」「政策を論じないパフォーマンス選挙を嫌う」のが普通です。つまり、低IQ層は次の選挙で味方になるとは限りませんが、政策本位層であれば味方になる可能性が多分にあるのです。
 そして今回の参院選では、もはや低IQ層は無関心に戻っており、自民党の味方にはなりません。逆に政策重視者は、そろそろ安部政権のスタンスを見極めたところでしょう。ここでも民主党が愚直に政策を訴えていれば、彼らの多くは民主党に回ったであろうことが予想できます。
 ところが、小沢氏はそれをぶち壊しにしました。教育基本法の審議は欠席してすぐ出席という意味不明で理念も何もない対応、政策については「消費税を上げる代わりに年金を抜本的に変える」というこれまでの主張を「野党共闘」のためにねじ曲げ、それだというのに共産党などから攻撃対象にされ、共闘の道も断たれてしまう始末。掲げた政策も実に情けないものばかりであり、何をどうやって支持すれば良いのかさえ見当がつきません。
 つまり、せっかく自民党が墓穴を掘ってくれたのに、民主党はそれを自ら潰してしまったのです。普段ならここで参院選に敗北しても「ザマ見ろ。小沢を代表から引きずりおろして顔を洗って出直せ」と言えるところなのですが、今回ばかりはそうはいきません。民主党が負けると高い確率で無賃残業が合法化され、消費税を上げるのと引き換えに法人税を下げられます。その恩恵を一手に受ける法人連中はといえば、賃上げ交渉に当たっても「お前らにやる給料などない」と強気の姿勢。はっきり言って自民党を勝たせることだけは何が何でも避けなければならないのです。
 旧時代の利権のかたまりである安倍・小沢の2人をどうするか。やはり「参院選で民主が勝利、しかし衆院選では振るわず、双方が党首でいられなくなる」のが最も良いのです。次のリーダーにふさわしい人間というのもあまり見当たりませんが、とにかく使えない人間は遠慮なく取り替えてしまうことです。

 続・Persistence API。いくらカスケードで登録するのが楽とはいっても、さすがにあの構造はおかしいものと考えられます。改めて考えてみれば、FOREIGN KEYでリンクされたテーブルにINSERTを打つにしても、まず親の側にINSERTを打ち、次に子の側にINSERTを打つのが普通です。1回で何でもやってしまおうと考えず、登録に少々手間がかかっても構造を変えてしまった方が良さそうです。
 実際問題として、リレー用カラムをNameに持たせた場合、これは本来名前だけを保存するテーブルであるにもかかわらず、リレーするテーブルの数だけリレー用カラムをNameに書かなければなりません。普通、リレー用カラムは相手側が持つものでしょう。リレー用カラムを相手側に持たせることで、Nameの構造を維持したまま他のテーブルを用いることができ、Nameのカラムが際限なく増えたり、新しいテーブルを作るたびにNameのテーブル構造を変更し、テーブルを作り直したりする必要もなくなります。
 しかし、それならそれでpersistのカスケードにはあまり意味がないような。ということで実装に参ります。
// Creator.java
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_creator" , schema="yamicha") 
	public class Creator{
	private int number;
	private String url;
	private String creator;
	private List<Name> names;

	public Creator(){
		names = new ArrayList<Name>();
	}
	public Creator(String url , String creator){
		this();
		this.url = url;
		this.creator = creator;
	}

	@Id @GeneratedValue(strategy=GenerationType.SEQUENCE) 
		@Column(name="number") 
		public int getNumber(){
		return number;
	}

	@Column(name="url") public String getURL(){
		return url;
	}
	@Column(name="creator") public String getCreator(){
		return creator;
	}
	@OneToMany(cascade=CascadeType.ALL , mappedBy="creator" , 
		fetch=FetchType.EAGER) 
		public List<Name> getNames(){
		return names;
	}

	private void setNumber(int n){
		number = n;
	}
	public void setURL(String u){
		url = u;
	}
	public void setCreator(String c){
		creator = c;
	}
	public void setNames(List<Name> names){
		this.names = names;
	}

	public String toString(){
		return creator;
	}
}

// Name.java
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_name" , schema="yamicha") 
	public class Name{
	private int number;
	private String name;
	private Creator creator;
	private List<Element> elements;
	private Prefix prefix;

	public Name(){
		elements = new ArrayList<Element>();
	}
	public Name(Creator creator , String name){
		this();
		this.creator = creator;
		this.name = name;
	}

	@Id @GeneratedValue(strategy=GenerationType.SEQUENCE) 
		@Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@ManyToOne(fetch=FetchType.EAGER) 
		@JoinColumn(name="creator") 
		public Creator getCreator(){
		return creator;
	}
	@ManyToMany(fetch=FetchType.EAGER , mappedBy="names") 
		@OrderBy("number") 
		public List<Element> getElements(){
		return elements;
	}
	@OneToOne(cascade=CascadeType.ALL , 
		fetch=FetchType.EAGER , mappedBy="name") 
		@PrimaryKeyJoinColumn(name="number" , 
		referencedColumnName="number") 
		public Prefix getPrefix(){
		return prefix;
	}

	private void setNumber(int number){
		this.number = number;
	}
	public void setName(String name){
		this.name = name;
	}
	public void setCreator(Creator c){
		creator = c;
	}
	public void setElements(List<Element> e){
		elements = e;
	}
	public void setPrefix(Prefix p){
		prefix = p;
	}

	public String toString(){
		return name;
	}
}

// Prefix.java
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_prefix" , schema="yamicha") 
	public class Prefix{
	private int number;
	private String prefix;
	private Name name;

	public Prefix(){
	}
	public Prefix(Name name , String prefix){
		this.prefix = prefix;
		this.name = name;
		number = name.getNumber();
	}

	@Id @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="prefix") public String getPrefix(){
		return prefix;
	}
	@OneToOne(fetch=FetchType.EAGER) 
		@PrimaryKeyJoinColumn(name="number" , 
		referencedColumnName="number") 
		public Name getName(){
		return name;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setPrefix(String p){
		prefix = p;
	}
	public void setName(Name n){
		name = n;
	}

	public String toString(){
		return prefix;
	}
}

// Element.java
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_element" , schema="yamicha") 
	public class Element{
	private int number;
	private String element;
	private List<Name> names;

	public Element(){
		names = new ArrayList<Name>();
	}
	public Element(int number , String element){
		this();
		this.number = number;
		this.element = element;
	}
	public Element(List<Name> names , int number , String element){
		this();
		this.number = number;
		this.element = element;
		this.names = names;
	}

	@Id @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="element") public String getElement(){
		return element;
	}
	@ManyToMany(fetch=FetchType.EAGER) 
		@JoinTable(name="persist_name_element" , 
		joinColumns=@JoinColumn(name="element") , 
		inverseJoinColumns=@JoinColumn(name="name")) 
		public List<Name> getNames(){
		return names;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setElement(String e){
		element = e;
	}
	public void setNames(List<Name> n){
		names = n;
	}

	public String toString(){
		return element;
	}
}
 前回からの変更点を簡潔に。マップ方向が変わった他、PrefixとElementからカスケードの指定(cascade=CascadeType.ALL)を除去しています。このカスケードが異常に強力な代物で、仮にこれを残しておいた場合、例えばNameの「イリアス」を削除すると、それが持つElementも全部削除され、ElementからNameにもカスケードされることでNameのデータまで全部消えてしまう、という危険極まりないものなのです。persistの場合はロクにカスケードされないくせに。
 また、Prefix.getNumber()から@GeneratedValueがなくなっています。その代わり、Name.getPrefix()に@PrimaryKeyJoinColumnアノテーションがつけられています。今回、せっかくデータを個別に登録する手間をかけるのですから、リレーションの関係からしてもNameを登録した後にPrefixを登録するはずであり(テーブル構造的に「CREATE TABLE persist_prefix ... FOREIGN KEY(number) REFERENCES persist_name(number)」であるため)、Prefixを登録する段階ではName.numberは確定しているはずです。つまり、この値を取得してPrefix.numberに設定してやれば、これを用いることができると考えられます。
 それでは実装してみましょう。
// Main.java
import javax.persistence.*;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;

public class Main{
	public static void main(String args[]) throws Exception{
		EntityManagerFactory factory = 
			Persistence.createEntityManagerFactory("persist");
		EntityManager em = factory.createEntityManager();

		// トランザクションを取得
		EntityTransaction et = em.getTransaction();
		et.begin();

		// クリエイター情報
		Creator creators[] = {
			new Creator("http://www.yamicha.com/" , 
				"yamicha.com") , 
			new Creator(null , "hisame") , 
			new Creator("http://sousya.umu.cc/" , "ruva") , 
			new Creator(null , "coolmint") , 
			new Creator(null , "Shou")
		};

		for(Creator c : creators)
			em.persist(c);

		// キャラ名
		Name names[] = {
			new Name(creators[0] , "イリアス") ,
			new Name(creators[0] , "サーラ") ,
			new Name(creators[0] , "シェイン") ,

			new Name(creators[1] , "うなぎ") ,
			new Name(creators[1] , "ネコ") ,
			new Name(creators[1] , "クロウ") ,

			new Name(creators[2] , "ルヴァ") ,
			new Name(creators[2] , "リサ") ,
			new Name(creators[2] , "ファルシア") ,

			new Name(creators[3] , "クルード") ,
			new Name(creators[3] , "フィオーナ") ,

			new Name(creators[4] , "アルヤ") ,
			new Name(creators[4] , "ルナン") ,
			new Name(creators[4] , "レイシス")
		};

		for(Name n : names)
			em.persist(n);

		// データをフラッシュ
		// ここでデータをフラッシュすることで
		// names[0].getNumber() を使用して
		// 自動生成された数値を取得できるようになる
		em.flush();

		// 属性を設定
		Element elements[] = {
			new Element(
				new ArrayList<Name>(Arrays.asList(
				new Name[]{names[0] , names[6] , names[11] , 
				names[12] , names[13]})) , 1 , "火") ,
			new Element(
				new ArrayList<Name>(Arrays.asList(
				new Name[]{names[0] , names[3] , names[6] , 
				names[10]})) , 2 , "水") ,
			new Element(
				new ArrayList<Name>(Arrays.asList(
				new Name[]{names[0] , names[3] , names[6] , 
				names[11] , names[12] , names[13]})) 
				, 3 , "雷") ,
			new Element(
				new ArrayList<Name>(Arrays.asList(
				new Name[]{names[0] , names[4] , names[6] , 
				names[12] , names[13]})) , 4 , "土") ,
			new Element(
				new ArrayList<Name>(Arrays.asList(
				new Name[]{names[0] , names[5] , names[6] , 
				names[10] , names[12] , names[13]})) 
				, 5 , "風") ,
			new Element(
				new ArrayList<Name>(Arrays.asList(
				new Name[]{names[0] , names[2] , names[6] , 
				names[8] , names[11]})) , 6 , "回復") ,
			new Element(
				new ArrayList<Name>(Arrays.asList(
				new Name[]{names[0] , names[2] , names[6]})) 
				, 7 , "聖") ,
			new Element(
				new ArrayList<Name>(Arrays.asList(
				new Name[]{names[0] , names[5]})) 
				, 8 , "暗") ,
			new Element(
				new ArrayList<Name>(Arrays.asList(
				new Name[]{names[0]})) , 9 , "RDBMS")
		};

		for(Element e : elements)
			em.persist(e);

		// プレフィックスを設定
		Prefix prefixes[] = {
			new Prefix(names[0] , "魔道士") ,
			new Prefix(names[1] , "騎士") ,
			new Prefix(names[2] , "聖騎士") ,

			new Prefix(names[3] , "デビル") ,
			new Prefix(names[4] , "デビル") ,
			new Prefix(names[5] , "デビル") ,

			new Prefix(names[6] , "司祭") ,
			new Prefix(names[7] , "僧侶") ,
			new Prefix(names[8] , "聖騎士") ,

			new Prefix(names[9] , "剣士") ,
			new Prefix(names[10] , "風術師") ,

			new Prefix(names[11] , null) ,
			new Prefix(names[12] , null) ,
			new Prefix(names[13] , null)
		};

		for(Prefix p : prefixes)
			em.persist(p);

		// コミット
		et.commit();

		// クローズ
		em.close();
		factory.close();
	}
}
 ポイントはem.flush()で、これをすることで生成された番号値を取得することができるようになります。後はPrefixのコンストラクタがname.getNumber()を呼び出し、番号を取得してくれるようになっています。これでNameとPrefixのプライマリキーの番号は一致し、めでたく@PrimaryKeyJoinColumnの恩恵にあずかることができるのです。
 何やら前回よりメンバーが少しだけ増えていますが、あまりお気になさらずに。ソースが異常に読みづらいですが、これはNameを配列ではなくキャラ名の変数にする(Name ilias , Name sara とか)だけでもかなり改善できると考えられます。それをしなかったのはスペルが分からないキャラが存在するためです。人様のキャラを勝手に使っておきながら、スペルまで間違えるなど非常に失礼ですから、配列にしてみました。
 さて、まずは普通にデータを取得してみましょうか。先ほどのMain.javaの「et.commit()」の部分に次のようなコードを加えます。
// Main.main() の末尾部分
// コミット
et.commit();

// ここから記入
Name n = em.find(Name.class , 1);
System.out.println("---" + n.getName() + "---");
System.out.println(n.getPrefix());
System.out.println(n.getElements());

// クローズ
em.close();
factory.close();
 これを実行してみると、結果は次の通り(例によってキャラは変動します)。
---サーラ---
null
[]
 nullですと?ちなみにこれは、登録後に一旦プログラムを終了させ、再び起動して表示しようとした場合には上手くいきます。データの登録直後にこれを呼び出した場合、どうやら先ほど登録したインスタンスがそのまま取得されるようです。だとすれば、n.getPrefix()でnullが返るのも当然です。カスケードの構造的にはPrefix(所有側)にName(被所有側)のデータを登録すれば足りるため、NameにはPrefixのインスタンスを登録していないのですから。
 これを避けるには「EntityManager.refresh()」メソッドを使用します。
for(Creator c : creators)
	em.refresh(c);
 これをすることで、findで取得されるデータはEAGERフェッチされたものになります(もちろん@OneToOnなどのアノテーションで「fetch=FetchType.EAGER」が指定されていることが必要です)。本来なら全データをrefreshしなければならないはずですが、ありがたいことにrefreshにはカスケードが有効のようです。結果、これで全データがリフレッシュされ、データが取得できるようになります。
 実行結果は次の通り(キャラが先ほどと変わっていますが、そういう仕様ですから仕方ありません)。
---クロウ---
デビル
[風, 暗]
 では実際にクエリを打ってみましょうか。データ構造を大幅に変更してはみたものの、果たして正しく登録されているのでしょうか。
// 魔法が使えないからといって、そこまで悲観されなくても・・・
SELECT p.prefix , n.name , 
GROUP_CONCAT(e.element ORDER BY e.number) AS 'elements' 
FROM persist_name n INNER JOIN persist_prefix p USING(number) 
LEFT JOIN persist_name_element ne ON n.number = ne.name 
LEFT JOIN persist_element e ON ne.element = e.number 
GROUP BY n.number ORDER BY COUNT(e.number);

prefix	name	elements
剣士	クルード	NULL
僧侶	リサ	NULL
騎士	サーラ	NULL
聖騎士	ファルシア	回復
デビル	ネコ	土
デビル	クロウ	風,暗
聖騎士	シェイン	回復,聖
風術師	フィオーナ	水,風
デビル	うなぎ	水,雷
NULL	アルヤ	火,雷,回復
NULL	レイシス	火,雷,土,風
NULL	ルナン	火,雷,土,風
司祭	ルヴァ	火,水,雷,土,風,回復,聖
魔道士	イリアス	火,水,雷,土,風,回復,聖,暗,RDBMS

// 「かわいい子には旅をさせよ」といいますが
SELECT c.creator , GROUP_CONCAT(n.name) AS 'names' FROM persist_creator c 
INNER JOIN persist_name n ON c.number = n.creator GROUP BY c.number;

creator	names
Shou	レイシス,ルナン,アルヤ
coolmint	クルード,フィオーナ
ruva	ファルシア,リサ,ルヴァ
hisame	ネコ,クロウ,うなぎ
yamicha.com	シェイン,サーラ,イリアス
 どうやら問題なさそうです。それにしても、SQLとは何と便利なのでしょうか。シークェルの魔法、恐るべし。
 それに比べてJavaでは「条件に当てはまるもののみ表示」したり、「レコードの数を計測」したりといったことが非常に困難です。一体どうすれば良いのでしょうか。そのような場合に役に立つのが「EJB QL」です。今では「Java Persistence Query Language」と呼ばれているようですが。これはいわゆる「クエリもどき」です。
 例えばこのように使用します。
Query q = em.createQuery(
	"SELECT c FROM Creator c WHERE c.number > :number");
q.setParameter("number" , 2);
List l = q.getResultList();
for(Object o : l){
	Creator c = (Creator)o;
	System.out.println(c.getCreator());
	System.out.println(c.getURL());
}
 慣れるまで文法には悩まされましたが、SQLに近いです。クエリの結果はListで返され、ループすることで複数の結果を取得できます。
 プレースホルダも使用できますが、これは「?1」または「:number」のような形式になります。「?1」の形式の場合は「setParameter(1 , value)」、「:number」の形式の場合は「setParameter("number" , value)」のようにして値をセットします。
 createQuery()の他、createNativeQuery()も用意されています。これを使えばRDBMS固有の処理を行うことができます。
Query q = em.createNativeQuery(
	"SELECT * FROM persist_creator WHERE number > ? LIMIT 1 , 2" , 
	Creator.class);
q.setParameter(1 , 2);

List l = q.getResultList();
for(Object o : l){
	Creator c = (Creator)o;
	System.out.println(c.getCreator());
	System.out.println(c.getURL());
}
 こちらは本物のSQL文です。LIMIT節はMySQL固有のものですが、ここでは使うことができます。私が引っかかった注意点としては、createNativeQuery()ではプレースホルダは「?」になります。また、EJBQLの場合と違ってクラス名も指定しなければなりません。クエリによって返された結果は、ここで指定したクラスにマップされるようです。
 createNativeQuery()は単なるSQL文であり、結果を特定のクラスにマップするだけのものですから、これ以上説明する必要もないでしょう。問題はEJBQLの文法です。英語の資料しかないという最悪の状況でしたが、とりあえず根性で習得してみました。
 単純なデータ取得や条件指定の方法は分かったとして、RDBMSに欠かせないテーブルのリレーションはどうすれば良いのでしょうか。資料では次の2つの書き方がなされています。
SELECT n FROM Creator c , IN(c.names) n WHERE c.number = 1
SELECT n FROM Creator c JOIN c.names n WHERE c.number = 1
 どちらもnumberが1のクリエイターが作成したキャラを全部取得するクエリです。次のSQL文に相当します。
SELECT n.* FROM persist_creator c 
INNER JOIN persist_name n ON n.creator = c.number WHERE c.number = 1;
 INとJOINはこの点でほぼ同じと見られますが、次のようなクエリはJOINの場合のみ有効で、INを使用した場合は動作しません。
SELECT n FROM Name n , IN(n.creator) c WHERE c.number = 1
SELECT n FROM Name n JOIN n.creator c WHERE c.number = 1
 INは対象がリストの場合でなければ使えないようです。
 ちなみに、JOINはINNER JOINに相当するようです。ですからElementのように「キャラによっては1つも持たない可能性がある」ものに対してこれを使うと、
SELECT n FROM Name n JOIN n.elements e

・取得されるキャラ
イリアス
シェイン
ルヴァ
ファルシア
アルヤ
うなぎ
ルナン
レイシス
クロウ
フィオーナ
ネコ
 このように、属性を持たないキャラは表示されなくなってしまいます。これを回避するには「LEFT JOIN」を使わなければなりません。
SELECT n FROM Name n LEFT JOIN n.elements e

・相当するクエリ
SELECT DISTINCT n.* FROM persist_name n 
LEFT JOIN persist_name_element ne ON n.number = ne.name 
LEFT JOIN persist_element e ON ne.element = e.number;
 これでキャラ全員を取得することができます。他には「IS EMPTY」という構文もあり、
SELECT n FROM Name n WHERE n.elements IS EMPTY

クルード
リサ
サーラ

・相当するクエリ
SELECT n.* FROM persist_name n 
LEFT JOIN persist_name_element ne ON n.number = ne.name 
WHERE ne.name IS NULL;
 リストの中身が存在しないもののみを取得することができます。他にも「IS NULL」などがありますが、これはSQLでもおなじみです。
SELECT n FROM Name n JOIN n.prefix p WHERE p.prefix IS NULL

レイシス
ルナン
アルヤ

・相当するクエリ
SELECT n.* FROM persist_name n 
INNER JOIN persist_prefix p USING(number) 
WHERE p.prefix IS NULL;
 LIKEなども使用できます。
SELECT n FROM Name n WHERE n.name LIKE '%ナ%'

ルナン
フィオーナ

・相当するクエリ
SELECT n.* FROM persist_name n WHERE n.name LIKE '%ナ%';
 無論、グループ化もできます。
SELECT COUNT(c) FROM Creator c

5

SELECT c.creator , COUNT(n.name) 
FROM Creator c JOIN c.names n GROUP BY c.creator ORDER BY c.number

Shou	3
coolmint	2
ruva	3
hisame	3
yamicha.com	3
 本来、GROUP BYには「c.number」を使うべきなのですが、どういうわけかSELECT文中のカラムに用いたものをGROUP BYに使用しなければならないらしく、それは不可能でした。どうしても必要があるなら「SELECT c.number , COUNT(n.name)」のような書き方が必要になります。もちろんSQLならc.numberを使用したグループ化も可能なのですが。
SELECT c.creator , COUNT(*) FROM persist_creator c 
INNER JOIN persist_name n ON c.number = n.creator GROUP BY c.number;
 グループ化時に使用する関数としては、他にMIN、MAX、SUM、AVGがあるようです。どれもSQLのそれと同じ意味を持ちます。
 さらにはWHERE節でサブクエリも使えるのですね、これが。やはりRDBMS魔法を名乗るからにはサブクエリも使えなければ。
SELECT p FROM Prefix p WHERE p.prefix = ANY 
(SELECT np.prefix FROM Prefix np WHERE np.number >= 10)

デビル
デビル
風術師
司祭
デビル
騎士
魔道士
 サブクエリには「ANY」「SOME」「ALL」がサポートされている(ANYとSOMEは同じ意味)ようです。また、比較対象を持たないものとして「EXISTS」も使用できます。いずれもSQLのそれと同じ意味を持ちます。
 更新・削除を行うクエリもありますが、これはトランザクション内部で行うようにしましょう。
Query q = em.createQuery(
"UPDATE Creator c SET c.creator = 'yamicha' WHERE c.creator = :creator");
q.setParameter("creator" , "yamicha.com");
q.executeUpdate();
 雑感としては、EJBQLでは@ManyToManyのリレーションが非常に楽なのはおいしいです。SQLだと3つのテーブルを絡ませなければならないのですが、EJBQLならJOINが1つだけで済みます。ただ、自由度が直打ちより少々低かったり、何とも言えない使いづらさは否めません。ネイティブクエリを絡ませるなどの工夫が必要そうです。
 しかし、SQLとは何とも奥深いものではありませんか。
カテゴリ [開発魔法][社会問題][ゲスト出演] [トラックバック 0][コメント 0]

モラルの幇死活動
2007/01/15(Mon)14:50:51
 いわゆる「痴呆」症状についての面白い研究結果が(「認知症」に言い換えが進んでいますが、これは全体の同意も得ず、一応5個の選択肢の中から「認知症」と定められたものではあるものの、これは選定される以前からすでに言い換え語が決まっていたような不公正なものであり、言い換えるべきかは悩みどころです)。
 世の中にはバイリンガルという人がいます。2ヶ国語をネイティブに操るとんでもない人々です。こうした人々は人間ではなく、人間をスーパークラスとする新しいクラスから継承したのではないかとも言いたくなりますが、それは置いておきまして、バイリンガルの隠された効用が明らかになったようです。とはいっても、外国の分析結果らしく、これがそのまま日本人にどれほど適合するのかは定かではありませんが。
 何でも、バイリンガルはそうでない人に比べて「痴呆」の発生が4年程度遅いらしいのです。頭を使えばボケにくいとは言われていますが(この通説に科学的・統計的根拠があるのかは知りませんが)、ありそうな話ではあります。といっても、ここで用いられた患者データは184人とのこと、母集団の数がやや少なすぎる事実は否めません。しかし、バイリンガルがボケにくいという可能性は否定できません。
 ちなみに、読売新聞の元記事によれば「高学歴の人は、認知症の発症が遅い代わりに、症状の進行が速いと言われている」そうな。本当でしょうか。頭が良い人の発症が遅いというのは感覚的にも理解できますが、進行速度が増すとはどうしたことでしょう。
 そこで気になるのが、「私がネイティブに扱っているあの言語やこの言語は、果たして言語に含まれるのか」という点に他なりません。要するに、英語でも仏語でも独語でも「脳の活動部位が同じなら同等の効果が期待できる」わけで、開発言語も言語を使うのと同じ脳部位で用いているのであれば、同様にボケ防止の効果を期待できるではありませんか。といっても、プログラム言語をネイティブに使っている老人などそうそういませんから、統計調査はできないのが現状でしょうが。
 もし含まれるのだとすれば、無属性開発魔法使いは「症状の発動が遅い」上に「症状の進行も遅い」という完璧な耐性を備えていることになるのでは。

 ここでバッドニュースです。どうやら高校生に対して「奉仕活動義務化」なるものが導入されそうです。森の時に散々却下されたというのに、安倍政権はバカの集まりですか。有識者とやらが寄ってたかって。しかし、有識者とは要するに「自称」であったり、あるいは「単なる肩書き持ち」であり、本当に識を有した人間であるかは分かりません。ゼロにゼロをいくつ足してもゼロなのです。
 まず、いわゆるボランティアの人間が絶対に持ってはならない感情は、「してやっている」という意識である、とのことです。そのような態度で接した場合、本来なら恩恵を受けるはずの人々が非常に迷惑するそうです。行動はぞんざいになり、態度はぶっきらぼうになり、確かに相手が迷惑がるのも分かります。災害時の救援活動などでは、ただでさえダメージを受けた被災者が、そうしたボランティアのせいでさらに不愉快になる、といったことも起こるのでしょう。迷惑度は節度もへったくれもないマスコミ連中よりもマシなレベルでしょうが
 無論、人間のできた方が被災者なり弱者なりの方々に奉仕しようというのは素晴らしいことです。きっと相手も大助かりに違いありません。しかし、「奉仕活動義務化」という、日本語の構成からしておかしなものになったとしたら、一体どうなるでしょうか。私がその立場であるなら、絶対に不満を隠せないでしょう。私は君子でも仏でもありません。
 そして、結果として恩恵者が不愉快になったとしても、私はおそらく何とも考えないでしょう。これはあくまで「義務」としてやっているのであり、まじめにやっても利益はありません。奉仕活動で自分を磨いたり、他人を助けたいのなら、義務外の奉仕活動を人知れずやるでしょう。やらなければ卒業ができなかったりするのであれば、嫌でもやるしかないのです。その結果として相手が不愉快になろうが知ったことではありません。
 こう書くと異常に冷淡なようですが、結果は明らかでしょう。あくまで「卒業する資格」という対価のために「奉仕活動」を行うのであり、それ以上のものを求める方がどうかしています。果ては、義務外の人知れぬ活動を行う親切な人が評価されず、義務化された活動を適当に果たしておおっぴらに自慢する人間の方が評価が高くなり、結果として「黙々と人知れぬ活動をするより、ぞんざいにやって自慢した場合の方が利益が大きい」ということになり、1億総堀江化現象が発生する可能性が多分にあります。
 そもそも奉仕活動は、義務化によって偽善的・損得主義的に行わせるようなものではありません。実は最も効果的なのが「とっさの機転・親切」であり、まさに「小さな手間で大きな効果」なのです。「席を譲る」などは良く言われますが、体の不自由な方を数秒でも手助けしたり、建物に入る際に後続の人がいれば(それが体の不自由な方や荷物を持った方なら特に)扉を開けておく、といった行為は労力の割に極めて有用です。状況次第で相手は大変助かるはずですが、このようなものは「義務化」の際に評価されることもありません。
 米国ではこの手の行為はごく自然に行われるのだそうです。あくまで「当たり前」であるとか。しかし日本では、ほんの2〜3秒の手間で済むことあっても大勢が無視したり、中には露骨に嫌な顔をする人間までいる始末。かくして困った人は、手伝ってもらえば数秒でできるのに、自分でそれを行うことはできず(またはできても困難で)、途方に暮れるのです。
 奉仕活動義務化はこの場合に「無視」する人間を確実に増加させます。今でさえそういう人間が多い(それどころか、子どもの手術費用捻出のために募金活動をされている親御さんを非難する人間までいる)というのに、さらに奉仕活動義務化で「損得第一主義」と「卒業資格という対価を得るための奉仕活動」を教え込まれでもすれば、もう地獄絵図です。車に置き去りにされた子どもを窓割って助けろとまでは言いませんから、米国を少しは見習って、義務化などというふざけた考えをボツにし、もう少しはマシなことを考えてはいかがでしょう。
 唯一の利点といえば、社会学の勉強(「公民」とでも言うのでしょうか。高校には疎いので不明ですが)の例題として役に立つことでしょうか。「君たちは奉仕活動によって労力を提供している。その対価として卒業する権利を得ている」のように説明すれば、すんなり理解できそうです。これなら同時に「金銭・物品によらない(権利を取引品目とする)授受」なども勉強できるでしょうし。そう考えると、何と素晴らしいアイデアでしょうか。
 ちなみに、私は大学9月入学自体には賛成ですが、「4月から半年間は奉仕活動に充てる」のは断固反対です。しかし、万が一これも導入されたら、法学部などの学生には「この場合、君たちは大学入学の権利と引き換えに奉仕活動の義務を受けたことになり、逆に相手(国か大学か自治体か)は入学を認める義務と引き換えに君たちに奉仕活動をさせる権利を得たことになる」などと授業で説明できるわけですか。確かに分かりやすくはあります。

 Persistence API。それでは開発魔法・リレーションでも使ってみるとしましょう。
 なぜシークェルの魔法は使えるのか。単に1つのテーブルしか使わないのであれば表計算も同じですが、この魔法の長所は何といってもリレーションにあります。慣れるまでは色々と難しいですが、慣れればクエリはおおむねカンで打ててしまいますし、何より直打ちは面白く、その上非常に強力です。これでも十分すぎるほど使えるのに、さらにトランザクションの魔法や外部キーまで使えてしまいます。これはもう使わない手はありません。
 それにしても、SQLクエリは他のプログラム言語とは違った面白さがあるのですが、これはボケ予防になるのでしょうか。もしJavaやC++、Perlなどにボケを防ぐ効果がなく、SQLにはあるのだとすれば、ORマッピングばかり使った人はそうでない(直打ち派の)人に比べて速くボケるわけですか。
 そういうバカバカしい話はともかく、まずはおさらいと参りましょう。まずPersistenceには大きく3タイプのリレーション方法が用意されており、それぞれ次のような関係にあります。
連結所有側被所有側
1対1@OneToOne/@JoinColumnなどを併記@OneToOne/mappedByを使用
1対多@ManyToOne/@JoinColumnなどを併記@OneToMany/mappedByなどを使用
多対多@ManyToMany/@JoinTableなどを併記@ManyToMany/mappedByを使用
 かなり大まかですが、ざっとこうなります。mappedByを指定した側が被所有側になり、被所有側に@JoinColumnなどをつけても効果はないようです。逆にmappedByを指定しなかった側は所有側になり、@JoinColumnアノテーションなどで動作を指定することになります。基本的に好きな方を所有側にして良いのですが、@ManyToOneにはmappedByが存在しないため、必ず所有側になるようです。また、所有側には被所有側とリレーするためのカラムが作られます
 もう1つ大きな点としては、Persistenceは「カスケード」をサポートしており、1つのインスタンスをデータベースに保存するだけで、そのインスタンスに登録された他の関連クラスのインスタンスのデータもすべてデータベースに保存してくれます。ただし、所有側に被所有側のデータを登録した場合には(被所有側に所有側のデータを登録しなくても)正しくカスケードされるのですが、逆だとカスケードされません。つまり、被所有側のインスタンスを保存する際は、所有側のインスタンスに対しても自分を登録しておく必要があります。
 しかしこの仕様、絶対に矛盾している気がしてなりません。理由は後述として、まずはそれぞれのリレー方式の意味について。しかし、以前に作った説明があまりにもピッタリ過ぎるため、今回もその説明を使用してみます。
 まず「@OneToOne」はテーブル同士が1対1で関連するものです。これはプレフィックスやクラス名に当たります。「騎士サーラ」とか「デュアルナイト・サーラ」とか。もっと近い例で言えば、生年月日などもこれに当たるでしょうか。生年月日は1人が複数持つことはありません。また、誰か1人の生年月日を変更しても、他人のそれは変更されません。
 次に「@OneToMany/@ManyToOne」ですが、こちらはテーブルが1対多で対応するものです。こちらは装備品に当たります。何やら「@ManyToMany」に似ている気もしますが、全く違います。この「1対多」というのは非常に重要です。仮に騎士サーラ及び剣聖ハードゥンが全く同じ「騎士剣」を所持しているとしましょう。しかし、騎士サーラはこれを改造し、大剣にしてしまいました。この場合、騎士サーラ所有の「騎士剣」を「大剣」に変更することになりますが、この場合に剣聖ハードゥン所有の「騎士剣」は何の影響も受けません。ここは非常に重要です。
 これをブログに例えるなら、記事とコメント・トラックバックの関係に当たります。1つの記事は0〜複数個のコメントを持ちます。また、カスケードを設定しておけば、記事を削除することでコメントも全部消えます。
 さらに、カテゴリを1つしか選択できないブログにおける記事とカテゴリの関係もこれに当たります(カテゴリが@OneToMany、記事が@ManyToOne)。1つのカテゴリに0〜複数の記事が所属するわけです。しかし、1つしかカテゴリを持てないブログは相当使いづらい気がするのですが、いかがでしょう。事実、私はブログのプログラムを書く際、「何が何でも複数カテゴリだけは実装する」と決めていました。
 残るは「@ManyToMany」ですが、これはキャラが使用できる魔法に当たります。これと「@OneToMany/@ManyToOne」との決定的な違いは、魔法テーブルのカラムを変更することで、その魔法使用者の全員が影響を受けることです。世には「品物のオーダー」などをSQLのサンプルに使用している例が多いですが、この例で言うところの「顧客ごとのオーダー」を@ManyToManyで管理しようものなら、何か騒動が起きることでしょう。
 具体的には、私は「J2SE」の魔法が使えます。イリアス氏もサーラ氏も「J2SE」の魔法が使えます。しかし、ここでSunが「これからJ2SEではなくJava SEと呼びましょう」と言い出しました(実話)。この場合、「J2SE」を「Java SE」に修正すれば、私の魔法もイリアス氏の魔法もサーラ氏の魔法も「Java SE」に修正されるのです。
 これをブログで説明すれば、複数カテゴリが使えるブログにおける記事とカテゴリの関係です。例えば、カテゴリに「徒然(トゼン)日記」と「戯言(ザレゴト)」「Ajax(アジャックス)」の3つが存在するとしましょう。ここで2つ目を「戯言(タワゴト)」に変更しました。すると、このカテゴリを用いているすべての記事にそれが反映されるのです。言うまでもありませんが、これを「装備品」とかに用いると、「@OneToMany」の例で言うところの騎士サーラの「騎士剣」を「大剣」に変更するだけで、剣聖ハードゥンの剣も「大剣」になってしまいます。これでは大変な騒ぎになりますのでご注意を。
 それで、カスケードに欠陥があるといいますのは、@ManyToOneの側が必ず所有側になるという点なのです。確かに、カラム面でそうなるのは仕方ありません。@OneToManyを使った場合、1つのカラムに対して大量のカラムがリレーされますから、こちらにリレー用カラムを持たせることは不可能です。それは分かります。
 しかし、これはブログの例で言うところの「コメントが記事の所有側になっている状態」なのです。つまり、記事に対してコメントを登録しても動作せず、コメントに対してそれぞれ記事を登録してやらなければならないのです。
Content content をブログ記事とする
Comment cmt1 , cmt2 を登録すべきコメントとする
Content は setComments(List<Comment>) メソッドを持つ
Comment は setContent(Content) メソッドを持つ
em は EntityManager のインスタンス

// 1.NG
ArrayList<Comment> cmts = new ArrayList<Comment>();
cmts.add(cmt1);
cmts.add(cmt2);
content.setComments(cmts);
em.presist(content);

// 2.OK
ArrayList<Comment> cmts = new ArrayList<Comment>();
cmts.add(cmt1);
cmts.add(cmt2);
cmt1.setContent(content);
cmt2.setContent(content);
content.setComments(cmts);
em.presist(content);

// 3.OK
cmt1.setContent(content);
cmt2.setContent(content);
em.persist(cmt1);
em.persist(cmt2);
 まず1の方法ですが、Contentが被所有側ですので不可能です。2の方法は最も現実的ですが、手間が倍増するのは言うまでもありません。本当は1の方法で全部カスケードしてくれるのが最も良いのです。
 3の方法は一見お手軽ですが、問題が多いです。確かにCommentは所有側なのですが、これだとコメントが存在しなければContentを登録できないという意味不明な矛盾が発生してしまいます。動作としてあまりにおかしすぎます。テーブル構造から考えても、テーブル作成時にカスケードを指定しているのなら、Contentが存在しなければCommentは登録できないはずなのです。
 しかし最も気になるのがカスケード。このような仕様であるからには、普通は所有側を主要な側とし、被所有側を副次的な側としなければとてもやっていられません。しかし、この例ではテーブル構造的に所有側が被所有側にカスケードすることになります。一体どのテーブルのレコードを削除したら、他のテーブルのレコードが連動して削除されるのか、このままでは全く想像がつきません。
 しかし、実際のところ「被所有側が親になり、所有側がFOREIGN KEYで被所有側とリレーする」仕様になっているようです。ということは、所有側を主要なテーブルにすれば登録が楽である(カスケードできる)反面、削除する時は大変である(カスケードできない)、ということになります。やはり矛盾は否めません。一応言っておきますが、仮に私がSQL直打ちでテーブルをデザインしたのであれば、絶対にNameとPrefix及びNameとElementの関係を反転させていたでしょう(Nameが被所有側になる)。つまり、今回のテーブルは実際のところ構造自体が間違っているのですが、こうしなければ保存の際のカスケードが非常に手間になりますから、今回はやむを得ないでしょう。
 仕様に文句を言っても仕方ありませんから、とにかく実装してみましょうか。今回用意するエンティティクラスは4つで、それぞれクリエイター、キャラ名、プレフィックス、属性を定義します。クリエイターは1人で複数のキャラを持つことができ、プレフィックスはキャラ1人につき1つだけ、属性は1人のキャラがいくつでも持てるものとします。persistence.xmlは以前のものをそのまま使います。
// Creator.java
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_creator" , schema="yamicha") 
	public class Creator{
	private int number;
	private String url;
	private String creator;
	private List<Name> names;

	public Creator(){
	}
	public Creator(String url , String creator){
		this.url = url;
		this.creator = creator;
		names = new ArrayList<Name>();
	}
	public Creator(String url , String creator , 
		List<Name> names){
		this.url = url;
		this.creator = creator;
		this.names = names;
	}

	@Id @GeneratedValue(strategy=GenerationType.SEQUENCE) 
		@Column(name="number") 
		public int getNumber(){
		return number;
	}

	@Column(name="url") public String getURL(){
		return url;
	}
	@Column(name="creator") public String getCreator(){
		return creator;
	}
	@OneToMany(cascade=CascadeType.ALL , mappedBy="creator" , 
		fetch=FetchType.EAGER) 
		public List<Name> getNames(){
		return names;
	}

	private void setNumber(int n){
		number = n;
	}
	public void setURL(String u){
		url = u;
	}
	public void setCreator(String c){
		creator = c;
	}
	public void setNames(List<Name> names){
		this.names = names;
	}

	public void addName(Name name){
		name.setCreator(this);
		names.add(name);
	}

	public String toString(){
		return creator;
	}
}
 このクラスはクリエイター名とサイトURLを保持します。addName()メソッドは実装を楽にするためのものです。Creatorは被所有側ですので、setList()でNameを登録するだけでは正しく動作しません。そこで、addName()を使ってNameを登録することで、自分のList<Name> namesにデータを登録するのと同時に、Name側にも自分を登録しているのです。
 後は他のクラスもぼちぼちと。
// Name.java
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_name" , schema="yamicha") 
	public class Name{
	private int number;
	private String name;
	private Creator creator;
	private List<Element> elements;
	private Prefix prefix;

	public Name(){
		elements = new ArrayList<Element>();
	}
	public Name(String name){
		this();
		this.name = name;
	}

	@Id @GeneratedValue(strategy=GenerationType.SEQUENCE) 
		@Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@ManyToOne(cascade=CascadeType.ALL , fetch=FetchType.EAGER) 
		@JoinColumn(name="creator") 
		public Creator getCreator(){
		return creator;
	}
	@ManyToMany(cascade=CascadeType.ALL , fetch=FetchType.EAGER) 
		@JoinTable(name="persist_name_element" , 
		joinColumns=@JoinColumn(name="name") , 
		inverseJoinColumns=@JoinColumn(name="element")) 
		public List<Element> getElements(){
		return elements;
	}
	@OneToOne(cascade=CascadeType.ALL , fetch=FetchType.EAGER) 
		@JoinColumn(name="prefix") 
		public Prefix getPrefix(){
		return prefix;
	}

	private void setNumber(int number){
		this.number = number;
	}
	public void setName(String name){
		this.name = name;
	}
	public void setCreator(Creator c){
		creator = c;
	}
	public void setElements(List<Element> e){
		elements = e;
	}
	public void setPrefix(Prefix p){
		prefix = p;
	}

	public String toString(){
		return name;
	}
}

// Prefix.java
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_prefix" , schema="yamicha") 
	public class Prefix{
	private int number;
	private String prefix;
	private Name name;

	public Prefix(){
	}
	public Prefix(String prefix){
		this();
		this.prefix = prefix;
	}
	public Prefix(String prefix , Name name){
		this(prefix);
		setName(name);
	}

	@Id @GeneratedValue(strategy=GenerationType.SEQUENCE) 
		@Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="prefix") public String getPrefix(){
		return prefix;
	}
	@OneToOne(cascade=CascadeType.ALL , fetch=FetchType.EAGER , 
		mappedBy="prefix") 
		public Name getName(){
		return name;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setPrefix(String p){
		prefix = p;
	}
	public void setName(Name n){
		name = n;
	}

	public String toString(){
		return prefix;
	}
}
 Prefix.javaはName.javaの延長にあるようなものですので、簡単ですね。このような場合、本当は「@PrimaryKeyJoinColumn」アノテーションを使ってプライマリキー同士でリレーするのが最も効率的なのですが、困ったことに@Id修飾された双方の変数に対して手動で同じ値を入れてやらなければならないのです。しかし、これでは自動採番ができないではありませんか。自動採番の場合、どういうアルゴリズムなのだか双方にてんでバラバラの番号を代入してしまい、名前とプレフィックスの関係がぐちゃぐちゃになってしまいます。「魔道士サーラ」とか。せっかくカスケードできるのですから、両方に同じ番号をセットする程度の脳はないのでしょうか。
 最後は属性クラスです。
// Element.java
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity @Table(name="persist_element" , schema="yamicha") 
	public class Element{
	private int number;
	private String element;
	private List<Name> names;

	public Element(){
		names = new ArrayList<Name>();
	}
	public Element(int number , String element){
		this();
		this.number = number;
		this.element = element;
	}

	@Id @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="element") public String getElement(){
		return element;
	}
	@ManyToMany(cascade=CascadeType.ALL , fetch=FetchType.EAGER , 
		mappedBy="elements") 
		public List<Name> getNames(){
		return names;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setElement(String e){
		element = e;
	}
	public void setNames(List<Name> n){
		names = n;
	}

	public String toString(){
		return element;
	}
}
 属性のみは自分で番号を割り当てるようにしてみました。
 @Column(name="name")や@JoinColumn(name="name")ではカラム名を指定するのに、どうやら@ManyToManyなどのmappedBy="name"では相手のBeans属性名に当たるものを指定するようです。混乱しますが、仕様ですから仕方ありません。
 ではこれを実行してみましょう。今回登録するデータは「主要なゲスト出演者及びゲスト出予定者の一覧」です。
import javax.persistence.*;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;

public class Main{
	public static void main(String args[]) throws Exception{
		EntityManagerFactory factory = 
			Persistence.createEntityManagerFactory("persist");
		EntityManager em = factory.createEntityManager();

		// トランザクションを取得
		EntityTransaction et = em.getTransaction();

		// クリエイターのデータ
		// 閉鎖予定・URL 未定サイト様などに関しては URL が null
		Creator creators[] = {
			new Creator("http://www.yamicha.com/" , "yamicha.com") , 
			new Creator(null , "hisame") , 
			new Creator("http://sousya.umu.cc/" , "ruva") , 
			new Creator(null , "coolmint") , 
			new Creator(null , "Shou")
		};

		// 属性データ
		// 果たして RDBMS 魔法を使える人などいるのでしょうか
		Element fire = new Element(1 , "火");
		Element ice = new Element(2 , "水");
		Element thunder = new Element(3 , "雷");
		Element earth = new Element(4 , "土");
		Element wind = new Element(5 , "風");
		Element recovery = new Element(6 , "回復");
		Element saint = new Element(7 , "聖");
		Element dark = new Element(8 , "暗");
		Element rdbms = new Element(9 , "RDBMS");

		// キャラ名データを作成
		Name names[][] = {
			{
				new Name("イリアス") ,
				new Name("サーラ") ,
				new Name("シェイン")
			} , {
				new Name("うなぎ") ,
				new Name("ネコ") ,
				new Name("クロウ")
			} , {
				new Name("ルヴァ") ,
				new Name("リサ")
			} , {
				new Name("クルード") ,
				new Name("フィオーナ")
			} , {
				new Name("アルヤ") ,
				new Name("ルナン")
			}
		};

		// データをクリエイターに対して登録
		for(int i = 0; i < names.length; i++){
			for(Name name : names[i]){
				creators[i].addName(name);
			}
		}

		// プレフィックスを登録
		names[0][0].setPrefix(new Prefix("魔道士"));
		names[0][1].setPrefix(new Prefix("騎士"));
		names[0][2].setPrefix(new Prefix("聖騎士"));

		names[1][0].setPrefix(new Prefix("デビル"));
		names[1][1].setPrefix(new Prefix("デビル"));
		names[1][2].setPrefix(new Prefix("デビル"));

		names[2][0].setPrefix(new Prefix("司祭"));
		names[2][1].setPrefix(new Prefix("僧侶"));

		names[3][0].setPrefix(new Prefix("剣士"));
		names[3][1].setPrefix(new Prefix("風術師"));

		names[4][0].setPrefix(new Prefix(null));
		names[4][1].setPrefix(new Prefix(null));

		// 属性を登録
		names[0][0].setElements(new ArrayList<Element>(
			Arrays.asList(new Element[]{fire , ice , thunder , 
			earth , wind , recovery , saint , dark , rdbms})));
		names[0][2].setElements(new ArrayList<Element>(
			Arrays.asList(new Element[]{recovery , saint})));

		names[1][0].setElements(new ArrayList<Element>(
			Arrays.asList(new Element[]{ice , thunder})));
		names[1][1].setElements(new ArrayList<Element>(
			Arrays.asList(new Element[]{earth})));
		names[1][2].setElements(new ArrayList<Element>(
			Arrays.asList(new Element[]{wind , dark})));

		names[2][0].setElements(new ArrayList<Element>(
			Arrays.asList(new Element[]{fire , ice , thunder , 
			earth , wind , recovery , saint})));

		names[3][1].setElements(new ArrayList<Element>(
			Arrays.asList(new Element[]{ice , wind})));
		
		names[4][0].setElements(new ArrayList<Element>(
			Arrays.asList(new Element[]{fire , thunder , 
			recovery})));
		names[4][1].setElements(new ArrayList<Element>(
			Arrays.asList(new Element[]{fire , thunder , 
			earth , wind})));

		// データを書き込む
		et.begin();

		for(Creator c : creators)
			em.persist(c);

		et.commit();

		// クローズ
		em.close();
		factory.close();
	}
}
 どういうアルゴリズムなのか、DBにはムチャクチャな順番で登録してくださいます。PRIMARY KEYの数値は自動生成されるのですが、もうバラバラです。しかし、人様のキャラクターを無断で勝手にこのような用途で使用するのは大丈夫なのでしょうか。何か後でものすごく怒られそうな気がするのですが。カスケード(@OneToOne(cascade=...))をかけているおかげで、これだけでデータが全部登録されます。
 それから、「setElements(new ArrayList<Element>(Arrays.asList(new Element[]{...})))」のような複雑怪奇な書き方をしている理由ですが、まずArrayListに配列をそのまま渡すコンストラクタはありませんので、配列を使いたい場合は必然的にArrays.asListでListに変換することになります。無論、ここで得たListをそのままsetElements()に渡しても良いのですが、どうやらここで得られるListはCloneableをインプリメントしていないらしく、toplinkが例外を投げてきます。そこで、Cloneableを持つArrayListのコンストラクタにデータを渡しているのです(ArrayListにはCollectionを引数に取るコンストラクタがあり、ListはCollectionを継承している)。
 それでは少々データをのぞいてみましょうか。
SELECT * FROM persist_name ORDER BY number LIMIT 5;

number	name	prefix	creator
1	シェイン	12	2
2	フィオーナ	10	1
3	イリアス	2	2
4	ルナン	7	3
5	アルヤ	4	3
 見事にバラバラでいらっしゃいます。どのような順番で登録されるかは分からないようです。同じように登録された方で、順番が上記と同じにならないという方がおられましても、別に異常ではありません。そういう仕様です。また、以下の一部のクエリでも実行結果が異なる場合がありますが、これも仕様です。
 さて、このデータを使うことで一体どのような照会ができるでしょうか。そこはRDBMSですから色々と。
// プレフィックスと肩書きは違うのでしょうが・・・
SELECT n.name , p.prefix FROM persist_name n 
INNER JOIN persist_prefix p ON n.prefix = p.number 
ORDER BY n.number LIMIT 5 , 3;

name	prefix
クロウ	デビル
うなぎ	デビル
クルード	剣士

// そこのお姉さん、魔法はお好きですか?
SELECT n.name , GROUP_CONCAT(e.element) AS 'elements' FROM persist_name n 
LEFT JOIN persist_name_element ne ON n.number = ne.name 
LEFT JOIN persist_element e ON ne.element = e.number 
GROUP BY n.name ORDER BY COUNT(e.element);

name	elements
クルード	NULL
サーラ	NULL
リサ	NULL
ネコ	土
クロウ	風,暗
シェイン	回復,聖
フィオーナ	水,風
うなぎ	水,雷
アルヤ	火,雷,回復
ルナン	火,雷,土,風
ルヴァ	火,水,雷,土,風,回復,聖
イリアス	火,水,雷,土,風,回復,聖,暗,RDBMS

// 長い黒髪、風になびかせ
SELECT n.name FROM persist_name n 
INNER JOIN persist_name_element ne ON n.number = ne.name 
WHERE ne.element = 5;

name
フィオーナ
イリアス
ルナン
クロウ
ルヴァ

// 親御さんによろしくお伝えください
SELECT c.creator , GROUP_CONCAT(n.name) AS 'names' FROM persist_creator c 
INNER JOIN persist_name n ON c.number = n.creator 
GROUP BY c.number;

creator	names
coolmint	フィオーナ,クルード
yamicha.com	シェイン,イリアス,サーラ
Shou	ルナン,アルヤ
ruva	ルヴァ,リサ
hisame	クロウ,うなぎ,ネコ
 ちなみに属性は「それなりに使いこなせる」もののみを登録しています。初等・中等魔法が少し使える程度で登録したのでは大変なことになりますので。某魔道士は論外として、個性が出ていて面白いリストです。それにしても、考える限りのことがすべてできてしまうのですから、RDBMSは便利です。
 無論、Javaからも扱えます。どれかデータを取り出してみましょうか。ただし、フェッチタイプがEAGER(@ManyToMany(fetch=FetchType.EAGER)のような)になっていなければ失敗するかもしれません。もちろん上記コードではEAGERにしてあります。
// Main.main() をこの通り変更
EntityManagerFactory factory = 
	Persistence.createEntityManagerFactory("persist");
EntityManager em = factory.createEntityManager();

Name n = em.find(Name.class , 2);
Creator c = n.getCreator();
Prefix p = n .getPrefix();

System.out.println(p.getPrefix() + n.getName());
System.out.println("Creator:" + c.getCreator());
System.out.println("Elements:");
for(Element e : n.getElements())
	System.out.println(e.getElement());

em.close();
factory.close();
 おおむね見ての通りですが、実行結果はこうなります(表示されるキャラクターは違う場合があります。とにかくpersist_name.numberが2のデータが表示されます)。
風術師フィオーナ
Creator:coolmint
Elements:
水
風
 これでデータを表示できることが分かりました。それでは、SQLの場合のようにデータの数量を得たり、特定の条件に当てはまるデータのみを得たり、リレーションの魔法を使ったりはできないのでしょうか。これを手作業でコーディングしようとすれば、とても実現できないか、あるいはものすごい手間と労力がかかるのは見えていますが。
 長くなりましたので、以下次号。
カテゴリ [開発魔法][社会問題][ゲスト出演] [トラックバック 0][コメント 0]

[1] [2] 次のページ->
- Blog by yamicha.com -