2015年12月15日火曜日

psql上でOracleのSQL*Plusのeditみたいなことをする

psql上では、\eで前回実行したSQL文をエディタ上で編集できるようになる。
エディターは、EDITOR環境変数に設定されたパスのものが開かれる。

2015年12月14日月曜日

MySQLは ON UPDATE CURRENT_TIMESTAMPで更新日時を自動的に更新できる

MySQLのタイムスタンプ型では、DEFAULT句に追加でon updateをつけることで更新時にも日時を自動設定できるらしいので試してみた。
試した時に使ったバージョンはMySQL Communityの5.7.10です。


DDLとテーブル定義

テーブル定義を見ると、on updateを指定したカラムはExtra列で確認することができる。
create table test_table (
    id int AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    last_modified TIMESTAMP NOT NULL DEFAULT current_timestamp ON UPDATE current_timestamp,
    PRIMARY KEY (id)
)

mysql> desc test_table;
+---------------+--------------+------+-----+-------------------+-----------------------------+
| Field         | Type         | Null | Key | Default           | Extra                       |
+---------------+--------------+------+-----+-------------------+-----------------------------+
| id            | int(11)      | NO   | PRI | NULL              | auto_increment              |
| name          | varchar(100) | NO   |     | NULL              |                             |
| last_modified | timestamp    | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+---------------+--------------+------+-----+-------------------+-----------------------------+

結果

insertやupdate時に対象のカラムを指定しないと、自動的にcurrent_timestampの値が入ることがわかる。
update時には、カラムの値が変更されている場合のみ、対象カラムのtaimustampが変更されている。

なお、insertやupdate時に任意の値を直接設定することもできる。
-- INSERT
mysql>  insert into test_table (name) values ('hoge');
Query OK, 1 row affected (0.07 sec)

-- 自動的にtimestampが設定される。
mysql> select * from test_table;
+----+------+---------------------+
| id | name | last_modified       |
+----+------+---------------------+
|  1 | hoge | 2015-12-14 12:24:09 |
+----+------+---------------------+
1 row in set (0.00 sec)

-- UPDATE
mysql> update test_table set name = 'fuga';
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- timestampの値が更新される。
mysql> select * from test_table;
+----+------+---------------------+
| id | name | last_modified       |
+----+------+---------------------+
|  1 | fuga | 2015-12-14 12:24:32 |
+----+------+---------------------+
1 row in set (0.03 sec)

-- 値が変更されないUPDATE
mysql> update test_table set name = 'fuga';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

-- timestampは更新されない
mysql> select * from test_table;
+----+------+---------------------+
| id | name | last_modified       |
+----+------+---------------------+
|  1 | fuga | 2015-12-14 12:24:32 |
+----+------+---------------------+
1 row in set (0.00 sec)

2015年12月8日火曜日

DBMS_XPLAN.DISPLAY_CURSORを使って最近実行したSQL文のアクセスプランを取得

マニュアルによると、「DISPLAY_CURSORファンクションはカーソル・キャッシュにロードされている任意のカーソルの実行計画を表示します」とあるので、
実行されてからあまり時間がたっていないSQLであれば簡単にアクセスプランが見れる。

DISPLAY_CURSORは、デフォルト動作ではそのセッションの最後のカーソルのアクセスプランを確認することができる。
また、SQL_IDを指定することで任意のSQL文のアクセスプランも簡単に確認することができる。


実行したSQL

select /* hoge */ 1 from dual;

v$sqlからSQLIDを取得

SQL> select sql_id, child_number from v$sql where sql_text like '%hoge%' and sql_text not like '%v$sql%';

SQL_ID        CHILD_NUMBER
------------- ------------
42mk8byafr2yx            0

DISPLAY_CURSORを使ってアクセスプランの表示

SQL> select * from table(DBMS_XPLAN.DISPLAY_CURSOR('42mk8byafr2yx', 0));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
SQL_ID  42mk8byafr2yx, child number 0
-------------------------------------
select /* hoge */ 1 from dual

Plan hash value: 1388734953

-----------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT |      |       |     2 (100)|          |
|   1 |  FAST DUAL       |      |     1 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------

DISPLAY_CURSORの3番めの引数のformatを指定すると、実行計画の詳細レベルを変更することができる。
詳細は、「PL/SQLパッケージおよびタイプ・リファレンス」を見るとわかる。


2015年12月7日月曜日

HibernateのImplicitNamingStrategyを使って識別子に任意のプレフィックス等を付加する

Hibernateでテーブル名やカラム名などに一律任意のサフィックスやプレフィックスを追加するには、ImplicitNamingStrategyを実装したクラスを作成し設定を追加しするだけでよい。
PhysicalNamingStrategyを使った識別子の変更とは異なり、明示的に識別子を指定した場合は、このクラスは呼び出されない。また、ImplicitNamingStrategyのほうが、命名ルールを細かく設定できる。

ImplicitNamingStrategyの実装クラス

JPAの場合はデフォルト実装のImplicitNamingStrategyJpaCompliantImplを継承する。
PhysicalNamingStrategyとくらべてかなり多くのメソッドが定義されているけど、テーブル名であればdeterminePrimaryTableNameをオーバライドしてあげるとよい。
サンプル実装では、テーブル名の後ろに「_TBL」を付加している。
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.ImplicitEntityNameSource;
import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl;

public class HogeImplicitNamingStrategy extends ImplicitNamingStrategyJpaCompliantImpl {

    @Override
    public Identifier determinePrimaryTableName(ImplicitEntityNameSource source) {
        Identifier tableName = super.determinePrimaryTableName(source);
        return Identifier.toIdentifier(tableName.getText() + "_TBL");
    }
}

persistence.xmlにImplicitNamingStrategy実装クラスを設定

hibernate.implicit_naming_strategyというプロパティに、作成したクラスのFQCNを設定する。

<properties>
  <property name="hibernate.implicit_naming_strategy" value="sample.HogeImplicitNamingStrategy" />
</properties>

結果

テーブル名の末尾に「_TBL」が設定されているのが確認できる。usersエンティティは、@Tableアノテーションが設定されているので、「_TBL」が付加されない。

Hibernate: create table Item_TBL (id bigint not null, primary key (id))
Hibernate: create table users (id bigint not null, name varchar(255), primary key (id))

2015年12月6日日曜日

HibernateのPhysicalNamingStrategyを使って識別子に任意のプレフィックス等を付加する

Hibernateでテーブル名やカラム名などに一律任意のサフィックスやプレフィックスを追加するには、PhysicalNamingStrategyを実装したクラスを作成し設定を追加しするだけでよい。

PhysicalNamingStrategyの実装クラス

PhysicalNamingStrategyのデフォルト実装のPhysicalNamingStrategyStandardImplを継承すると必要なメソッドだけ実装すればいいのでよい。
この例では、テブル名の先頭に「hoge_」を付加している。
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

public class HogeNameStrategy extends PhysicalNamingStrategyStandardImpl {
    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        return Identifier.toIdentifier("hoge_" + name.getText(), name.isQuoted());
    }
}

persistence.xmlへPhysicalNamingStrategy実装クラスを設定

hibernate.physical_naming_strategyというプロパティに、作成したクラスのFQCNを設定する。
<properties>
  <property name="hibernate.physical_naming_strategy" value="sample.HogeNameStrategy" />
</properties>

結果

一律プレフィックスが追加されているのがわかる
Hibernate: create table HOGE_Item (id bigint not null, primary key (id))
Hibernate: create table "HOGE_user" (id bigint not null, name varchar(255), primary key (id))

2015年12月5日土曜日

Hibernate5のhibernate.auto_quote_keywordでキーワードを識別子に使えるように

hibernate.auto_quote_keywordプロパティの値をtrueに設定すると、キーワードを識別子に使った場合、自動的にクォートで囲ってくれるようになる。
これで、キーワードとかぶるエンティティを作ったとしても、@Tableアノテーションでテーブル名を明示的に指定しなくても良くなる。

Entityクラス

Oracleでは識別子に指定できないUserがテーブル名となるエンティティクラスを定義する。
@Entity
public class User {
    @Id
    public Long id;
    public String name;
}

hibernate.auto_quote_keywordの指定

persistence.xmlのproperties部にhibernate.auto_quote_keywordの設定を追加する。
設定値にtrueを指定すると、自動的にダブルクォートで囲われるようになる。
<properties>
  <property name="hibernate.auto_quote_keyword" value="true" />
</properties>

実行結果

ログ上で確認するとこんな感じにクォートで囲われているのがわかる。
Hibernate: create table "User" (id number(19,0) not null, name varchar2(255 char), primary key (id))

この設定値をfalseにした場合は、こんな結果になる。
Hibernate: create table User (id number(19,0) not null, name varchar2(255 char), primary key (id))
ERROR: HHH000389: Unsuccessful: create table User (id number(19,0) not null, name varchar2(255 char), primary key (id))
ERROR: ORA-00903: 表名が無効です。


JPA(Hibernate)経由でSQL実行しているだけだといいけど、自分でSQLを書く必要が出てきた時にはまりそうだなぁと思ったり。

2015年11月6日金曜日

[Oracle]DDL_LOCK_TIMEOUTでDDLのロックタイムアウトの待機時間を設定する

Oracle11gからは、DMLでロックされているレコードがあるテーブルに対するDDL文のロックタイムアウトの待機時間が設定できる。

設定がない場合(デフォルトの動作は)、nowaitなのでロックされているレコードがあるテーブルに対するDDLは即タイムアウトする。
発生するエラーは、こんな感じ。
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired

設定値の変更は、DDL_LOCK_TIMEOUTパラメータで行う。例えば、10秒を設定したい場合には、以下の様なalter sessionを実行する。
alter session set DDL_LOCK_TIMEOUT=10;

2015年11月1日日曜日

[Oracle]パスワードの大文字小文字の区別の設定

初期化パラメータのsec_case_sensitive_logonで大文字、小文字を区別するか設定する事ができる。

この値が、trueの場合は大文字と小文字が区別されるようになる。

設定値の確認

show parametersで確認できる。
SQL> sho parameters sec_case_sensitive_logon

NAME         TYPE  VALUE
------------------------------------ ----------- ------------------------------
sec_case_sensitive_logon      boolean  FALSE

設定値の変更

alter systemを使用して変更する。

alter system set sec_case_sensitive_logon = false

注意点

Oracle12cのセキュリティ・ガイドによると、この機能は非推奨で下位互換のためだけに残されているようです。

2015年10月19日月曜日

Oracleのバーチャルインデックス

バーチャルインデックスを使うと、ディスクスペースや作成時間、他のアプリケーション(セッション)を
気にせずにインデックス追加時のアクセスプランなどを確認することができる。

テーブル定義

SQL> desc person
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 P_ID                                      NOT NULL NUMBER
 NAME                                               VARCHAR2(100)

バーチャルインデックスの作成

create indexのオプションにnosegmentを指定することでバーチャルインデックスになる
create index person_name_index on person(name) nosegment;

バーチャルインデックスを使ってみる

通常は、バーチャルインデックスは使用されない。
  1* select * from person where name = 'a'

実行計画
----------------------------------------------------------
Plan hash value: 1493655343

----------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |     1 |    65 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| PERSON |     1 |    65 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("NAME"='a')

_use_nosegment_indexesパラメータの値をtrueに変更することで、現在のセッションのみバーチャルインデックスが有効となる。
SQL> alter session set "_use_nosegment_indexes"=true;

セッションが変更されました。

バーチャルインデックスを有効に変更した後にためしてみると、インデックスが参照されるようになる。
  1* select * from person where name = 'a'

実行計画
----------------------------------------------------------
Plan hash value: 966497106

---------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name              | Rows  | Bytes | Cost (%CPU)| Time  |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                   |     1 |    65 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| PERSON            |     1 |    65 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | PERSON_NAME_INDEX |     1 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

2015年9月25日金曜日

border-radiusで角の丸いボックスを配置する

CSS3のborder-radiusで角の丸いボックスを簡単に配置できる。

下のように、4つの値を指定した場合は、左上から時計回りに指定した値が適用される。
#box {
  border-radius: 40px 160px 80px 120px;
}

表示例

2015年9月18日金曜日

Angularでテンプレートを切り替えたタイミングで、Material Design Liteのコンポーネントを更新する

AngularJSとMaterial Design Liteを組み合わせて使った場合で、Angularのrouteを使ってtemplateを切り替えた場合、
切り替えて出力されたmdlコンポーネントの初期化処理が実行されない問題が発生した。

この問題を回避するには、templateを切り替えたタイミングでcomponentHandler.upgradeDom();を呼び出してあげれば良い。
参考サイト

    angular.module('app', ['ngRoute', 'ngResource'])
        .run(function ($rootScope, $location, $timeout) {
            $rootScope.$on('$viewContentLoaded', function () {
                $timeout(function () {
                  componentHandler.upgradeAllRegistered();
            });
        })
    })

2015年9月16日水曜日

CSS3でフェードインとフェードアウト

CSS3のtransition-durationを使うと、jQueryなどを使わずに簡単に要素のフェードインやフェードアウトを行うことができる。

transition-durationに2sを設定しているので、2秒かけて要素の状態が徐々に変化していく。
removeでは、要素を徐々に透明にしつつ高さを0にする。
showでは、徐々に要素の高さと透明の設定も元にもどしている。
div.remove {
  opacity: 0;
  height: 0;
  transition-duration: 2s;
}

div.show {
  opacity: 1;
  height: 200px;
  transition-duration: 2s;
}
フェードイン、フェードアウト対象の要素の定義とJavaScriptの内容。
JavaScriptでは、ボタンのクリックイベントで対象要素のクラスを切り替えてフェードイン、フェードアウトを実行している。
<div id="target">
  要素
</div>

<button type="button" id="remove">削除</button>
<button type="button" id="add">表示</button>

<script>
  var removeBtn = document.getElementById('remove');
  var addBtn = document.getElementById('add');
  var div = document.getElementById('target');

  removeBtn.addEventListener("click", function() {
    div.className = 'remove';
  });
  addBtn.addEventListener("click", function() {
    div.style.display = '';
    div.className = 'show';
  });
</script>

実行例


要素




2015年9月14日月曜日

[CSS3]box-sizingプロパティでボックス幅の計算方法を指定する

CSS3のbox-sizingプロパティを使うと、ボックスの幅や高さの計算方法を指定することができるようになる。

指定できる値

content-box
paddingやborderはbox幅や高さに含まれない
border-box
paddingとborderはbox幅や高さに含まれる

この例の場合、oneとtwoのボックスサイズは同じ大きさとなる。
twoは、paddingとborderがボックスサイズに含まれるので、幅と高さをその分大きく設定している。
  <!-- スタイル定義 -->
  <style>
    #one {
        box-sizing: content-box;
        width: 200px;
        height: 200px;
        border: 10px solid black;
    }

    #two {
        box-sizing: border-box;
        width: 220px;
        height: 220px;
        border: 10px solid black;
    }
  </style>

  <!-- html -->
  <div id="one"></div>
  <div id="two"></div>

2015年8月29日土曜日

Maven pluginでプロジェクトのビルドアウトプットに依存する方法

Guide to Maven Classloadingによると、Maven pluginはPlugin classloaderでクラスがロードされます。
プラグインのdependenciesに設定されたライブラリはロードされるけど、プラグインが設定されたプロジェクトのビルドアウトプットやdependenciesに設定されたライブラリを見ることはできない。

どうしても、プラグインからプロジェクトのビルドアウトプットを参照できないとダメなときは、Mojoでカスタムクラスローダを使ってロードする必要がある。例えば、Mojoから使うライブラリがClassオブジェクトを要求する場合なんかは、自分でカスタムクラスローダ使って頑張る必要がある。

Stackoverflowいい解決案があるので、これを参考に対応するのがよい。→Maven plugin can't load class

自分は、最も+1された回答を参考にしてみました。
やった内容は、大きくしたの2点になります。

MavenProjectをMojoにインジェクトする

Mojoのフィールドで、MavenProjectを受け取ります。
    @Parameter(defaultValue = "${project}", required = true, readonly = true)
    private MavenProject project;

カスタムクラスローダを使ってクラスをロードする

MavenProjectのgetRuntimeClasspathElementsを使って、クラスパスに設定されているパスを取得して、カスタムクラスローダを作ります。
getRuntimeClasspathElementsが返す値は、MojoアノテーションのrequiresDependencyResolution属性に指定した値によって変わります。
属性を指定しない場合は、target/classesだけが取得されます。ResolutionScope.RUNTIMEを設定すると、dependenciesに設定したライブラリも取得されます。
    private URLClassLoader createClassLoader() {
        List runtimeClasspathElements;
        try {
            runtimeClasspathElements = project.getRuntimeClasspathElements();
        } catch (DependencyResolutionRequiredException e) {
            throw new RuntimeException(e);
        }

        final URL[] urls = runtimeClasspathElements.stream()
                .peek(s -> System.out.println("classpath:" + s))
                .map(s -> {
            try {
                return new File(s).toURI().toURL();
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }
        }).toArray(URL[]::new);
        return new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());
    }

[CSS3]attribute selector

attribute selectorは属性値を使って要素を選択できるセレクター。

使い方は、簡単で[]内にattribute selectorを定義する。
[attribute selector] {
}

/* attributeという属性を持つ要素を選択 */
[attribute] {
}

/*
class="hogs"を持つ要素を選択
class="hogs fuga"はマッチしないので選択されない
*/
[class="hogs"] {
}

/* class属性の値がhogeから始まる要素を選択 */
[class^="hoge"] {
}

/* class属性の値がfugaで終わる要素を選択 */
[class$="fuga"] {
}

/*
class属性の値の中で、スペースで区切られた値「hoge」がある要素を選択

class="hogs fuga"やclass="hoge"が選択される
*/
[class~="hoge"] {
}

2015年8月6日木曜日

PostgreSQLのpsqlでテーブル定義を確認する

psqlメタコマンドの\d テーブル名で確認することができる。

実行例

=> \d hoge
 テーブル "public.hoge"
  列  |   型   | 修飾語
------+--------+--------
 id   | bigint |
 name | text   |

2015年7月27日月曜日

eclipselinkでbatch insert

eclipselinkでは、jdbc.batch-writingjdbc.batch-writing.sizeの2つのプロパティを指定することでbatch insertが使用されます。

jdbc.batch-writingには、基本的にjdbcを指定すればよく、jdbc.batch-writing.sizeにはバッチサイズを指定する。

例えば、以下のように設定するとバッチサイズは100となります。

<properties>
  <property name="javax.persistence.schema-generation.database.action" value="drop-and-create" />

  <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>

  <property name="eclipselink.jdbc.batch-writing" value="JDBC" />
  <property name="eclipselink.jdbc.batch-writing.size" value="100" />
</properties>

結果を見ると、batch insertのほうが早くなっているのがわかります。

batch insertあり
---------- 1回目: 625ms ----------
---------- 2回目: 319ms ----------
---------- 3回目: 274ms ----------

batch insertなし
---------- 1回目: 1630ms ----------
---------- 2回目: 1342ms ----------
---------- 3回目: 1146ms ----------

2015年7月22日水曜日

hibernateでbatch-insert

hibernate.jdbc.batch_sizeに任意の数字を指定すると、その値が自動的にJDBCのバッチサイズになります。

例えば、以下の設定を行うとバッチサイズは100となります。

<properties>
  <property name="javax.persistence.schema-generation.database.action" value="drop-and-create" />
  <property name="javax.persistence.jdbc.driver" value="oracle.jdbc.OracleDriver" />
  <property name="javax.persistence.jdbc.url" value="url"/>
  <property name="javax.persistence.jdbc.user" value="user"/>
  <property name="javax.persistence.jdbc.password" value="password"/>

  <property name="hibernate.jdbc.batch_size" value="100" />
</properties>

試しに以下のコードでbatch insertの効果があるのか確認してみます。
private static void insert(EntityManager em) {
        final EntityTransaction transaction = em.getTransaction();
        transaction.begin();
        for (int i = 1; i <= 1000; i++) {
            final UserEntity entity = new UserEntity();
            entity.setName("name_" + i);
            em.persist(entity);
        }
        transaction.commit();
    }

結果をみると、batch insertの効果で5倍ぐらい早くなっているのがわかります。

batch insertあり
---------- 1回目: 766ms ----------
---------- 2回目: 390ms ----------
---------- 3回目: 317ms ----------
batch insertなし
---------- 1回目: 2369ms ----------
---------- 2回目: 2347ms ----------
---------- 3回目: 1589ms ----------

2015年7月19日日曜日

PostgreSQLのsleepファンクション

pg_sleepを使うことでデータベースサーバ側でsleep処理ができる。
pg_sleepの引数には、sleepしたい秒数を指定する。

-- 5秒sleep
select pg_sleep(5);
 pg_sleep
----------

(1 行)

時間: 5016.963 ms

-- 3秒sleep
select pg_sleep(3);

 pg_sleep
----------

(1 行)

時間: 3001.497 ms

2015年7月5日日曜日

IS DISTINCT FROM演算子

IS DISTINCT FROM(IS NOT DISTINCT FROM)演算子の使い方。PostgreSQLでは使うことができる。Oracleは12c時点では使うことができない。
この演算子を使うと、一方がnullの可能性がある場合の条件をcol1 = :col1 or col1 is nullのようにnullを考慮する必要がなくなる。

使い方

IS NOT DISTINCT FROMは、以下の条件と同じ動きとなる。
(expo IS NOT NULL
AND exp2 IS NOT NULL
AND exp1 = exp2)
OR (exp1 IS NULL AND exp2 IS NULL)

検索対象のデータ

データの内容(@は、nullを示しています)
select * from test;
 col
-----
   1
   2
   3
   @
(4 行)

IS NOT DISTINCT FROM

is not distinct from nullとするとnullのレコードのみ取得できる。
select * from test where col is not distinct from null;
 col
-----
   @

条件を指定すると、そのレコードのみ取得できる。
select * from test where col is not distinct from 3;
 col
-----
   3
(1 行)

2015年6月28日日曜日

[Oracle12c]SQL*Loaderのエクスプレス・モード

Oracle12CからはSQL*Loaderのエクスプレス・モードを使用してコントロールファイルを作らずにデータをロードすることができるようです。
この機能は、表の列がすべて文字列、数値または日付型で、入力ファイルにデリミタ付きの文字列データが含まれている場合(例えばCSVファイル)に、テーブル名の指定のみでロードが実行できます。

エクスプレスモードの例

テーブル定義

主キーと属性1つづつのシンプルなテーブルで試してみます。
SQL> desc users;
 Name        Null?    Type
 ----------------------------------------- -------- ----------------------------
 USER_ID       NOT NULL NUMBER(10)
 NAME          VARCHAR2(100 CHAR)

CSVファイルの内容

データが5行のCSVファイルです。
1,user1
2,user2
3,user3
4,user4
5,user5

実行結果

実行結果の標準出力内容です。5行のロードが正常に終わっていることがわかります。
実行時には、接続情報をテーブル名のみを与えるのみで、コントロールファイルは作成していません。
入力ファイルを指定しない場合、デフォルトでは「テーブル名.dat」となります。今回の場合は、「users.dat」が入力ファイルとなります。
$ sqlldr hoge/hoge@orcl TABLE=users

SQL*Loader: Release 12.1.0.2.0 - Production on Sat Jun 27 10:49:48 2015

Copyright (c) 1982, 2014, Oracle and/or its affiliates.  All rights reserved.

Express Mode Load, Table: USERS
Path used:      External Table, DEGREE_OF_PARALLELISM=AUTO

Table USERS:
  5 Rows successfully loaded.

Check the log files:
  users.log
  users_%p.log_xt
for more information about the load.

テーブルにも5レコード格納されています。
SQL> r
  1* select * from users

   USER_ID NAME
---------- -------------------------
  1 user1
  2 user2
  3 user3
  4 user4
  5 user5

どんな定義でロードされるの?

実行時の定義(コントロールファイルの内容)は、実行ログから確認できます。
今回の場合は、以下の定義でロードされています。
OPTIONS(EXTERNAL_TABLE=EXECUTE, TRIM=LRTRIM)
LOAD DATA
INFILE 'users'
APPEND
INTO TABLE USERS
FIELDS TERMINATED BY ","
(
  USER_ID,
  NAME CHAR(400)
)

デフォルト値の上書き方法

デフォルト値はコマンドラインパラメータで上書きします。例えば、フィールドの区切り文字をデフォルトのカンマ(,)から縦棒(|)に変更したい場合には以下のようにします。
sqlldr hoge/hoge@orcl TABLE=users TERMINATED_BY='|'

変更できる項目はマニュアルで参照できます。

2015年6月24日水曜日

SQL*Loaderでインプットファイルの論理行番号をテーブルに格納する

SQL*Loaderでデータロード時に、ファイルの論理レコード番号をテーブルに格納したい場合には、RECNUMパラメータを使用する。
RECNUMを使用するとスキップしたレコードや不良レコード、破棄レコードも1レコードとしてカウントされる。

ロード先のテーブル定義

名前       NULL?    型
--------- -------- ----------------------------
REC_NUM   NOT NULL NUMBER
NAME               VARCHAR2(100)

コントロールファイルの内容

論理行番号を格納したいカラムにRECNUMパラメータを使用する。
OPTIONS (SKIP=1)
LOAD DATA
CHARACTERSET 'AL32UTF8'
INFILE 'input.csv' "str '\n'"
BADFILE 'input.bad'
DISCARDFILE 'input.dsc'
TRUNCATE
INTO TABLE HOGE
FIELDS TERMINATED BY ',' 
(
REC_NUM RECNUM,
NAME OPTIONALLY ENCLOSED BY '"' 
)

CSVファイルの内容

タイトル行あり、不正なレコード(空行)ありのファイル。
name
あ
い

う
え
お

実行結果の確認

以下のコマンドで、上のCSVファイルを取り込みます。
sqlldr 接続先情報 control=input.ctl

スキップしたヘッダー行や不正なレコードの空行もレコード数としてカウントアップされているのが分かる。
1* select * from hoge

REC_NUM    NAME
---------- ------------------------------
2          あ
3          い
5          う
6          え
7          お

2015年6月12日金曜日

[PostgreSQL]単一のINSERT文で複数レコードを登録

INSERT文のVALUES区に登録対象のレコード情報をカンマ(,)区切りで複数列挙することで一括登録ができる

SQL例

使い方は簡単で、INSERTのVALUE句に設定する。
INSERT INTO users (id, name) VALUES
    (1, 'hoge'),
    (2, 'fuga')

2015年5月17日日曜日

[JPA2.1]DDL generation

JPA2.1で標準化されたDDL Generationを試してみました。

Entity

Entityクラスの実装
package sample;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "user")
public class UserEntity {

    private Long id;

    private String name;

    @Id
    @GeneratedValue()
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

persistence.xml

DDL Generationを使うための設定が入ったpersistence.xmlの例
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">

  <persistence-unit name="pu" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

    <class>sample.UserEntity</class>

    <properties>
      <property name="javax.persistence.schema-generation.database.action" value="drop-and-create" />
      <property name="javax.persistence.schema-generation.scripts.action" value="drop-and-create" />
      <property name="javax.persistence.schema-generation.scripts.create-target" value="create.sql" />
      <property name="javax.persistence.schema-generation.scripts.drop-target" value="drop.sql" />
      <property name="javax.persistence.sql-load-script-source" value="file:./load-data.sql" />

      <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
      <property name="javax.persistence.jdbc.url" value="jdbc:h2:file:/tmp/test-db" />
      <property name="javax.persistence.jdbc.user" value="sa" />
      <property name="eclipselink.logging.level" value="fine" />
    </properties>
  </persistence-unit>

</persistence>

この設定でアプリケーションを実行すると、カレントディレクトリ配下にdorp用とcreate用のDDLが作成されます。
また、データベースに対してもdropでDBクリーニング後にCREATEでデータベースがセットアップされます。
データベースセットアップ後には、javax.persistence.sql-load-script-sourceで指定したSQLスクリプトが実行されます。

javax.persistence.schema-generation.database.action
データベース上へのアクションを指定します。drop-and-createを指定しているので、Entityを元に既存オブジェクトの削除をしてから新しくオブジェクトが作られます。
その他にも、dropcreateなどを選択できます。

javax.persistence.schema-generation.scripts.action
オブジェクト作成用のSQLスクリプトファイルの生成方法を指定します。drop-and-createを指定しているので、dropとcreate用の2つのSQLファイルが出力されます。

create文は、javax.persistence.schema-generation.scripts.create-targetで設定したファイルに出力されます。
drop文は、javax.persistence.schema-generation.scripts.drop-targetで設定したファイルに出力されます。

javax.persistence.sql-load-script-source
データベースセットアップ後に実行するSQLスクリプトを設定します。
初期データのセットアップなどを行うINSERT文などを定義します。


2015年5月9日土曜日

[Oracle12C]拡張データ型でVARCHAR2やRAWの最大長をより大きい値にする

Oracle12cからの新機能の拡張データ型を使用すると、VARCHAR2NVARCHAR2RAWの最大サイズが32,767バイトまで指定可能となります。

※以前のバージョンでは、文字列(VARCHAR2、NVARCHAR2)は4000、RAWは2000が最大サイズだったかと思います。

拡張データ型の使用方法

拡張データ型は初期化パラメータのMAX_STRING_SIZEにより設定します。

この初期化パラメータには、以下の2種類の設定値があります。設定値を拡張データ型(EXTENDED)に変更するとSTANDARDに戻すことはできないので注意が必要です。
EXTENDED
上限が32,767バイトになります
STANDARD
Oracle12cより前のバージョンと同じ仕様となります。デフォルトはこの値となります。

現在値の確認

SQL> show parameters max_string_size

NAME                                        TYPE       VALUE
------------------------------------ ----------- ------------------------------
max_string_size                      string      STANDARD

UPGRADEモードでデータベースを再起動

非CDBの場合には、以下の手順でデータベースを再起動します。
SQL> shutdown
SQL> startup UPGRADE

拡張データ型の有効化

altert systemで拡張データ型を使用できるようにします。
SQL> alter system set MAX_STRING_SIZE = EXTENDED;

System altered.

SQL> show parameters max_string_size

NAME                                        TYPE        VALUE
------------------------------------ ----------- ------------------------------
max_string_size                      string      EXTENDED

rdbms/admin/utl32k.sqlを実行し、ノーマルモードでデータベースを再起動します。

テーブルを作ってみる

以下のように、VARCHAR2の4000バイトを突破したカラムを定義できるようになります。
CREATE TABLE TEST_TABLE
(
    EXTENDED_VARCHAR VARCHAR2(32767) NOT NULL
)

作成したテーブルの定義を確認すると、指定した桁数のVARCAR2列が定義されている事がわかります。
SQL> desc test_table;
 Name                                                Null?  Type
 ----------------------------------------- -------- ----------------------------
 EXTENDED_VARCHAR                   NOT NULL VARCHAR2(32767)

データを登録してみる

以下のように4000バイトを超えるデータを挿入、参照できていることがわかります。
SQL> insert into test_table values (rpad('1', 32767, '1'));

1 row created.

SQL> select length(extended_varchar) from test_table;

LENGTH(EXTENDED_VARCHAR)
------------------------
     32767

JDBCで扱ってみる


メータデータからカラム定義を取得

以下のコードでメタデータの情報を出力してみます。
final ResultSet rs = metaData.getColumns(null, null, "TEST_TABLE", null);
while (rs.next()) {
    System.out.println("rs.getString(\"column_name\") = " + rs.getString("column_name"));
    System.out.println("rs.getInt(\"data_type\") = " + rs.getInt("data_type"));
    System.out.println("Types.VARCHAR = " + Types.VARCHAR);
    System.out.println("rs.getString(\"type_name\") = " + rs.getString("type_name"));
    System.out.println("rs.getInt(\"column_size\") = " + rs.getInt("column_size"));
}

実行結果は、以下のようになります。ドキュメントには、4000バイトを超えるデータは内部的にはCLOBのテクノロジを使用して格納するとあったけど、テーブル作った時のデータタイプなどが取得できています。
table.getString("column_name") = EXTENDED_VARCHAR
table.getInt("data_type") = 12
Types.VARCHAR = 12
table.getString("type_name") = VARCHAR2
table.getInt("column_size") = 32767

データを参照してみる

JDBC経由での参照は特に何かを意識する必要はなさそうです。
型マッピングもVARCHAR2と同じですね。(CLOBになってなくてよかったです。)
final Statement statement = connection.createStatement();
final ResultSet rs = statement.executeQuery("select * from TEST_TABLE");
if (rs.next()) {
    final Object object = rs.getObject(1);
    System.out.println("String? " + (object instanceof String));
    final String str = String.class.cast(object);
    System.out.println("str.length() = " + str.length());
}
//-------------------
// 実行結果
//-------------------
String? true
str.length() = 32767

2015年4月18日土曜日

[PostgreSQL]ドメインを定義する

ドメイン定義を使用することで、よく利用する列定義をテーブルごとにするのではなくドメインの定義に集約することができる。

ドメインの定義方法

create domainを使用してドメインを作成する。

-- not null制約のついた、varchar(5)のドメインを作成
CREATE DOMAIN test_domain VARCHAR(5) NOT NULL;

-- ドメインの定義内容の確認
                              ドメイン一覧
  スキーマ   |    名前     |          型          |  修飾語  | チェック
-------------+-------------+----------------------+----------+----------
 test_schema | test_domain | character varying(5) | not null |


ドメイン型のカラムの作成

-- テーブルの作成
CREATE TABLE TEST_TABLE (
    ID   SERIAL NOT NULL,
    NAME TEST_DOMAIN,   -- 型にドメインを指定したカラム
    PRIMARY KEY (ID)
)

-- テーブルの定義の確認
-- ドメインに設定しているnot null制約は確認できない
                      テーブル "test_schema.test_table"
  列  |     型      |                         修飾語
------+-------------+---------------------------------------------------------
 id   | integer     | not null default nextval('test_table_id_seq'::regclass)
 name | test_domain |
インデックス:
    "test_table_pkey" PRIMARY KEY, btree (id)

データをインサートしてみる

-- 有効なデータの場合正しくinsertできる
insert into test_table (name) values ('12345');
INSERT 0 1

-- 桁数オーバの場合エラーになる
insert into test_table (name) values ('123456');
ERROR:  value too long for type character varying(5)

-- 制約違反の場合エラーになる
insert into test_table (name) values (null);
ERROR:  domain test_domain does not allow null values

2015年4月9日木曜日

[PostgreSQL]JDBCの接続URLでカレントスキーマを指定する

JDBCで接続先のURLを指定するときに、接続パラメータでカレントのスキーマを指定する。

URLのフォーマット

カレントスキーマの接続パラメータは、currentSchemaなので、以下のようにURLの最後に指定する。

jdbc:postgresql://localhost/test?currentSchema

実行例

// 以下のコードの場合、カレントスキーマはtest_schemaとなる
dataSource.setUrl("jdbc:postgresql://localhost/test?currentSchema=test_schema");
try (Connection connection = dataSource.getConnection()) {
    final String schema = connection.getSchema();
    System.out.println("schema = " + schema);       // -> schema = test_schema
}

2015年4月2日木曜日

[jdbc]カレントのスキーマ名を取得する

現在の接続のカレントスキーマ名を得る方法。

Connectionインタフェースにカレントスキーマ名を取得するメソッド(getSchema)があるのでそれを使えばよい。
※getSchemaは、Java7から追加された機能。

サンプルコード

        final Connection connection = dataSource.getConnection();
        final String schema = connection.getSchema();
        System.out.println("schema = " + schema);

[Spring]MockMVCとJsonPathをつかたテストでjava.lang.NoClassDefFoundError: com/jayway/jsonpath/InvalidPathExceptionが出た場合の対処


エラー内容

java.lang.NoClassDefFoundError: com/jayway/jsonpath/InvalidPathException
 at org.springframework.test.web.servlet.result.JsonPathResultMatchers.(JsonPathResultMatchers.java:44)
 at org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath

対処方法

依存ライブラリにjson-pathを追加します。

Gradleの場合には、以下のようになります。
    testCompile "com.jayway.jsonpath:json-path:2.0.0"

2015年3月18日水曜日

JPA2.1のConverterを使ってEntityの属性の型変換

JPA2.1では、Entityの属性にConverterが設定できて、DBへの保存時や取得時に型変換を行うことができる。

使用方法


Converterクラスの作成

Converterクラスは、javax.persistence.AttributeConverterインタフェースをを実装し、javax.persistence.Converterアノテーションを設定する。
AttributeConverterの総称型には、Entityの属性の型とDBへ挿入する型を指定する。

このコードの例だと、Sex列挙型の値をStringに変換してDBに登録します。
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

import jpa.entity.Sex;

@Converter
public class SexConverter implements AttributeConverter<Sex, String> {

    @Override
    public String convertToDatabaseColumn(Sex attribute) {
        return attribute.toString();
    }

    @Override
    public Sex convertToEntityAttribute(String dbData) {
        return Sex.valueOf(dbData);
    }
}

Entityの実装

変換対象のカラムにjavax.persistence.Convertアノテーションを指定する。
このアノテーションのconverter属性に、先ほど作成したConverterクラスを指定する。
@Entity
@Table(name = "user_info")
public class User {

    private Long id;

    private Sex sex;

    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column
    @Convert(converter = SexConverter.class)
    public Sex getSex() {
        return sex;
    }

    public void setSex(Sex sex) {
        this.sex = sex;
    }
}

実行

以下のコードで、Sex列挙型の項目がDBに文字列で保存されることを確認します。
final User user = new User();
user.setSex(Sex.MALE);
em.persist(user);
DBの値は下の結果のように列挙型の文字列表記になっています。
ID           SEX
---------- --------------------
1            MALE

2015年3月10日火曜日

PostgreSQLでシーケンスを使う

PostgreSQLでのシーケンス作成やシーケンスの操作関数の纏め。

シーケンスの作成

create sequence文を使ってシーケンスを作成する。

全てデフォルトの設定でシーケンスを作成する。(初期値は1、増分値は1、キャッシュはなしなどになる)
create sequence test_seq;

一時的な(今のセッションだけに関連づいた)シーケンスを作成する。
シーケンス作成時に、temp(temporary)を指定すると今のセッションに関連づいた一時的なシーケンスが作られる。(セッションが終了すると、自動的に破棄される。)
既に同名のシーケンスが存在していた場合、一時的なシーケンスが優先されて既にあったシーケンスは見えなくなる。
create temp sequence test_seq;

シーケンスの情報を確認する

シーケンスを検索することで、今の状態が確認できる。
select * from シーケンス名


シーケンスを操作するための関数

nextvalで次の値を取得する。
select nextval('シーケンス名')

currvalで最後にnextvalされた値を取得できる。
select currval('シーケンス名')

setvalで現在の値を変更できる。この例だと、値を999に変更している。
select setval('シーケンス名', 999)


2015年3月8日日曜日

JPA2.1でストアドプロシージャ実行-REF CURSOR編

JPA2.1でストアドプロシージャ実行の続編。
ストアドプロシージャで開いたカーソル(REF CURSOR)をJPA経由で受け取るサンプル。

実行対象のプロシージャ

パラメータは1つで、アウトパラメータでカーソルを返す。
CREATE OR REPLACE PROCEDURE REF_CURSOR_TEST(cur out SYS_REFCURSOR)
AS
BEGIN
    OPEN CUR FOR SELECT *
                 FROM USERS;
END;

実行例

EntityManagercreateStoredProcedureQueryにストアドプロシージャ名を指定してStoredProcedureQueryを生成します。
第2引数には、REF CURSORを受け取るEntityクラスを指定します。(Entity以外でもいいのか?)

executeでストアドプロシージャを実行して、getOutputParameterValueを呼び出してREF CURSORの結果を受け取ります。
ここで受け取ったEntityは管理対象となるので、状態を変更した場合は永続化層に反映される。
final StoredProcedureQuery query = em.createStoredProcedureQuery("REF_CURSOR_TEST", User.class)
         .registerStoredProcedureParameter(1, void.class, ParameterMode.REF_CURSOR);
query.execute();
List users = (List) query.getOutputParameterValue(1);

System.out.println("取得件数 = " + users.size());
users.forEach(user -> {
            System.out.println("id = " + user.getId());
            System.out.println("name = " + user.getName());
            user.setName(user.getName() + ":" + user.getId());
        }
);

実行結果

ストアドプロシージャで開いたカーソルの結果が受け取れていることがわかる。
[EL Fine]: sql: 2015-03-07 23:51:33.88--ClientSession(1620989914)--Connection(108049354)--Thread(Thread[main,5,main])--BEGIN REF_CURSOR_TEST(?); END;
 bind => [=> 1]
取得件数 = 2
id = 1
name = hoge
id = 2
name = fuga

2015年2月28日土曜日

[Oracle12C]with句でストアドプロシージャの定義

Oracle12Cでは、with句内でストアドプロシージャが定義できるようになりました。

使い方は、with句内にストアドプロシージャを定義して、それをクエリで使います。
この例では、パラメータの値を2倍するファンクションを定義して、それをselect句で使用しています。
WITH
FUNCTION func(x IN PLS_INTEGER) RETURN PLS_INTEGER
IS
BEGIN
    RETURN X * 2;
END;
SELECT func(100)
FROM DUAL;

実行結果です。ファンクションに渡した値が2され取得されているのがわかります。
SQL> r
  1  WITH
  2  FUNCTION func(x IN PLS_INTEGER) RETURN PLS_INTEGER
  3  IS
  4  BEGIN
  5  RETURN X * 2;
  6  END;
  7  SELECT func(100)
  8  FROM DUAL
  9
 10*

 FUNC(100)
----------
       200

2015年2月25日水曜日

JPA2.1でストアドプロシージャ実行

JPA2.1のストアドプロシージャの実行機能の使い方サンプル。

実行対象のプロシージャ

INパラメータを2つ受け取って、その値の合計値をアウトパラメータで返却する単純なプロシージャです。
CREATE OR REPLACE FUNCTION PUBLIC.test_function(X INT, Y INT, RET OUT INT)
AS
$BODY$
BEGIN
  RET = Y + X;
END;
$BODY$
LANGUAGE plpgsql

実行例

EntityManagercreateStoredProcedureQueryにストアドプロシージャ名を指定してStoredProcedureQueryを生成します。
StoredProcedureQueryregisterStoredProcedureParameterを使用して、パラメータ情報を設定します。(パラメータの位置、イン or アウトパラメータや型情報を設定します。)
入力パラメータの場合には、setParameterを呼び出してストアドプロシージャに引き渡す値を設定します。

実行準備が整ったら、executeでストアドプロシージャを実行します。アウトパラメータはgetOutputParameterValueを使用して取得します。

CallableStatementと使い方は近いので特に迷うこともなさそうな気がします。
final StoredProcedureQuery storedProcedureQuery = em.createStoredProcedureQuery("test_function")
        .registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN)
        .setParameter(1, 10)
        .registerStoredProcedureParameter(2, Integer.class, ParameterMode.IN)
        .setParameter(2, 20)
        .registerStoredProcedureParameter(3, Integer.class, ParameterMode.OUT);
storedProcedureQuery.execute();
System.out.println("result = " + storedProcedureQuery.getOutputParameterValue(3));

NamedStoredProcedureQueryを使った場合の実行例


Entity側の準備

Entityには以下のように名前付きでストアドプロシージャの情報を定義します。
上の例で示したregisterStoredProcedureParameterの呼び出し部が、NamedStoredProcedureQueryparameters属性に設定されているイメージになります。
@NamedStoredProcedureQuery(
        name = "calc",
        procedureName = "test_function",
        parameters = {
                @StoredProcedureParameter(name = "x", type = Integer.class, mode = ParameterMode.IN),
                @StoredProcedureParameter(name = "y", type = Integer.class, mode = ParameterMode.IN),
                @StoredProcedureParameter(name = "result", type = Integer.class, mode = ParameterMode.OUT)
        })

実行分

createNamedStoredProcedureQueryにEntityで定義したストアドプロシージャクエリの名前を指定して、Queryを生成します。
生成した、Queryに対してパラメータ情報を設定して、executeを呼び出しアウトパラメータがある場合には結果を取得します。
上の例と比べると、パラメータの情報の設定がEntity側で定義されているのでなくなっています。
final StoredProcedureQuery func = em.createNamedStoredProcedureQuery("calc")
        .setParameter("x",1)
        .setParameter("y",2);
func.execute();
System.out.println("func.getOutputParameterValue(\"result\") = "+func.getOutputParameterValue(3));

2015年1月31日土曜日

[JPA]中間テーブルを作らずに直接結合させる

例えば1対多の依存があるエンティティをJPAで使用した場合、デフォルトでは中間テーブルを使った結合となる。

Entityの関連定義

    @OneToMany(cascade = {CascadeType.ALL})
    public List<Tel> getTels() {
        return tels;
    }

テーブル定義

上のEntityの関連定義だと下のようなテーブル構造となります。
中間テーブルのテーブル名やカラム名を変更したい場合には、OneToManyとセットでJoinTableアノテーションを定義して任意の値に変更します。

どうしても過去の資産などを使わないとダメな場合なので中間テーブルを使えない場合には、OneToManyとセットでJoinColumnを定義してあげる。JoinColumnのname属性には、子供側テーブルの外部キーとなる項目名を定義します。

Entityを次のように変更すると、Userテーブルの子テーブルのTelテーブルにはUserテーブルを参照するための外部キー(user_id)が作成される。
    @OneToMany(cascade = {CascadeType.ALL})
    @JoinColumn(name = "user_id")
    public List<Tel> getTels() {
        return tels;
    }

テーブル定義は次のようになります。

2015年1月25日日曜日

[JPA]ElementCollectionとCollectionTableアノテーション

JPAのEntityクラスに基本型のCollectionやListの属性を定義する場合は、ElementCollectionアノテーションを使用する。

ElementCollectionをつけることで、基本型(Entityじゃないクラス)をもつCollectionやListの属性を別のテーブルに保存してくれるようになる。
CollectionTableアノテーションを使うと、保存先のテーブルの名前などをカスタマイズできるようになる。

Entityクラスの定義です。ソースコードが長くなると見づらくなるので、JPAのアノテーションはpublicフィールドに設定してアクセッサは定義していません。
@Entity
public class User {

    @Id
    @GeneratedValue
    public Long id;

    public String name;

    @ElementCollection
    public Set mail;
}

上のEntity定義の場合、CollectionTableアノテーションを使用していないのでデフォルトの定義でテーブルが作られます。
テーブル名は、親テーブルのテーブル名とその属性の名前(user_mail)となり、カラム名は属性名(mail)となります。


テーブル名を変更したい場合には、CollectionTableアノテーションのname属性を使用します。
カラム名を変更したい場合には、Columnアノテーションを使用します。



2015年1月20日火曜日

[JDBC]DatabaseMetaDataから識別子の格納方法を判断する

JDBCのDatabaseMetaDataから、データベースの識別子(テーブル名やカラム名)がどのように格納されているか判断する方法。
DatabaseMetaDataを使ってカラム一覧や主キー一覧などを取得するときには、格納方法を判断して識別子を大文字や小文字に変換してから使用する必要がある。

引用符付きの識別子は、基本使わない(使っとこともない)ので、引用符なしの識別子が大文字、小文字どちらで格納されているかを判断するだけのサンプルコード
// storesMixedCaseIdentifiersで引用符なしの識別子の格納方法を判断する。
// falseが返される場合は、大文字、小文字のどちらかで格納されている。
System.out.println("大文字小文字混在の引用符なし識別子を大文字小文字混在で格納する → " + metaData.storesMixedCaseIdentifiers());

// storesLowerCaseIdentifiersで小文字で格納されているかを判断する。
// trueが返される場合は、小文字で格納されている。
System.out.println("引用符なし識別子を小文字で格納する → " + metaData.storesLowerCaseIdentifiers());

// storesUpperCaseIdentifiersで大文字で格納されているかを判断する。
// trueが返される場合は、大文字で格納されている。
System.out.println("引用符なし識別子を大文字で格納する → " + metaData.storesUpperCaseIdentifiers());

実行結果

Oracleの場合

大文字で格納されていることが分かる
大文字小文字混在の引用符なし識別子を大文字小文字混在で格納する → false
引用符なし識別子を小文字で格納する → false
引用符なし識別子を大文字で格納する → true

PostgreSQLの場合

小文字で格納されていることが分かる
大文字小文字混在の引用符なし識別子を大文字小文字混在で格納する → false
引用符なし識別子を小文字で格納する → true
引用符なし識別子を大文字で格納する → false

2015年1月8日木曜日

PostgreSQLはStatement.RETURN_GENERATED_KEYSで全カラムの値が返される

PostgresSQLは、Statement生成時に、Statement.RETURN_GENERATED_KEYSを指定するとStatement#getGeneratedKeysでそのテーブルの全カラムの情報が取得される。

これは、Statement.RETURN_GENERATED_KEYSでStatementを生成すると、実行されるINSERT文に「RETURNING *」が付加されるため。

ID、NAME、BIRTHDAYの3つのカラムを持つテーブルにINSERT処理を行っています。
INSERT対象のカラムはNAMEのみで、ID列は自動採番カラム(SERIAL)となっています。
PreparedStatement statement = connection.prepareStatement("insert into USER_INFO (name) VALUES (?)",
        Statement.RETURN_GENERATED_KEYS);
statement.setString(1, "なまえ");

statement.executeUpdate();
ResultSet generatedKeys = statement.getGeneratedKeys();
if (generatedKeys.next()) {
    System.out.println(MessageFormat.format("カラム数 = [{0}]",
            statement.getMetaData().getColumnCount()));
    System.out.println("generatedKeys.getDate(3) = " + generatedKeys.getDate(3));
}
connection.commit();

実行結果

実行すると、GeneratedKeysで取得したResultSetのカラム数は3となっていることがわかります。
また、INSERT時に列挙していないカラムのデータもとれているのがわかります。
カラム数 = [3]
generatedKeys.getDate(3) = null

実際に実行されたSQL文

ログを見てみると、実行されたINSERT文にRETURNING *が付加されているのがわかります。
これが理由で、getGeneratedKeysが全カラムの情報を戻して来ています。
2015-01-08 12:48:11 JST LOG:  duration: 0.000 ms  parse : insert into USER_INFO (name) VALUES ($1) RETURNING *
2015-01-08 12:48:11 JST LOG:  duration: 0.000 ms  bind : insert into USER_INFO (name) VALUES ($1) RETURNING *
2015-01-08 12:48:11 JST DETAIL:  parameters: $1 = 'なまえ'
2015-01-08 12:48:11 JST LOG:  duration: 1.000 ms  execute : insert into USER_INFO (name) VALUES ($1) RETURNING *
2015-01-08 12:48:11 JST DETAIL:  parameters: $1 = 'なまえ'
2015-01-08 12:48:11 JST LOG:  duration: 0.000 ms  parse : insert into USER_INFO (name) VALUES ($1) RETURNING *

カラムインデックスやカラム名指定の場合は?

Statement生成時に自動生成カラムのインデックスや名前を指定した場合は、指定したカラムの情報のみ取得することが出来ます。
例えば、ID列のみを指定した場合実行されるSQL文は以下のようになります。
2015-01-08 12:55:28 JST LOG:  duration: 34.000 ms  parse : insert into USER_INFO (name) VALUES ($1) RETURNING "id"
2015-01-08 12:55:28 JST LOG:  duration: 0.000 ms  bind : insert into USER_INFO (name) VALUES ($1) RETURNING "id"
2015-01-08 12:55:28 JST DETAIL:  parameters: $1 = 'なまえ'
2015-01-08 12:55:28 JST LOG:  duration: 0.000 ms  execute : insert into USER_INFO (name) VALUES ($1) RETURNING "id"
2015-01-08 12:55:28 JST DETAIL:  parameters: $1 = 'なまえ'
2015-01-08 12:55:28 JST LOG:  duration: 0.000 ms  parse : insert into USER_INFO (name) VALUES ($1) RETURNING "id"

2015年1月7日水曜日

OracleでJDBC経由でレコード追加後にROWIDを取得する

OracleのJDBCドライバで、Statement生成時にStatement.RETURN_GENERATED_KEYSフラグを指定している場合、自動生成キーは追加されたレコードのROWIDが返されます。

何が返されるかわからなかったので、試してみたらROWIDが取得出来たのですが、OracleのドキュメントにROWIDが取得されることが明記されていました。
以下が、ドキュメントの内容となります。
java.sql.Statement

キー列を明示的に指定しないと、Oracle JDBCドライバでは取り出す列を特定できません。特定できるのは、列名または列索引の配列が使用されている場合です。
ただし、int型のStatement.RETURN_GENERATED_KEYSフラグが使用されている場合、Oracle JDBCドライバではこれらの列を特定できません。int型フラグを使用して自動生成キーが戻るように指定した場合は、ROWID擬似列がキーとして戻ります。
このROWIDは、ResultSetオブジェクトからフェッチして、他の列を取り出すために使用できます。

PreparedStatement statement = connection.prepareStatement("INSERT INTO HOGE VALUES (?, ?)",
Statement.RETURN_GENERATED_KEYS);
statement.setLong(1, 1);
statement.setString(2, "name");
statement.executeUpdate();
ResultSet generatedKeys = statement.getGeneratedKeys();
if (generatedKeys.next()) {
    ROWID rowid = (ROWID) generatedKeys.getRowId(1);
    System.out.println(rowid.stringValue());
}
上のコードを実行した場合、標準出力にはROWIDを文字列表現に変換した値が出力されます。
例えば、「AAAOKLAAEAAACQTAAA」のような値が出力されます。