昨日の、Javascriptを利用してActionを実行する方法だけど、この応用で、Javascriptを使い、ブラウザでSQLを組み立てると、サーバー側のプログラムを変えずに、かなり柔軟に仕様変更できる。
さらに、サーバー側のプログラムはStrutsでなくても、CGIでも、サーブレットでも、何でもよくなってくる。
具体的に例をだそう。
なお、これ以降の説明中に出てくるテーブルは、改訂新版 はじめてのSQLの36ページDEPT表を使っている。(が、実は、どんなテーブルでもOKなのお・・・ってことは、後でわかる)
■概要
たとえば、Strutsを利用して検索の場合、
(1)検索画面では、strutsタグでは、hiddenで、paraという1項目だけを定義する(ここにSQLが入る)
(2)そして、他の入力項目はINPUTタグで定義し(そうすると、ActionFormで、セッターゲッターを定義しなくてよい)、ボタンを用意する。
(3)ボタンが押されたら、入力項目からデータを取ってきて、検索SQL文を作成し、paraにセットし、submit()して、サーバーへ
(4)サーバー側では、paraからSQL文を受け取り、それを実行、結果をArrayList(要素はHashMap)にいれる
1レコード1ArrayListの要素(=1HashMap)で、
1レコード分のHashMapの1つのキーは1項目分、値は、それに対する値
(5)そのArrayListをセッションにいれて、結果表示JSPは、そのセッションの値を
元に表示する。
Strutsでない場合、(3)をsubmitでなく、AJAX(REST)とかにして、(5)の返り値をXMLにするとかすれば、サーバーのフレームワーク、言語は問わないことになる。
■ソース
ってことで、まず、(1)~(3)を実行する、検索部分のJSPはこんなかんじ(index.jsp)
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %> <%@ page contentType="text/html; charset=utf-8" %> <html:html> <HEAD> <TITLE>検索</TITLE> <script type="text/javascript" language="javascript"> <!-- function zikko() { where = ""; where = addwhere(where,selectForm.basho.value,"LOC","'"); where = addwhere(where,selectForm.dname.value,"DNAME","'"); selectForm.para.value = "SELECT * FROM DEPT"; if ( where != "" ) { selectForm.para.value += " WHERE " + where; } selectForm.submit(); } function addwhere(where,val,key,flg) { if (val != "") { if ( where != "" ) { where += " AND"; } where += ( " "+ key+ "=" + flg + val + flg ); } return where; } // --> </script> </HEAD> <BODY> <html:form action="/select"> <html:hidden property="para"/> 場所<INPUT TYPE=text NAME="basho" size="6"/><BR> 名前<INPUT TYPE=text NAME="dname" size="6"/><BR> <html:button property="b1" onclick="zikko()">検索実行</html:button> </html:form> </BODY> </html:html> |
(上記 < >は、本当は半角)
そして、(4)の部分だが、まず、ActionFormは、こんなかんじで、
package dbsample; import org.apache.struts.action.*; public class selectActionForm extends ActionForm { private String para; public String getPara() { return para; } public void setPara(String para) { this.para = para; } } |
(4)を実行するActionは、こんなかんじ
package dbsample; import java.sql.*; import java.util.*; import javax.servlet.http.*; import org.apache.struts.action.*; import java.io.*; public class selectAction extends Action { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { // 画面パラメータの取得 selectActionForm myForm = (selectActionForm)form; // セッションの取得 HttpSession session = request.getSession(); try { Connection con = null; Statement stmt = null; // DB接続(testデータベースに、ログイン名root,パスワードpasswordの場合) Class.forName("org.gjt.mm.mysql.Driver"); con = DriverManager.getConnection("jdbc:mysql:///test","root","password"); stmt = con.createStatement(); // SQL(画面から引数para)実行 ResultSet rs = stmt.executeQuery(myForm.getPara()); // 結果をArrayListに ArrayList<HashMap<String,String>> result = new ArrayList<HashMap<String,String>>(); while(rs.next()) // 行数分処理 { HashMap<String,String> map = new HashMap<String,String>(); // 項目数分出力 for(int j = 1 ; j <= rs.getMetaData().getColumnCount() ; j ++) { map.put(rs.getMetaData().getColumnName(j), rs.getString(j)); } result.add(map); } // あとしまつ stmt.close(); con.close(); // 結果をセッションにいれる session.setAttribute("result",result); session.setAttribute("kekka",""); } catch(Exception e) { session.removeAttribute("result"); session.setAttribute("kekka",e.toString()); return mapping.findForward("error"); } return mapping.findForward("success"); } } |
(上記 < >は、本当は半角)
SQLを受け取るところ、myForm.getPara()になっている。
つまり、javascriptでSQLを作って、そのまま実行している。
そして、結果表示は、こんなかんじ(kekka.jsp)
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %> <%@ page contentType="text/html; charset=utf-8" %> <HTML> <HEAD> <TITLE>検索結果</TITLE> </HEAD> <BODY> 結果<BR> <table border=1> <tbody> <logic:notEmpty name="result" scope="session"> <logic:iterate id="me" name="result" scope="session" indexId="idx"> <tr> <td><bean:write name="me" property="DEPTNO" /></td> <td><bean:write name="me" property="DNAME" /></td> <td><bean:write name="me" property="LOC" /></td> </tr> </logic:iterate> </logic:notEmpty> </tbody> </table><BR> <A HREF="index.jsp">戻る</A> </BODY> </HTML> |
(上記 < >は、本当は半角)
エラーの場合は、エラーメッセージを出している(error.jsp)
<%@ page contentType="text/html; charset=utf-8" %> <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %> <HTML> <HEAD> <TITLE>エラー</TITLE> </HEAD> <BODY> <H2> <bean:write name="kekka" scope="session"/> </H2> <A HREF="index.jsp">戻る</A> </BODY> </HTML> |
(上記 < >は、本当は半角)
ついでに書くと、struts-config.xmlは、こんなかんじ
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.dtd"> <struts-config> <form-beans> <form-bean name="selectForm" type="dbsample.selectActionForm"/> </form-beans> <action-mappings> <action path="/select" type="dbsample.selectAction" name="selectForm" scope="request" validate="false"> <forward name="success" path="/kekka.jsp"/> <forward name="error" path="/error.jsp"/> </action> </action-mappings> </struts-config> |
(上記 < >は、本当は半角)
■メリット
こりゃー、チョー便利だ。
テーブル項目を追加、変更したい場合,
たとえば、DEPTNOという項目を追加したい場合、
検索部分のJSP(index.jsp)を
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %> <%@ page contentType="text/html; charset=utf-8" %> <html:html> <HEAD> <TITLE>検索</TITLE> <script type="text/javascript" language="javascript"> <!-- function zikko() { where = ""; where = addwhere(where,selectForm.basho.value,"LOC","'"); where = addwhere(where,selectForm.dname.value,"DNAME","'"); where = addwhere(where,selectForm.deptno.value,"DEPTNO",""); selectForm.para.value = "SELECT * FROM DEPT"; if ( where != "" ) { selectForm.para.value += " WHERE " + where; } selectForm.submit(); } function addwhere(where,val,key,flg) { if (val != "") { if ( where != "" ) { where += " AND"; } where += ( " "+ key+ "=" + flg + val + flg ); } return where; } // --> </script> </HEAD> <BODY> <html:form action="/select"> <html:hidden property="para"/> 場所<INPUT TYPE=text NAME="basho" size="6"/><BR> 名前<INPUT TYPE=text NAME="dname" size="6"/><BR> 番号<INPUT TYPE=text NAME="deptno" size="6"/><BR> <html:button property="b1" onclick="zikko()">検索実行</html:button> </html:form> </BODY> </html:html> |
(上記 < >は、本当は半角。赤字は修正箇所)
ってなかんじで、値をいれるところと、あとSQL文をつくるところを変えるだけで、本体を何もいじらないでできる。
ってか、テーブル自体変えちゃう事だってできる。
もちろん、変更に伴い、結果表示画面を変えることはある。でも、結果表示もJSPだけなので、結局JSPを書き換えるだけで、Action,ActionFormは変えずに、Tomcatをとめずにできる。
エラーチェックはJavascriptですればいいし。。。
つーか、元となるプログラムは
・select用(execQuery実行)
・1行Update用(execUpdate実行)
・トランザクション複数行更新用
の3つを用意すれば、あとは画面のJSPを作れば、どーにでもなりそうな気がする
(画面制御は、kekka.jspにlogic:forwardを書いて、ステータス(をセッションに入れておき)によって、表示したいJSPにフォワードするようにすれば、struts-configの修正もいらない。
ステータスは、成功時、次に遷移するステータスをjavascriptでSQLと一緒に送るようにすればいい。もちろん、条件とかもあり、それも送らないといけない場合もあるかもしれないけど、まあ、そのへんは適当に拡張してもらって・・・)
■問題点
しかし、このやり方は致命的問題点がある。
セキュリティがぼろぼろだ。
SQLがパラメータなので、テキトーなSQLを送ると、それを実行してしまう。
テーブル全削除とか・・(^^;)
なので、このままでは、使えないだろう。
ただ、テーブルごとに画面を用意し、"SELECT * FORM DEPT "ぐらいを書いておき、Where句のみをJavascriptで作らせ(そのとき、Javascriptで、エラーチェックする)、サーバー側でも、複数文かかれていないか、つまり、";"とかが含まれていないかをチェックし、実行するという形にすれば、かなり柔軟に画面やDBが変えられることになる。
とはいえ、今のように画面項目を受け渡すようにしてしまうと、DBや画面の項目変更に伴い、プログラムが動く。これは、ちょっと作業工程的に、ためし作りがしにくくなってくる(理由は、UML等各種ダイアグラムのエラーチェック体系化のほうで出てくる)。
ということで、どこまでjavascriptを使って、クライアント側に柔軟性を持たせるかということが、セキュリティと開発効率の間でトレードオフになってくるわけだが、クラウドとかマッシュアップの流れは、明らかに、クライアント側に柔軟性を持たせる方向に進んでいるといえる。
(かといって、SQL発行までやらせはしないけど。。。)