/**
* @version $Id: AbstractDaoConnector.java 997 2013-06-05 01:58:11Z yukihiro-kinjyo $
*
* 2011/11/04 01:04:11
* @author imai-yoshikazu
*
* Copyright 2011-2014 TIDAコンソーシアム All Rights Reserved.
*/
package com.tida_okinawa.corona.io.dam.hibernate.connector.impl;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import com.tida_okinawa.corona.io.dam.hibernate.connector.DaoConnecter;
/**
* DB読み書きのクラス
*
* 大きいテーブルで、値をオンデマンドで取得するのに使う
*
* TODO: 不要になったときにThreadを破棄しないと keys ごと残ってしまう
*
* @author imai
*
* @param <K>
* キーの型
* @param <T>
* レコードのデータの型
*/
abstract public class AbstractDaoConnector<K, T> implements DaoConnecter<K, T> {
/**
* commit待ち
*/
private ArrayBlockingQueue<Data> queue;
/**
* commitを実行するスレッド
*/
protected Thread committer;
/**
* committerへの終了指示
*/
private AtomicBoolean isClosed = new AtomicBoolean(false);
/**
* キーの一覧
*
* note: new K[0] ができないのでSetにしている
*/
protected Set<K> keys;
AbstractDaoConnector() {
/* コミット用のキューを用意 */
queue = new ArrayBlockingQueue<Data>(100);
}
/**
* コミット用スレッドを開始
*/
private void startCommitter() {
/* スレッドを起動 */
/* 以降 commit() が呼ばれるごとにコミットを実行 */
isClosed.set(false);
/* コミット用のスレッドを用意 */
committer = new Thread() {
List<Data> data = new ArrayList<AbstractDaoConnector<K, T>.Data>();
@Override
public void run() {
try {
Data d = null;
int len = 0;
while (!(isClosed.get() && queue.isEmpty())) {
d = queue.poll(10, TimeUnit.MILLISECONDS);
if (d != null) {/* データがある時 */
int curLength = d.key.toString().length() + d.value.toString().length();
if ((len + curLength) >= 330000) {
/*
* データがnullでない、かつ、リストに追加したデータの総文字数が330000byte以上になる場合
* 文字列MAXが1000000。 UTF-8で日本語は大体3byte。
* 1000000/3=333333なので、330000byte
* MySQLは4byte文字を扱えないので、考慮しないでいいらしい!
*/
doCommit(data);
data.clear();
len = 0;
}
data.add(d);
len += curLength;
}
}
if (data.size() > 0) {/* 余りの端数分があれば、doCommitに投げる */
doCommit(data);
}
} catch (InterruptedException e) {
}
}
};
committer.start();
}
/**
* コミット用スレッドを終了
*/
private void stopCommitter() {
isClosed.set(true);
try {
committer.join();
} catch (InterruptedException e) {
//
}
committer = null;
}
/**
* {@link Connection} を取得する
*
* @return
* @throws HibernateException
* DB接続に失敗した
*/
abstract Session getConnection() throws HibernateException;
@Override
public Set<K> getKeys() {
try {
/* コミット終了まで待つ */
while (committer != null) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
String strSQL = createStatementForGetKeys();
Session connection = getConnection();
String checkSql = "Select rec_id, pattern_id From"; //$NON-NLS-1$
int endIndex = checkSql.length();
if (strSQL.length() > endIndex && checkSql.equals(strSQL.subSequence(0, endIndex))) {
@SuppressWarnings("unchecked")
List<Object[]> ids = connection.createSQLQuery(strSQL).list();
if (ids == null) {
return new HashSet<K>(0);
}
keys = new HashSet<K>(10000);
for (Object[] id : ids) {
K key = getKey(id);
if (key != null) {
keys.add(key);
}
}
} else {
@SuppressWarnings("unchecked")
List<Object> ids = connection.createSQLQuery(strSQL).list();
if (ids == null) {
return new HashSet<K>(0);
}
keys = new HashSet<K>(10000);
for (Object id : ids) {
Object[] idArr = new Object[2];
idArr[0] = id;
K key = getKey(idArr);
if (key != null) {
keys.add(key);
}
}
}
} catch (ClassCastException ex) {
ex.printStackTrace();
keys = new HashSet<K>(0);
} catch (HibernateException e) {
e.printStackTrace();
keys = new HashSet<K>(0);
}
return keys;
}
/**
* キーの一覧を取得するためのSQLを作る
*
* @return
*/
abstract protected String createStatementForGetKeys();
/**
* {@link #createStatementForGetKeys()}の結果からキーを取得する
*
* @param rs
* @return
*/
abstract protected K getKey(Object[] rs) throws HibernateException;
@Override
public int size() {
if (keys == null) {
keys = getKeys();
}
return keys.size();
}
/**
* get() で使う Sql(Hqlとしてでは使用しない)
*/
protected String stmtForGet;
@Override
public T get(K key) {
try {
Session session = getConnection();
String strSqlExec;
if (stmtForGet == null) {
String strSQL = prepareStatementForGet();
stmtForGet = strSQL;
}
strSqlExec = setParamForGet(stmtForGet, key);
@SuppressWarnings("unchecked")
List<Object[]> list = session.createSQLQuery(strSqlExec).list();
if (list != null) {
if (list.size() > 0) {
T value = getResultDirect(list);
return value;
}
}
} catch (HibernateException e) {
System.err.println(e + ":" + key); //$NON-NLS-1$
}
return null;
}
/**
* {@link DaoConnecter#get(Object)} のためのSQLを用意
*
* @return
* @throws HibernateException
*/
abstract protected String prepareStatementForGet() throws HibernateException;
/**
* {@link #prepareStatementForGet()} で用意したSQLにパラメータをセットする
*
* @param key
*/
abstract protected String setParamForGet(String stmt, K key) throws HibernateException;
/**
* クエリーの結果から値を取得する
*
* @param rs
* @return
*/
abstract protected T getResultDirect(List<Object[]> list) throws HibernateException;
/**
* コミット用のレコード
*/
class Data {
final K key;
final T value;
Data(K key, T value) {
this.key = key;
this.value = value;
}
}
@Override
public synchronized void commit(K key, T value) {
Data data = new Data(key, value);
if (committer == null) {
startCommitter();
}
try {
queue.put(data);
} catch (InterruptedException e) {
}
}
/**
* コミット実行部分<br/>
* 今、このメソッドは使われていない。2012/02/17
*
* @param data
*/
@SuppressWarnings("unused")
protected void doCommit(K key, T value) {
// TODO 20131111 jdbc版での呼び出し階層での確認では未使用となっている。
}
protected void doCommit(List<Data> datas) {
/*
* 問い合わせデータのレコード量が多いとき、最後までstmtを閉じないとメモリが足りなくなる。
* 毎回生成とcloseを行う。
*/
String strSqlExec;
Session session = null;
try {
String strSQL = prepareStatementForCommit(datas.size());
session = getConnection();
strSqlExec = setParamForCommit(strSQL, datas);
/* トランザクション開始 */
if (session.getTransaction().isActive()) {
System.out.println("Transactions are likely to be nested");
session.getTransaction().commit();
}
session.beginTransaction();
session.createSQLQuery(strSqlExec).executeUpdate();
session.flush();
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
} finally {
/* トランザクション中だった場合はロールバックする */
if (session.getTransaction().isActive()) {
session.getTransaction().rollback();
}
}
}
/**
* {@link DaoConnecter#commit(Object, Object)} 用のSQLを用意
*
* @return
*/
abstract protected String prepareStatementForCommit();
/**
* {@link DaoConnecter#commit(Object, Object)} 用のSQLを用意(複数レコード用)
*
* @param size
* 1以上の値
* @return
*/
abstract protected String prepareStatementForCommit(int size);
/**
* {@link #commit(Object, Object)}のSQLにパラメータをセットする
*
* @param stmt
* @param key
* @param value
*/
abstract protected String setParamForCommit(String stmt, K key, T value) throws HibernateException;
/**
* {@link #commit(Object, Object)}のSQLにパラメータをセットする
*
* @param stmt
* @param key
*/
abstract protected String setParamForCommit(String stmt, List<Data> data) throws HibernateException;
@Override
abstract public void clear();
@Override
public void close() {
try {
if (committer != null) {
/* データコミット時は毎回クローズするようにした */
stopCommitter();
}
if (stmtForGet != null) {
stmtForGet = null;
}
} catch (HibernateException e) {
System.err.println(e);
}
}
}