2010年11月28日日曜日

Spring SecurityでDB接続してユーザ認証してみる

Springの認証機構Spring SecurityでのDBと繋いだログイン処理。

・環境
Spring 3.0.5
Roo 1.1.0

基本となる各種設定をSpring Rooでセットアップします。

roo> security setup

Managed ROOT\pom.xml [Adding property 'spring-security.version' to '3.0.2.RELEASE']
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-core:${spring-security.version}]
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-config:${spring-security.version}]
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-web:${spring-security.version}]
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-taglibs:${spring-security.version}]
Created SRC_MAIN_RESOURCES\META-INF\spring\applicationContext-security.xml
Created SRC_MAIN_WEBAPP\WEB-INF\views\login.jspx
Managed SRC_MAIN_WEBAPP\WEB-INF\views\views.xml
Managed SRC_MAIN_WEBAPP\WEB-INF\web.xml
Managed SRC_MAIN_WEBAPP\WEB-INF\spring\webmvc-config.xml

Spring RooによってSpring Securityアドオンがインストールされ、web.xmlにプロキシが登録されたり、各種viewが作成されます。
(実行時のSpring Securityバージョンは3.0.2)

認証機構の定義ファイルとして
SRC_MAIN_RESOURCES\META-INF\spring\applicationContext-security.xml
が生成されます。

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

 <!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
     <form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/resources/j_spring_security_logout"/>
        
        <!-- Configure these elements to secure URIs in your application -->
        <intercept-url pattern="/choices/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/member/**" access="isAuthenticated()" />
        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/**" access="permitAll" />
    </http>

 <!-- Configure Authentication mechanism -->
    <authentication-manager alias="authenticationManager">
     <!-- SHA-256 values can be produced using 'echo -n your_desired_password | sha256sum' (using normal *nix environments) -->
     <authentication-provider>
      <password-encoder hash="sha-256"/>
         <user-service>
             <user name="admin" password="8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" authorities="ROLE_ADMIN"/>
          <user name="user" password="04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb" authorities="ROLE_USER"/>
      </user-service>
     </authentication-provider>
 </authentication-manager>

</beans:beans>

デフォルトのこの設定ファイルで、
"/choices/**"のURLは'ROLE_ADMIN'なロールを持っていること、
"/member/**"のURLは認証されていること、
その他のアクセスは無制限に許可、
のインターセプトがかかります。

また、User/Passがadmin/adminの場合はROLE_ADMIN、user/userの場合はROLE_USERな権限設定がXMLに直接記述されています。

これをDBに問い合わせ、ユーザ認証を行うように変更します。

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

 <!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
     <form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/resources/j_spring_security_logout"/>
        
        <!-- Configure these elements to secure URIs in your application -->
        <!-- cssやimgなどWEBリソースは無制限 -->
        <intercept-url pattern="/resources/**" access="permitAll" />
        <!-- ログイン処理は無制限 -->
        <intercept-url pattern="/login/**" access="permitAll" />
        <!-- それ以外はadmin権限 -->
        <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
    </http>

 <!-- Configure Authentication mechanism -->
    <authentication-manager alias="authenticationManager">
     <!-- SHA-256 values can be produced using 'echo -n your_desired_password | sha256sum' (using normal *nix environments) -->
     <authentication-provider>
      <!-- sha-256でパスワードをエンコーディングする -->
      <!-- "admin"文字列のハッシュ => "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" -->
      <!-- "user"文字列のハッシュ => "04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb" -->
      <password-encoder hash="sha-256"/>
      <!-- データソース指定。SQL指定。 -->
      <jdbc-user-service data-source-ref="dataSource"
      users-by-username-query="select users_id as username, password as password, true as enabled from users where users_id=?"
      authorities-by-username-query="select users_id as username, 'ROLE_ADMIN' as authority from users where users_id=?"/>
     </authentication-provider>
 </authentication-manager>
</beans:beans>

以下で記述されているスキーマを定義することが好ましいですが、上記の設定ではusersテーブル一つで処理を行っています。
http://static.springsource.org/spring-security/site/docs/2.0.x/reference/appendix-schema.html

usersテーブルにはusers_id、passwordの2つのフィールドが定義されているものとします。

users-by-username-query設定で以下のクエリを発行し、認証を許可するユーザを取得します。

select
    users_id as username,
    password as password,
    true     as enabled
from
    users
where
    users_id=?

authorities-by-username-query設定で実際の認証設定を取得する以下のクエリを発行します。
ユーザが存在し、users-by-username-queryでのユーザ取得が行えれば全てのリソースに接続可能とするため、'ROLE_ADMIN'な固定値をauthorityとして返却します。

select
    users_id     as username,
    'ROLE_ADMIN' as authority
from
    users
where
    users_id=?

usersテーブルに以下のデータを登録すればhoge/adminでログイン可能となります。
users_id:hoge
password:8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918

参考資料)

・Spring Securityの機能について
http://ameblo.jp/spring-beginner/entry-10234230466.html

・実際に認証と認可をWEBにつけるには?
http://ameblo.jp/spring-beginner/entry-10236817063.html

・Spring by Example - Simple Spring Security Webapp
http://www.springbyexample.org/examples/simple-spring-security-webapp-spring-config.html

・5 Minute Guide to Spring Security
http://www.mularien.com/blog/2008/07/07/5-minute-guide-to-spring-security/

・Using Spring-Security Database in Spring-Roo
http://roosbertl.blogspot.com/2010/06/using-spring-security-database-in.html

・Security Database Schema
http://static.springsource.org/spring-security/site/docs/2.0.x/reference/appendix-schema.html

・Security Namespace Configuration
http://static.springsource.org/spring-security/site/docs/3.1.x/reference/ns-config.html#ns-auth-manager

・概説 Springプロダクト(3) - Spring Securityでユーザ認証/アクセス制御
http://journal.mycom.co.jp/articles/2010/03/25/spring3/index.html

・Spring Roo日本語リファレンス - 2.10 セキュアなアプリケーション
http://www.spring-roo.com/home/210

2010年11月10日水曜日

EclipseプラグインでJetty連携

開発用のAPコンテナをJettyにしよう作業してたところ、EclipseからJettyを起動させるプラグインを発見。

http://docs.codehaus.org/display/JETTY/Web+Tooling+Support


これはいいや、と書かれている通りに構築していたのですが、以下のエラーが出て起動できず。

java.lang.NoClassDefFoundError: org/mortbay/start/Main
Caused by: java.lang.ClassNotFoundException: org.mortbay.start.Main
 at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Exception in thread "main" 

別途アプリをデプロイすればいいだけなのですが、開発資材を毎度毎度操作するのは面倒過ぎる。。

何だかんだで5時間近く色々と試してみたところ、当該プラグインがJetty7には対応していない、というオチ。

こことか、ここにその旨ありました。

Jetty6系だとちゃんと動きます。

2010年11月9日火曜日

Eclipseで比較エディタが開けなくなった。

Eclipse Version: 3.6.1

Updateかけたら比較エディタが開けなくなった。

こんなエラーが出る。

Could not open the editor: An unexpected exception was thrown.

java.lang.NullPointerException
 at org.eclipse.php.internal.core.format.FormatPreferencesSupport.verifyValidity(FormatPreferencesSupport.java:100)
 at org.eclipse.php.internal.core.format.FormatPreferencesSupport.getIndentationChar(FormatPreferencesSupport.java:89)
 at org.eclipse.php.internal.ui.editor.configuration.PHPStructuredTextViewerConfiguration.getIndentPrefixes(PHPStructuredTextViewerConfiguration.java:448)
 at org.eclipse.wst.sse.ui.internal.StructuredTextViewer.configure(StructuredTextViewer.java:311)
 at org.eclipse.php.internal.ui.compare.MergeSourceViewer.configure(MergeSourceViewer.java:542)
 at org.eclipse.php.internal.ui.compare.TextMergeViewer$ContributorInfo.internalSetDocument(TextMergeViewer.java:546)
 at org.eclipse.php.internal.ui.compare.TextMergeViewer$ContributorInfo.setDocument(TextMergeViewer.java:434)
 at org.eclipse.php.internal.ui.compare.TextMergeViewer.updateContent(TextMergeViewer.java:2569)
 at org.eclipse.php.internal.ui.compare.ContentMergeViewer.internalRefresh(ContentMergeViewer.java:814)
 at org.eclipse.php.internal.ui.compare.ContentMergeViewer.inputChanged(ContentMergeViewer.java:704)
 at org.eclipse.jface.viewers.ContentViewer.setInput(ContentViewer.java:274)
 at org.eclipse.compare.CompareViewerSwitchingPane.setInput(CompareViewerSwitchingPane.java:276)
 at org.eclipse.compare.internal.CompareContentViewerSwitchingPane.setInput(CompareContentViewerSwitchingPane.java:158)
 at org.eclipse.compare.CompareEditorInput.internalSetContentPaneInput(CompareEditorInput.java:844)
 at org.eclipse.compare.CompareEditorInput.access$8(CompareEditorInput.java:842)
 at org.eclipse.compare.CompareEditorInput$11.run(CompareEditorInput.java:778)
 at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
 at org.eclipse.compare.CompareEditorInput.feed1(CompareEditorInput.java:772)
 at org.eclipse.compare.CompareEditorInput.feedInput(CompareEditorInput.java:750)
 at org.eclipse.compare.CompareEditorInput.createContents(CompareEditorInput.java:554)
 at org.eclipse.compare.internal.CompareEditor.createCompareControl(CompareEditor.java:456)
 at org.eclipse.compare.internal.CompareEditor.createPartControl(CompareEditor.java:414)
 at org.eclipse.ui.internal.EditorReference.createPartHelper(EditorReference.java:670)
 at org.eclipse.ui.internal.EditorReference.createPart(EditorReference.java:465)

数日無視してたんですが、、地味に困るぜ、、ということで調べてみた。

何でも知ってるGoogle先生に聞けばすぐに判明。
https://bugs.eclipse.org/bugs/show_bug.cgi?id=326227
PDTのバグらしい。

Updateサイトを登録してHelp>Check for Updates
http://download.eclipse.org/tools/pdt/updates/2.2/milestones

PDT2.2が落ちてくるので更新すればOK。

2010年10月20日水曜日

PHPExcel1.7.4の不具合?について

以前PHPExcelのメモリ馬鹿食いについてで触れていたコードがPHPExcel1.7.4では正常に動作しないことが判明しました。

ワークシートオブジェクトのclone処理で、セルへの参照が1.7.4では上手くハンドリングされていないようで、書式が設定されたセルへ値を書き込むと、別のシートの同じセルにも値が書き込まれることがあります。

再現する条件を完全には洗い出せていませんが、書式が何も設定されていないプレーンなセルに対する書き込みは問題ありませんが、以下のような条件で別のシートの同じセルにも値が書き込まれることがあります。

・書き込むセルに既に値が入力されている
・書き込むセルがセル結合されている

他にもワークシート名の変更もちょっと動作が怪しいことがあります。

PHPExcelの1.7.4には不具合があるようです。
というよりも根本的な不具合があるのは1.7.3系で、PHPExcelのメモリ馬鹿食いについてで触れていたメモリリークっぽい現象を修正しようとして失敗しているようです。

セルのデータをキャッシュ(デフォルトではメモリ上に展開?)し、それを利用してセルのコピーやclone処理を行っているようですが、1.7.4系ではデータキャッシュのライブラリが格納されているClasses/PHPExcel/CachedObjectStorage配下が大幅に修正されています。

1.7.4は使用せずに1.7.3cに巻き戻した方が良さそうです。

1.7.3cではPHPExcel_CachedObjectStorage_Memcache.phpに明らかなバグ(単純ミス)があって気持ち悪いので、その箇所のみパッチ当てて使ってます。
ただし、このクラスはPHPExcel_Settingsを通して明示的にmemcachedをキャッシュオブジェクトとして指定しないと使われないようなので、デフォルト設定では実害はないようです。

以下、パッチ。

189行目
- if (!$this->_memcache->addServer($memcacheServer, $memcachePort, false, 50, 5, 5, true, array($this, 'failureCallback')) {
+ if (!$this->_memcache->addServer($memcacheServer, $memcachePort, false, 50, 5, 5, true, array($this, 'failureCallback'))) {

200行目
- throw new Exception('memcache '.$host.':'.$port' failed');
+ throw new Exception('memcache '.$host.':'.$port.' failed');

それから、PHPExcelのメモリリークを調べていた際に参考にさせて頂いたサイト【PHP】PHPExcelがループ内でメモリを使いすぎるで「さらに注意点としては、1.7.3cはaddExternalSheetにバグがあるので、最新のソースをダウンロードした方がいい。」との記述がありますが、これもPHPExcel.phpに以下のパッチを当てれば良さそう。

437行目
- $cell = $sheet->getCell($cellID);
+ $cell = $pSheet->getCell($cellID);

パッチを当てた1.7.3cで当分しのいで、1.7.4の修正版が出るのを待つ作戦をおすすめ。

2010年10月10日日曜日

PHPExcelでセルの背景色指定

背景色の付け方自体は以下の参考リンクから。

PHPExcel-セルのスタイル
PHPExcel(リファレンス1)
PHPExcel セル背景色 & 行と列からアドレスに変換

上記、すごく助かったんですが、実際にどんな値設定すれば何色になるのかリファレンスは見つからず。
PHPExcel_Style_Colorにいくつか有用な定義がされていますが、今一つ何色になるのか分かりにくい。

    const COLOR_BLACK                        = 'FF000000';
    const COLOR_WHITE                        = 'FFFFFFFF';
    const COLOR_RED                            = 'FFFF0000';
    const COLOR_DARKRED                        = 'FF800000';
    const COLOR_BLUE                        = 'FF0000FF';
    const COLOR_DARKBLUE                    = 'FF000080';
    const COLOR_GREEN                        = 'FF00FF00';
    const COLOR_DARKGREEN                    = 'FF008000';
    const COLOR_YELLOW                        = 'FFFFFF00';
    const COLOR_DARKYELLOW                    = 'FF808000';

    public static function indexedColor($pIndex) {
        // Clean parameter
        $pIndex = intval($pIndex);

        // Indexed colors
        if (is_null(self::$_indexedColors)) {
            self::$_indexedColors = array();
            self::$_indexedColors[] = '00000000';
            self::$_indexedColors[] = '00FFFFFF';
            self::$_indexedColors[] = '00FF0000';
            self::$_indexedColors[] = '0000FF00';
            self::$_indexedColors[] = '000000FF';
            self::$_indexedColors[] = '00FFFF00';
            self::$_indexedColors[] = '00FF00FF';
            self::$_indexedColors[] = '0000FFFF';
            self::$_indexedColors[] = '00000000';
            self::$_indexedColors[] = '00FFFFFF';
            self::$_indexedColors[] = '00FF0000';
            self::$_indexedColors[] = '0000FF00';
            self::$_indexedColors[] = '000000FF';
            self::$_indexedColors[] = '00FFFF00';
            self::$_indexedColors[] = '00FF00FF';
            self::$_indexedColors[] = '0000FFFF';
            self::$_indexedColors[] = '00800000';
            self::$_indexedColors[] = '00008000';
            self::$_indexedColors[] = '00000080';
            self::$_indexedColors[] = '00808000';
            self::$_indexedColors[] = '00800080';
            self::$_indexedColors[] = '00008080';
            self::$_indexedColors[] = '00C0C0C0';
            self::$_indexedColors[] = '00808080';
            self::$_indexedColors[] = '009999FF';
            self::$_indexedColors[] = '00993366';
            self::$_indexedColors[] = '00FFFFCC';
            self::$_indexedColors[] = '00CCFFFF';
            self::$_indexedColors[] = '00660066';
            self::$_indexedColors[] = '00FF8080';
            self::$_indexedColors[] = '000066CC';
            self::$_indexedColors[] = '00CCCCFF';
            self::$_indexedColors[] = '00000080';
            self::$_indexedColors[] = '00FF00FF';
            self::$_indexedColors[] = '00FFFF00';
            self::$_indexedColors[] = '0000FFFF';
            self::$_indexedColors[] = '00800080';
            self::$_indexedColors[] = '00800000';
            self::$_indexedColors[] = '00008080';
            self::$_indexedColors[] = '000000FF';
            self::$_indexedColors[] = '0000CCFF';
            self::$_indexedColors[] = '00CCFFFF';
            self::$_indexedColors[] = '00CCFFCC';
            self::$_indexedColors[] = '00FFFF99';
            self::$_indexedColors[] = '0099CCFF';
            self::$_indexedColors[] = '00FF99CC';
            self::$_indexedColors[] = '00CC99FF';
            self::$_indexedColors[] = '00FFCC99';
            self::$_indexedColors[] = '003366FF';
            self::$_indexedColors[] = '0033CCCC';
            self::$_indexedColors[] = '0099CC00';
            self::$_indexedColors[] = '00FFCC00';
            self::$_indexedColors[] = '00FF9900';
            self::$_indexedColors[] = '00FF6600';
            self::$_indexedColors[] = '00666699';
            self::$_indexedColors[] = '00969696';
            self::$_indexedColors[] = '00003366';
            self::$_indexedColors[] = '00339966';
            self::$_indexedColors[] = '00003300';
            self::$_indexedColors[] = '00333300';
            self::$_indexedColors[] = '00993300';
            self::$_indexedColors[] = '00993366';
            self::$_indexedColors[] = '00333399';
            self::$_indexedColors[] = '00333333';
        }

        if (array_key_exists($pIndex, self::$_indexedColors)) {
            return new PHPExcel_Style_Color(self::$_indexedColors[$pIndex]);
        }

        return new PHPExcel_Style_Color();
    }

ということで、Excel標準のカラーパレットに定義されている色のARGBを調べました。
カラーパレット左上から右順で、

FF000000, FF993300, FF333300, FF003300, FF003366, FF000080, FF333399, FF333333,
FF800000, FFFF6600, FF808000, FF008000, FF008080, FF0000FF, FF666699, FF808080,
FFFF0000, FFFF9900, FF99CC00, FF339966, FF33CCCC, FF3366FF, FF800080, FF969696,
FFFF00FF, FFFFCC00, FFFFFF00, FF00FF00, FF00FFFF, FF00CCFF, FF993366, FFC0C0C0,
FFFF99CC, FFFFCC99, FFFFFF99, FFCCFFCC, FFCCFFFF, FF99CCFF, FFCC99FF, FFFFFFFF,

控えておくと地味に役に立つこともあるかも。

2010年9月29日水曜日

Formを作らずにJSでPOST送信

AタグやButtonのonclickなんかを引っ掛けてPOSTしたい場合、hidden要素しか持たない隠しformを作ったりしていましたが、Javascriptだけで処理できたのでメモ。

※参考
HTMLでformを作らずにJavaScriptでPOST送信

/**
 * データをPOSTする
 * @param String アクション
 * @param Object POSTデータ連想配列
 */
function execPost(action, data) {
 // フォームの生成
 var form = document.createElement("form");
 form.setAttribute("action", action);
 form.setAttribute("method", "post");
 form.style.display = "none";
 document.body.appendChild(form);
 // パラメタの設定
 if (data !== undefined) {
  for (var paramName in data) {
   var input = document.createElement('input');
   input.setAttribute('type', 'hidden');
   input.setAttribute('name', paramName);
   input.setAttribute('value', data[paramName]);
   form.appendChild(input);
  }
 }
 // submit
 form.submit();
}

こんな感じで呼び出し。

<a onclick="execPost('/hoge', {'fuga':'fuga_val', 'piyo':'piyo_val'});return false;" href="#">POST送信</a>

2010年9月9日木曜日

MySQLのトランザクションとロック

毎回同じこと調べてる気がするので、メモ。

MySQL5.0.77、InnoDB、デフォルトのAUTOCOMMIT=1

tableテーブル
id, paramカラム


■関係ない行

・A
START TRANSACTION;
INSERT INTO `table` VALUES('1', '1');

・B
SELECT * FROM `table`
// 正常終了(取得結果0件)

・A
COMMIT;

・B
SELECT * FROM `table`
// 正常終了(取得結果1件)


■更新中の行

・A
START TRANSACTION;
UPDATE `table` SET `param` = 'hoge' WHERE `id` = 1;

・B
SELECT * FROM `table`
// 正常終了(取得結果1件、更新前の値)

・A
COMMIT;

・B
SELECT * FROM `table`
// 正常終了(取得結果1件、更新後の値)


■両方更新

・A
START TRANSACTION;
UPDATE `table` SET `param` = 'A' WHERE `id` = 1;
// 正常終了(更新結果1件)

・B
UPDATE `table` SET `param` = 'B' WHERE `id` = 1;
// 待ち状態

・A
COMMIT;
// BプロセスのUPDATEが終了

・B
SELECT * FROM `table`
// 正常終了(取得結果、param='B')


■ロック(READロック)

・A
LOCK TABLES `table` READ;
// 更新処理はエラー

・B
SELECT * FROM `table`
// 正常終了(取得結果1件)
// 更新系クエリは待ち

・A
// 更新処理はエラー
UNLOCK TABLES;
// BプロセスのUPDATEが終了

■ロック(WRITEロック)

・A
LOCK TABLES `table` WRITE;
UPDATE `table` SET `param` = 'A' WHERE `id` = 1;
// 正常終了(更新結果1件)

・B
SELECT * FROM `table`
// 待ち
// 更新系クエリも待ち

・A
UNLOCK TABLES;
// BプロセスのSELECTが終了
// BプロセスのUPDATEが終了


■ロックとトランザクション(ロックしてからトランザクション開始)

・A
LOCK TABLES `table` WRITE;

・B
SELECT * FROM `table`
// 待ち

・A
START TRANSACTION;
// START TRANSACTIONする前のアクティブなトランザクションは閉じられる。
// 1回目のLOCK TABLESが開放されて、BプロセスのSELECTが終了


■ロックとトランザクション(トランザクション発行してからREADロック)

・A
START TRANSACTION;
UPDATE `table` SET `param` = 'A' WHERE `id` = 1;

・B
SELECT * FROM `table`
// 正常終了(取得結果1件、更新前)

・A
LOCK TABLES `table` READ;
// 開始していたトランザクションが閉じられる

・B
SELECT * FROM `table`
// 正常終了(取得結果1件、更新後)

■ロックとaoutocommit

・AUTOCOMMITを無効にしていてもUNLOCK時にはコミットされる
SET AUTOCOMMIT=0;
LOCK TABLES `table` WRITE;
UPDATE `table` SET `param` = 'hoge5' WHERE `id` = 1;

UNLOCK TABLES;
// hoge5

・ROLLBACKすればOK
SET AUTOCOMMIT=0;
LOCK TABLES `table` WRITE;
UPDATE `table` SET `param` = 'hoge6' WHERE `id` = 1;

ROLLBACK;
UNLOCK TABLES;
// hoge5

・でもAUTOCOMMITが有効なら意味無い
SET AUTOCOMMIT=1;
LOCK TABLES `table` WRITE;
UPDATE `table` SET `param` = 'hoge6' WHERE `id` = 1;

ROLLBACK;
UNLOCK TABLES;
// hoge6

■まとめ
・トランザクション(ロック)の発行はそれまでのロック(トランザクション)が閉じられるので1シーケンスの中にトランザクションとロックの発行を混在させないこと。
・トランザクションが終わるまで他のプロセスに値がreadされるとまずい場合、行ロックやテーブルロック。
・ロック時にはAUTOCOMMITを無効にして明示的にCOMMITすること。
・readされるとまずい場合は行ロックでは辛いので現実的にはテーブルロック