2007/12/11

ibatisのはまりどころ:JDBCタイプのARRAYを利用するその2

ibatisで配列を参照できるようになったと思ったら更新ではまった。

ARRAYタイプの更新は当然JDBC実装に依存する。
僕が利用していたDBはPostgres。

JDBC規約?によればsetObjectを利用すべしとのこと。
でもJDBCソースみてみるとsetObject内には配列時の対応はないし、setArrayを利用するにもArrayはIFの為、オブジェクトの生成しずらい。
Postgresは配列対応なのになんでそういう実装がないの?
Webで検索してもよい資料はでてこない。

なので以下のような対応をしました。
一応、これでできることはできるんだけどパフォーマンスがでないだろうなー。

本気でやるならJDBCを拡張するかしたほうがよいと思うがなんでみんな対応しないんだろう?

// -- 以下コード
import java.sql.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.List;


import com.ibatis.sqlmap.client.extensions.ParameterSetter;
import com.ibatis.sqlmap.client.extensions.ResultGetter;
import com.ibatis.sqlmap.client.extensions.TypeHandlerCallback;

public class ArrayTypeHandlerCallback implements TypeHandlerCallback {



public Object getResult(ResultGetter resultGetter) throws SQLException {
Array array = resultGetter.getArray();
if (array == null)
return null;
else
return Arrays.asList((Object[])array.getArray());
}

public void setParameter(ParameterSetter parameterSetter, Object obj)
throws SQLException {

if(obj == null){
parameterSetter.setNull(Types.ARRAY);
} else {

List lst = (List)obj;
parameterSetter.setArray(createArray(parameterSetter, lst));
}

}

public Object valueOf(String str) {
// 特につかっていないので今回は実装しない
return "";
}

public Array createArray(ParameterSetter parameterSetter, List list) throws SQLException {

Array array = null;
PreparedStatement ps = null;
try {

String arrayPhrase = toArrayPhrase(list);
if (arrayPhrase == null)
return null;

ps = parameterSetter.getPreparedStatement();
Connection cn = ps.getConnection();
String sql = "SELECT " + arrayPhrase;
ps = cn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
rs.next();
array = rs.getArray(1);
}
finally {

// 初回のPreparedStatementはIbatisによって制御されるのでcloseはしない
if (ps != null)
ps.close();
}

return array;

}

public String toArrayPhrase(List list) {

if (list == null || list.isEmpty())
return null;

int cnt = 0;
int max = list.size();
String value = null;
StringBuffer sb = new StringBuffer();
sb.append("ARRAY[");
for(Object obj : list) {

cnt++;

value = decorateSQLValue(obj);
if (cnt != max)
sb.append(value + ",");
else
sb.append(value);

}
sb.append("]");

return sb.toString();
}

public String decorateSQLValue(Object obj) {

if (obj == null)
return null;

if (obj instanceof String) {
return "'" + obj.toString() + "'";
}
else if (obj instanceof Integer) {
return obj.toString();
}
else {
return "'" + obj.toString() + "'";
}

}

}

2007/11/28

DBのはまりどころ

よく実行するSQLのパフォーマンスを向上させるために
javaのpreparedstatementにてSQLを実行する。

昨今のフレームワークもSQLはプリコンパイルした形で実行することが多い。
SQLインジェクション対策にもなっている。

これは一見、よさそうに見える。
但し、以下の点に注意
パラメータ数が多いもの。
この場合、SQLをプリコンパイルするのにかなりの時間を要するので逆に
パフォーマンス低下を発生させかねない。
簡単な確認しか行っていないがPostgres8.2.5ではだいたい100以上のパラメータ
をつけると急激にパフォーマンス低下が発生した。
みなさんどうですか?

2007/11/07

全文検索時に利用できるパラメータトークンロジック

Pattern pattern = Pattern.compile("(((?<=\\s| |\\A)\".+?\"(?=\\s| |\\Z))|((?<=\\s| |\\A)[^\\s| ]+?(?=\\s| |\\Z)))");
Matcher matcher = pattern.matcher("\"aaaa  \" bbbb  \" cccc\" dddd");
StringBuffer sb = new StringBuffer();
while (matcher.find()) {

System.out.println("発見0:[" + matcher.group(0) + "]");

}
matcher.appendTail(sb);
System.out.println(sb.toString());

または

// Pattern pattern = Pattern.compile("(((?<=\\s| |\t|\\A)\".+?\"(?=\\s| |\t|\\Z))|((\\s|\t| )+))");
// Pattern pattern2 = Pattern.compile("((?<=\\s| |\t|\\A)\".+?\"(?=\\s| |\t|\\Z))");
// Matcher matcher = pattern.matcher("\"aaaa  \" bbbb   \" cccc\" dddd");
// Matcher matcher2;
// StringBuffer sb = new StringBuffer();
// while (matcher.find()) {
//
// System.out.print("発見:[" + matcher.group(0) + "]");
// matcher2 = pattern2.matcher(matcher.group(0).toString());
// // 「"」でかこまれた文字列
// if (matcher2.find()) {
// System.out.println("0:[" + matcher.group(0) + "]");
// matcher.appendReplacement(sb, " +" + matcher.group(0));
// }
// // 単純な空白文字列
// else {
// System.out.println("1:[" + matcher.group(0) + "]");
// matcher.appendReplacement(sb, " +");
// }
// }
// matcher.appendTail(sb);
// System.out.println(sb.toString());

2007/09/14

ibatisのはまりどころ:JDBCタイプのARRAYを利用する

いやー、ぜんぜんドキュメントがないのでまいりました。はまること1時間。やっとみつけました。
これはツールadabarで行う方法ですが直接SQLMAP.xmlに記述でもできると思います。
ではそのやりかたですが


javaType="int" jdbcType="INTEGER" />
javaType="String" jdbcType="VARCHAR" />
javaType="int" jdbcType="INTEGER" />
javaType="java.sql.Array" jdbcType="ARRAY"
typeHandler="com.ibatis.sqlmap.engine.type.ObjectTypeHandler" />


ポイントはtypeHandlerの指定とそれをフルパスで指定すること

そうすれば以下のように
ShainDAO dao =
(ShainDAO) daoManager.getDao(ShainDAO.class);
Shain shain = new Shain();
// shain.setShainId(111);
shain = dao.selectByPrimaryKey(111);
System.out.println(shain.getShainName());
String[] a = (String[])shain.getColary().getArray();
System.out.println(a[0]);

で取得できます。
ibatis便利だー。

2007/09/11

ibatisのはまりどころ:データをDAOを利用してインサート時

インサートするテーブルの主キーがシリアルタイプの場合(オートナンバー)
Eclipseツールのabatorを利用してDAO作成時にabatorConfig.xmlの設定ファイルとして
以下のようにする。





但し、PostGresの場合だけあとは固定値のMySQLとかSQLServerとかを設定。

2007/09/03

postgresのはまりどころ:Lower関数に泣く!

initdbのときにロケールなしで指定しておかないとSQL関数のlower等で以下のようなエラーが発生。

ERROR: invalid multibyte character for locale
SQLステート:22021
ヒント:The server's LC_CTYPE locale is probably incompatible with the database encoding.



回避方法はDBのデータを一旦、pgdumpall等で退避しておいて以下のコマンドで再度DB構築
initdb --locale=C 各種オプション...

以上

2007/09/02

phpのはまりどころ:sendmailではまる

linuxでmail関数等を利用する場合にsendmail_pathの設定が必要になるがそのほかに
sendmailのサービスをたちあげておかなければならない。

>sendmail-8.12以降ではセキュリティ強化のため mailコマンドによる
>メール送信の際にも MTA(sendmailデーモン)が必要となっています。

更にapacheユーザーがsendmailのメールキューをためておくディレクトリ
「/var/spool/clientmqueue/」の参照、書込み権限等がないだめ。
/etc/groupのsmnpユーザーにに追加しよう。


2007/08/28

CIのはまりどころ[]

CIのキャッシュは一度よまれるとそれ以降はキャッシュが有効なかぎり実際のメソッドはよばれはない。
あたまえだが、実装としてはさらにメソッド毎にキャッシュするかどうかを設定できるといいな。
とおもって結局自分でつくりました。

2007/08/27

変数に設定した関数名を指定して実行する

function bbbb() {

echo "BBBB";
}

class a1 {

var $m;
var $m2;
var $m3;

function a1() {


$this->m = "aaaa";
$mt = $this->m;
$this->$mt();

$this->m2 = "$this->aaaa";
$mt2 = $this->m2;
$mt2;

/*これはだめうーん
ほかにやり方があるとおもうんだけど・・・
$this->m3 = "bbbb";
$mt3 = $this->m3;
$mt3();
*/
}

function aaaa() {

echo "AAAA";


}

}

$obj = new a1();

2007/08/21

Axis2のAXIOMでXPATHのはまりどころ



1111
2222



これがOMElementのelementに格納されていてaa要素すべてを取得する場合
普通は以下のように思うはず
AXOMXPath xp = new AXOMXPath("//aa");
List lstOmeAA = xp.selectNodes(element);

だがこれだと取得できず。
なぜなら暗黙のプレフィックスが存在していてxpath式の「aa」という要素名は存在しない。
じゃあどのように取得するのかというと

AXOMXPath xp = new AXOMXPath("//xs:aa");
xp.addNamespace("xs", "http://sample.org");
List lstOmeAA = xp.selectNodes(element);

てな感じ。
どこにもかいとらんよ。こんなの。
でもPHP4のXPATH関連の関数使ったときもこれとおんなじ挙動だった。
もしかしてXPATH関連の関数ワールドではあたりまえ?

2007/08/08

prototype.jsでのはまりどころ($Fについて)

$Fはドロップダウンでもテキストボックスでもvalueの値を取得できるので便利だが
IE6とFF2では多少挙動が違う。
ドロップダウンでname属性のみ宣言されているときに$FはIE6では取得できるがFF2ではエラーになってしまう。そこでidとnameともに同様な値で宣言しておけば$Fは取得できる。

prototype.jsでのはまりどころ

よくメソッドの名前につかわれる「update」とかを単体で関数宣言時に利用しないこと。
「update~」とかはOK。でないと挙動がおかしくなる。

2007/08/07

prototype.jsのハッシュキーの削除

ハッシュの内容、キーを削除したい場合は以下
マニュアルにはのっとらんのよ

var prm = $H({});
prm["a"] = "aa";
prm["b"] = "aa";

prm.remove("a");

2007/08/06

redmineのインストールで注意点

注意点を以下にあげておく。
①プロキシー越えでruby on railsのインストールをする場合
多くのサイトでは以下のように
gem install rails --include-dependencies -p http://exsample:ポート番号
というふうにインストールするよう記述しているが場合によっては失敗することがある。
その場合は環境変数を先にセットしておいてから行うとよい。
windowsならset http_proxy=http://exsample:ポート番号
linux-bashならexport http_proxy=http://exsample:ポート番号
で行ってから
gem install rails --include-dependencies -r
を実行。1回目の実行でたまに異常終了することもあるが再度実行しなおせばうまくいく。

2007/08/02

URLの一部に「/」をエンコードした値を利用したいとき

apacheのhttp.confの設定で
AllowEncodedSlashes on
にすること。
検索クエリーとかもこれで「q=***」
でなく「/***/」みたいにあたえられるよ。
でもGoogleは「q=***」だけど・・・。
他にもURLの文字として含むとエラーになる文字があるのかも?

2007/08/01

tomcat5.5のデータソースの設定

■server.xmlにデータソースの設定

■context.xmlの用意
(1) %CATALINA_HOME%\conf\にContext.xmlを作成
 全てのアプリケーションのデフォルトコンテキストになります。
(2) %CATALINA_HOME%\conf\Catalina\localhostにContext.xmlを作成
 上のディレクトリはデフォルトのままの場合です。localhost、つまりTOMCATをインストールしたPC上の全てのアプリケーションのデフォルトコンテキストになります。
(3) %CATALINA_HOME%\conf\Catalina\localhostにTestAp.xmlを作成
 上のディレクトリはデフォルトのままの場合です。アプリケーション用ディレクトリをいくつか作る場合、つまりコンテキストをいくつか作るときに、アプリケーション毎にコンテキスト設定を分ける方法です。
(4) %CATALINA_HOME%\webapps\TestAp\META-INF\にContext.xmlを作成
 アプリケーション用ディレクトリにMETA-INFというディレクトリを作成し、そこに Context.xmlを作成する方法です。上の(1)~(3)でTestApが定義されていないとき、ここを見に行きます。Javaプログラムを TOMCATに配備(デプロイ)するときに、warファイルというものを作って行う方法があります。その場合、コンテキスト定義も一緒に配備出来るので便利かもしれません。

2007/07/26

prototype.jsのハッシュキーに変数を利用する場合

通常、$Hでハッシュを利用する場合は以下のように
var h = $H({
'aaa' : 111,
'bbb' : 222,
});

と思いますがキーに変数を利用した場合は
var prmA = 'p1';
var prmB = 'p2';
var h = $H({
prmA : 111,
prmB : 222
});
とやりたいところですがこれだとキー名は「prmA」「prmB」
になってしまいます。eval等を利用してスクリプトとして全文字列を生成してから行っても
いいのですがちょっと面倒なので以下のように
var prmA = 'p1';
var prmB = 'p2';
var h = $H({});// ←new Object()ではダメ
h[prmA] = 111;
h[prmAB = 222;
とすることで簡単にできます。

2007/07/25

改行コードを
に変換する
改行コードには\r、\n、\r\nがあります。
それぞれ、OSなどによって差異があるわけですが、これを正規表現で「\r\nか\rか\n」という条件で
に変換します。
これはreplace(/\r\n|\r|\n/g,"
")で出来るわけですが、
http://tech.bayashi.net/pdmemo/chara.html
を参考に改行コード値(\r=\x0D、\n=\x0A、\r\n=\x0D\x0A)として変換します。
つまり、replace(/\x0D\x0A|\x0D|\x0A/g,"
")を使います。

CIでページキャッシュの拡張

CI付属のキャッシュ機能だとGETパラメータは省いたURLごとにキャッシュをする。
でも実際にはGETのパラメータも含めたURL毎にキャッシュすることが多いいのでコアライブラリを拡張する。拡張方法はCIのドキュメントをみてね。簡単にできますよ。

class MY_Output extends CI_Output {

function MY_Output()
{
parent::CI_Output();
log_message('debug', "MY_Output Class Initialized");
}

function _get_prm() {


if (isset($_GET) && count($_GET) != 0) {
$qryPrm = $_GET;

// IEのキャッシュ対策で時間をパラメーターとして付加している
// その部分のパラメータは考慮しない。「h」がキー
unset($qryPrm["h"]);
ksort($qryPrm);
return $qryPrm;
}
return "";
}

function _concatQryPrm($hm, $emptyExecFlg = true) {

$ret = null;

if ($hm == null || count($hm) == 0) {

return $ret;

}

$keys = array_keys($hm);
for ($cnt = 0; $cnt < count($keys); $cnt++) {

$key = $keys[$cnt];
if ($hm[$key] === null) {
if ($emptyExecFlg == false) {
continue;
}
}

if ($cnt == 0)
$ret .= $key . "=" . $hm[$key];
else
$ret .= "&" . $key . "=" . $hm[$key];

}

return $ret;

}



/**
* Write a Cache File
*
* @access public
* @return void
*/
function _write_cache($output)
{
$CI =& get_instance();
$path = $CI->config->item('cache_path');

$cache_path = ($path == '') ? BASEPATH.'cache/' : $path;

if ( ! is_dir($cache_path) OR ! is_writable($cache_path))
{
return;
}

// $uri = $CI->config->item('base_url').
// $CI->config->item('index_page').
// $CI->uri->uri_string();

// GETパラメータも含める
$q = $this->_concatQryPrm($this->_get_prm());
$q = ($q == "" ? "" : "?" . $q);
$uri = $CI->config->item('base_url').
$CI->config->item('index_page').
$CI->uri->uri_string().
$q;

$cache_path .= md5($uri);

if ( ! $fp = @fopen($cache_path, 'wb'))
{
log_message('error', "Unable to write ache file: ".$cache_path);
return;
}

$expire = time() + ($this->cache_expiration * 60);

flock($fp, LOCK_EX);
fwrite($fp, $expire.'TS--->'.$output);
flock($fp, LOCK_UN);
fclose($fp);
@chmod($cache_path, 0777);

log_message('debug', "Cache file written: ".$cache_path);
}

// --------------------------------------------------------------------

/**
* Update/serve a cached file
*
* @access public
* @return void
*/
function _display_cache(&$CFG, &$RTR)
{
$CFG =& load_class('Config');
$RTR =& load_class('Router');

$cache_path = ($CFG->item('cache_path') == '') ? BASEPATH.'cache/' : $CFG->item('cache_path');

if ( ! is_dir($cache_path) OR ! is_writable($cache_path))
{
return FALSE;
}

// Build the file path. The file name is an MD5 hash of the full URI
// $uri = $CFG->item('base_url').
// $CFG->item('index_page').
// $RTR->uri_string;

// GETパラメータも含める
$q = $this->_concatQryPrm($this->_get_prm());
$q = ($q == "" ? "" : "?" . $q);
$uri = $CFG->item('base_url').
$CFG->item('index_page').
$RTR->uri_string.
$q;

$filepath = $cache_path.md5($uri);

if ( ! @file_exists($filepath))
{
return FALSE;
}

if ( ! $fp = @fopen($filepath, 'rb'))
{
return FALSE;
}

flock($fp, LOCK_SH);

$cache = '';
if (filesize($filepath) > 0)
{
$cache = fread($fp, filesize($filepath));
}

flock($fp, LOCK_UN);
fclose($fp);

// Strip out the embedded timestamp
if ( ! preg_match("/(\d+TS--->)/", $cache, $match))
{
return FALSE;
}

// Has the file expired? If so we'll delete it.
if (time() >= trim(str_replace('TS--->', '', $match['1'])))
{
@unlink($filepath);
log_message('debug', "Cache file has expired. File deleted");
return FALSE;
}

// Display the cache
$this->_display(str_replace($match['0'], '', $cache));
log_message('debug', "Cache file is current. Sending it to browser.");
return TRUE;
}


}

2007/07/24

PostgreSQLをチューニング

PostgreSQLをチューニングする機会があったので
その時に調べたチューニング項目を備忘録として残しておきます。
バージョンの違いやサーバの規模などによっても
効果は変わってくると思うのであくまで参考程度のものですが。

・shared_buffers
 7系では8000〜10000程度まで引き上げる
 8系では150000程度まで引き上げることが可能、100000程度が性能のピーク
 これに多く割り当てるよりOSのバッファ領域として使う方が性能が向上する
 テーブルサイズを割り出して設定するのがベスト
 簡単に設定するなら搭載メモリ量の1/4、搭載メモリが多ければ1/2ぐらいでも可
・max_connections
 7系では256程度、8系では1000程度が性能のピーク
・work_mem(sort_mem)
 適切なサイズに調整する、2048〜4096程度
 プロセス毎に領域が割り当てられるので多すぎるとスワップする可能性も
 ソートメモリはORDER BYだけではなくMerge Join、CREATE INDEX時でも使用される
・deadlock_timeout
 長めに設定する
 1000ms×同時セッション数が理想
・effective_cache_size
 数GBメモリ積んだマシンなら総メモリの1/4〜1/2を設定
・wal_buffers
 トランザクションが多い、大きい場合に多く取っておくと書き込み効率が良くなる。32〜64程度。
・commit_delay
 同時トランザクション数が多い場合、0以上を設定すると書き込み効率が上がる
・random_page_count
 余程巨大なテーブルが無い限り数GBのメモリのマシンなら2〜3が適当
・max_fsm_pages
 小さすぎるとクラスタ容量が増えすぎる、大きすぎると検索オーバーヘッドが大きくなる、vacuum -vの情報を見て調節
・bgwriter_maxpages
 参照系DBなら値を小さく、トランザクションが多いDBなら大きく設定する
・checkpoint_segments
 大きく設定するとハードディスクへの書き込み頻度が減り性能が向上する

・VACUUM FULLをしてもインデックスの領域は切り詰められないのでREINDEXを行い無駄な領域を削除する
・複数のハードディスク上のテーブルスペースにテーブルを作ることによりIOが分散し効率が良くなる
・walログも別ハードディスクに移すと結構IO効率が上がる
・fstabのマウント情報にnoatimeを指定し、atimeの更新を抑えると若干性能アップ

2007/07/12

PHPでUTF-8の文字数をShift-jisの文字数にてカウントする方法

$str = "12345あいうえお12345";
$str = mb_convert_encoding($str, "shift-jis", "UTF-8");
$str = mb_strcut($str, 0, 10, "shift-jis");
$str = mb_convert_encoding($str, "UTF-8", "shift-jis");
echo($str);
?>

2007/06/27

CIのはまりどころ_クッキーヘルパーにて

以下のようにしないと第2引数までしか指定しない場合に有効期限がセットした日から30日前になってしまいます。

function set_cookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '')
{
if (is_array($name))
{
foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'name') as $item)
{
if (isset($name[$item]))
{
$$item = $name[$item];
}
}
}

// Set the config file options
$CI =& get_instance();

if ($prefix == '' AND $CI->config->item('cookie_prefix') != '')
{
$CI->config->item('cookie_prefix');
}
if ($domain == '' AND $CI->config->item('cookie_domain') != '')
{
$CI->config->item('cookie_domain');
}
if ($prefix == '/' AND $CI->config->item('cookie_path') != '/')
{
$CI->config->item('cookie_path');
}

if ( ! is_numeric($expire))
{

$expire = time() + 86500;
// $expire = time() - 86500; //<-これバグでないの?

}
else
{


if ($expire > 0)
{
$expire = time() + $expire;
}
else
{
$expire = 0;
}
}

setcookie($prefix.$name, $value, $expire, $path, $domain, 0);
}

CI

2007/06/22

AjaxのresponseXMLにてはまる

IE6とFF2ではネームスペースの対応が違う。
たとえば以下ようなXMLがあったとき。
xsd:a xmlns:xsd="http:/sample.com/xsd"
xsd:bこれはテスト