2.6 Webサーバ(クラウド)側プログラムの作成
次にサーバ側でのプログラムサンプルですが、Webクライアントから送られたリクエストはサーブレット(hrSvlt)が受信し、サーブレットからインスタンス化されたビーンズ(hrBean)メソッド内でリクエストに対するCRUD処理が実行されます。
サーブレット
リスト4のサーブレットではdoGetメソッドで参照リクエストを、doPostメソッドで登録、更新、削除のリクエストを受信して、それぞれの処理を実行するビーンズのインスタンスを(1)と(3)で生成し、(2)、(4)、(5)、(6)でそれぞれのメソッドを実行しています。
package com.business; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; @SuppressWarnings("serial") public class hrSvlt extends HttpServlet { @Override public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{ req.setCharacterEncoding("utf-8"); res.setContentType("text/html; charset=utf-8"); PrintWriter out; out = res.getWriter(); String mode = req.getParameter("mode"); String emp_no = req.getParameter("emp_no"); String resp = ""; hrBean hr = new hrBean(); //(1)ビーンズインスタンス生成 resp = hr.revEmp(emp_no); //(2)参照処理用メソッド実行 out.println(resp); out.flush(); out.close(); } @Override public void doPost (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{ req.setCharacterEncoding("utf-8"); res.setContentType("text/html; charset=utf-8"); PrintWriter out; out = res.getWriter(); String resp = ""; String mode = req.getParameter("mode"); String emp_no = req.getParameter("emp_no"); String name = req.getParameter("name"); String job = req.getParameter("job"); String assign = req.getParameter("assign"); String license = req.getParameter("license"); hrBean hr = new hrBean(); //(3)ビーンズインスタンス生成 if(mode.equals("add")){ resp = hr.addEmp(emp_no, name, job, assign, license); //(4)登録処理用メソッド実行 }else if(mode.equals("upd")){ resp = hr.updEmp(emp_no, name, job, assign, license); //(5)更新処理用メソッド実行 }else if(mode.equals("del")){ resp = hr.delEmp(emp_no); //(6)削除処理用メソッド実行 } out.println(resp); out.flush(); out.close(); } }
ビーンズ
リスト5のビーンズにはCloud SQLの処理を実行するコードが記述されており、サーブレットからの呼び出しにより実行されます。
package com.business; import java.text.*; import java.util.Date; import java.sql.*; import com.google.appengine.api.rdbms.AppEngineDriver; public class hrBean { String user = "user1"; String pass = "pass1"; Connection con = null; int peCnt = 0; public hrBean(){ try{ DriverManager.registerDriver(new AppEngineDriver()); //Cloud SQLドライバをロード con = DriverManager.getConnection("jdbc:google:rdbms://branecosmology:business/business", user, pass); //Cloud SQLデータベース(MySQL)接続 } catch (Exception e) { return; } } /////////////////////////////////////////// // 登録処理 public String addEmp(String emp_no, String name, String job, String assign, String license){ Date now = new Date(); DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); String moddate = df.format(now); //以下、動的SQLでemp_masテーブルへレコード登録 try{ String sql = "insert into emp_mas values( ?, ?, ?, ?, ?)"; //動的SQLでの登録SQL文 PreparedStatement ps = con.prepareStatement(sql); ps.setString(1, emp_no); ps.setString(2, name); ps.setString(3, job); ps.setString(4, assign); ps.setString(5, moddate); ps.executeUpdate(); //登録実行 if(license != ""){ //ライセンス項目がある場合はlicenseテーブルにレコード登録 //登録は選択されたライセンスの数だけレコード登録される String[] license_a = license.split(","); for(int i = 0; i < license_a.length; i++) { String sql2 = "insert into license values( ?, ?)"; PreparedStatement ps2 = con.prepareStatement(sql2); ps2.setString(1, emp_no); ps2.setString(2, license_a[i]); ps2.executeUpdate(); } }else { //選択されたライセンス項目がない場合は、ダミー("NA")登録 String sql2 = "insert into license values( ?, ?)"; PreparedStatement ps2 = con.prepareStatement(sql2); ps2.setString(1, emp_no); ps2.setString(2, "NA"); ps2.executeUpdate(); } return "{\"stat\": \"" + "登録成功: " + emp_no + "\"}"; }catch(SQLException e) { return "{\"stat\": \"" + "登録不成功: " + e + "\"}"; } } ///////////////////////////////////////////////// // 参照処理 public String revEmp(String emp_no){ String rv = ""; try{ //動的SQLでemp_masからキー(emp_no) 検索 String sql = "select * from emp_mas where emp_no = ?"; PreparedStatement ps = con.prepareStatement(sql); ps.setString(1, emp_no); ResultSet rs = ps.executeQuery(); if(rs.next()) { String name = rs.getString("name"); String job = rs.getString("job"); String assign = rs.getString("assign"); String moddate = rs.getString("moddate"); rv = "{\"stat\": \"" + "参照成功" + "\",\"name\": \"" + name + "\", \"job\": \"" + job + "\", \"assign\": \"" + assign + "\", \"moddate\": \"" + moddate+ "\"," ; } else { return "{\"stat\": \"参照不成功\"}"; } //動的SQLでlicenseテーブルから任意数のライセンスう登録を検索 String sql2 = "select qua from license where emp_no = ?"; PreparedStatement ps2 = con.prepareStatement(sql2); ps2.setString(1, emp_no); ResultSet rs2 = ps2.executeQuery(); rv += "\"license\":["; while(rs2.next()) { String qua = rs2.getString("qua"); rv += "{\"emp_no\": \"" + emp_no + "\", \"qua\": \"" + qua + "\"},"; } rv = rv.substring(0, rv.length() - 1) + "]}"; return rv; }catch(SQLException e){ return "{\"stat\": \"NO:SQLエラー\"}"; } } /////////////////////////////////////////////// // 更新処理 public String updEmp(String emp_no, String name, String job, String assign, String license){ Date now = new Date(); DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); String moddate = df.format(now); try{ String sql = "update emp_mas set name = ?, job = ?, assign = ?, moddate = ? where emp_no = ? "; PreparedStatement ps = con.prepareStatement(sql); ps.setString(1, name); ps.setString(2, job); ps.setString(3, assign); ps.setString(4, moddate); ps.setString(5, emp_no); ps.executeUpdate(); //licenseテーブルの更新処理はDELETE/ADD(全件削除して新規登録)で行う。 //licernseテーブルの削除 String sql2 = "delete from license where emp_no = ? "; PreparedStatement ps2 = con.prepareStatement(sql2); ps2.setString(1, emp_no); ps2.executeUpdate(); if(license != ""){ //license項目がある場合は、その数だけ新規登録を行う String[] license_a = license.split(","); for(int i = 0; i < license_a.length; i++) { String sql3 = "insert into license values( ?, ?)"; PreparedStatement ps3 = con.prepareStatement(sql3); ps3.setString(1, emp_no); ps3.setString(2, license_a[i]); ps3.executeUpdate(); } }else { //license項目がない場合は、1件ダミー登録を行う String sql3 = "insert into license values( ?, ?)"; PreparedStatement ps3 = con.prepareStatement(sql3); ps3.setString(1, emp_no); ps3.setString(2, "NA"); ps3.executeUpdate(); } return "{\"stat\": \"" + "更新成功: " + emp_no + "\"}"; }catch(SQLException e) { return "{\"stat\": \"" + "更新不成功: " + e + "\"}"; } } //////////////////////////////////////////////// // 削除処理 public String delEmp(String emp_no){ try{ //動的SQLでemp_masレコードの削除処理 String sql = "delete from emp_mas where emp_no = ? "; PreparedStatement ps = con.prepareStatement(sql); ps.setString(1, emp_no); ps.executeUpdate(); //動的SQLでlicenseレコードの削除処理 String sql2 = "delete from license where emp_no = ? "; PreparedStatement ps2 = con.prepareStatement(sql2); ps2.setString(1, emp_no); ps2.executeUpdate(); return "{\"stat\": \"" + "削除成功: " + emp_no + "\"}"; }catch(SQLException e) { return "{\"stat\": \"" + "削除不成功: " + e + "\"}"; } } }
Cloud SQLのCRUD(登録・参照・更新・削除)処理は、リスト5のビーンズで行われますが、基本的な処理手順については、「Google Cloud SQLとBigtableのコード記述と処理スピードを比較・検討してみる」の5ページ目以降で解説していますのでそちらを参照してください。
ただし今回のサンプルでは、資格(license)で任意数の資格取得項目をセットできるようになっているので、この部分はメイン(emp_mas)とは別テーブル(license)で登録処理を行っています。
ここで、emp_masとlicenseは同じキー(emp_no)を使用しているので、参照ではJOIN句を使用したテーブル結合で必要なデータを取得することもでき、例えば次のINNER JOINを使用したSQL文を、Google API ConsoleのSQL Prompt画面から実行した場合、図11のような結果が得られます。
select * from emp_mas as t1 inner join license as t2 on t1.emp_no = t2.emp_no and t1.emp_no = "e002"
このサンプルのように、JOINでテーブルを結合した検索を行った場合、図11のように重複するフィールドがほとんどで取得データが大幅に冗長になってきます。GAEのクラウドではSQLを発行するサーバとデータベースが配置されているサーバはクラウドシステム上に分散配置されるので、データベース検索結果を、SQLを発行したサーバに送信する場合にもネットワーク負荷とそれによるレイテンシが大きいと想像され、このサンプルでは、冗長データによるネットワーク負荷軽減を考えてemp_masおよびlicenseテーブル個別にSQLを発行するようにしています。しかし、個別送信ではまた別途の負荷(ヘッダなど)もあることから、実際にどちらがより効率的かは、タイマーなどでの検証も必要になってくるでしょう。
GoogleのCloud SQLサイトには、「SQL prompt」は基本的なSQLステートメントのみ(for basic SQL statements only)実行できると書かれていますが、JOINでテーブルを結合した検索もできることから、すべての確認は行っていませんが一般的なSQL文はほとんど実行できるのではと考えられます。
更新処理
資格(license)テーブルの更新処理はいわゆる「DELETE/ADD」で行われています。これは実際の基幹系システム(統合生産管理システム)でも経験したことですが、複雑な更新処理はDELETE/ADDで行う事もよくあり、またこの方が間違いも少なかったように思います。このサンプルのように、あるフィールドの項目数が任意になるような場合は、正規化を持ち出すまでもなく別テーブルとして登録されますが、この場合はDELETE/ADDで処理するのも1つの方法でしょう。
まとめ
以上今回(連載第1回)は、jQueryを使用したCloud SQLの基本CRUD処理を、基幹系システムの基本構成とマスタ・テーブルとの関係も絡めて見てきました。次回はWebを使用した仕訳入力について見ていく予定です。
仕訳入力は期末決算処理など、制限期限内にすべて入力を完了しなければならないことから、徹夜作業になることもあり、実際筆者もそのような状況を見てきています。従って、仕訳は入力の操作性とスピードが特に要求されることから、これまでWeb化も進んでいない分野でした。次回はこのような視点から、クラウドを利用しながらどのようにして仕訳入力で要求される条件を満たすUIをクラウドで構成できるかを検討してみます。