Class.forName で DB アクセスできるようになる理由

Java の研修で DB(だいたいMDBかな) にアクセスするプログラムを作ることになったとき、講師はほぼ 100% 「JDBC Driver を使用するためには Class.forName を使用します」と言うはず。ただ、呪文のごとく。
で、Class.forName の API を見てみる。

forName(String name, boolean initialize, ClassLoader loader)
指定されたクラスローダを使って、指定された文字列名を持つクラスまたはインタフェースに関連付けられた Class オブジェクトを返します

そして疑問が生まれる。「クラスをロードするだけでなんでDBにアクセスできるようになるの?」と。
講師はなぜできるかは説明しない。分かってないってことは無いと思うけど「まだ初心者だから覚えとけばいい」的な感じだろう。
けど、ここは言わせてもらう!
説明すべきだ!
おかげで今になってようやく理解できた。特に必要なかったのもあるけど、ふと気になって調べてみた。
んで、こういう事(間違ってたら恥ずかしいから早く教えてください)。

各ベンダの JDBC Driver は java.sql.Driver を実装している必要があり、その実装クラスは static initializer で java.sql.DriverManager.registerDriver(java.sql.Driver) を使って登録している。

Class.forName -> static initializer -> DriverManager.registerDriver って流れ。あとは DriverManager を経由して各 Driver にアクセスして使用する。
ついでにいくつかの JDBC Driver のソースを確認してみた。

sun

JDBC Driver の登録

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

static initializer

public class JdbcOdbcDriver
	extends		JdbcOdbcObject
	implements	JdbcOdbcDriverInterface
{
  //--------------------------------------------------------------------
  // Static method to be executed when the class is loaded.
  //--------------------------------------------------------------------
  static
  {		
    JdbcOdbcTracer tracer1 = new JdbcOdbcTracer();
    if (tracer1.isTracing ()) {
      tracer1.trace ("JdbcOdbcDriver class loaded");
    }

    JdbcOdbcDriver driver = new JdbcOdbcDriver ();

    // Attempt to register the driver

    try {
      DriverManager.registerDriver (driver);
    }
    catch (SQLException ex) {
      if (tracer1.isTracing ()) {
        tracer1.trace ("Unable to register driver");
      }  
    }
  }

MySQL

JDBC Driver の登録

Class.forName("com.mysql.jdbc.Driver");

static initializer

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
  // ~ Static fields/initializers
  // ---------------------------------------------

  //
  // Register ourselves with the DriverManager
  //
  static {
    try {
      java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
      throw new RuntimeException("Can't register driver!");
    }
  }

PostgreSQL

JDBC Driver の登録

Class.forName("org.postgresql.Driver"); 

static initializer

public class Driver implements java.sql.Driver
{

    // make these public so they can be used in setLogLevel below

    public static final int DEBUG = 2;
    public static final int INFO = 1;

    private static final Logger logger = new Logger();
    private static boolean logLevelSet = false;

    static
    {
        try
        {
            // moved the registerDriver from the constructor to here
            // because some clients call the driver themselves (I know, as
            // my early jdbc work did - and that was based on other examples).
            // Placing it here, means that the driver is registered once only.
            java.sql.DriverManager.registerDriver(new Driver());
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
    }

SQLite

JDBC Driver の登録

Class.forName("org.sqlite.JDBC");

static initializer

public class JDBC implements Driver
{
    private static final String PREFIX = "jdbc:sqlite:";

    static {
        try {
            DriverManager.registerDriver(new JDBC());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

長年の疑問がまた一つ解けたよ。

HashMap#put のソースを読む

ちょっと疑問があったので調べていたのだが、ソースも公開されている事だしちょっとずつ Java のソースを見ていこうと思う。仕事では JDK1.4 だけど読むのは JDK5.0 にする。今更ながら結構違うのよね。ジェネリックスが登場してソースコードも結構修正されてる。
第一弾は HashMp#put

public V put(K key, V value) {
  K k = maskNull(key);
  int hash = hash(k);
  int i = indexFor(hash, table.length);

  for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    if (e.hash == hash && eq(k, e.key)) {
      V oldValue = e.value;
      e.value = value;
      e.recordAccess(this);
      return oldValue;
    }
  }

  modCount++;
  addEntry(hash, k, value, i);
  return null;
}

まず、K とか V とかってなんだよ(笑) Object じゃないのかよ。理解不能だがこれは後で調べる。
気になったのが、

return oldValue

put したら一つ前の値が返ってくるんだよ (^^; 今まで実装で困ったことは無いしこれをあてにしたことも無いから実害は無いと思うけど、、、ちょっと微妙だなぁ、、、普通に考えれば今突っ込んだ値を返してメソッドチェーンでつなぐんじゃね?
とりあえずテストコードを書いてみた。

Map map = new HashMap();
for(int i = 0 ; i < 9 ; i++) {
  System.out.println("put " + i + " -> " + map.put("KEY" , i + ""));
}

実行結果

put 0 -> null
put 1 -> 0
put 2 -> 1
put 3 -> 2
put 4 -> 3
put 5 -> 4
put 6 -> 5
put 7 -> 6
put 8 -> 7

一つ前の値が返ってきてるいるのが分かる。そして、JavaDoc を見ていて気がついた。

@return previous value associated with specified key

ちゃんと「キーに関連する前の値が返される」と書かれている。日本語訳されている JavaDoc を見てみる

指定されたキーに関連した値

HashMap#put JDK5

いや、誤訳じゃね?
気になったので、JDK6 の API も見てみた。

key に以前に関連付けられていた値

HashMap#put JDK6

直ってる!
安心した。

[rails] migration 分かりずれー

ruby script/generate model モデル名

ruby script/generate migration 変更名

rake db:migrate

の違いが分かりずらい。順番的には、

  1. テーブルを追加するために generate model -> rake
  2. 変更したくなったら generate migration -> rake
  3. テーブルを追加したくなったら generate model -> rake

を繰り返す感じかな。変更を実際に DB へ反映させるためには rake しないといけないところがポイント。