Hibernate による O/R マッピング(その 3)

DAO クラスの作成

package jp.mydns.akanekodou.dao;

import org.hibernate.SessionFactory;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class DaoUtil {
    private SessionFactory sessionFactory;
    private static DaoUtil inst = new DaoUtil();

    private DaoUtil() {
        sessionFactory = createSessionFactory();
    }

    public static SessionFactory getSessionFactory() {
        return inst.sessionFactory;
    }

    private SessionFactory createSessionFactory() {
        Configuration conf = new Configuration().configure();
        ServiceRegistryBuilder sb = new ServiceRegistryBuilder();
        sb.applySettings(conf.getProperties());
        ServiceRegistry sr = sb.buildServiceRegistry();
        return conf.buildSessionFactory(sr);
    }
}

SessionFactory はスレッドセーフなのでシングルトンにして使い回します。

package jp.mydns.akanekodou.dao;

import org.hibernate.Session;
import org.hibernate.Criteria;

import java.util.List;

import jp.mydns.akanekodou.model.Customer;

public class CustomerDAO {
    private Session session;

    public CustomerDAO() {
        session = DaoUtil.getSessionFactory().openSession();
    }

    public List<Customer> all() {
        Criteria cr = session.createCriteria(Customer.class);
        @SuppressWarnings("unchecked")
        List<Customer> result = cr.list();

        return result;
    }

    public Customer find(int id) {
        return (Customer)session.get(Customer.class, id);
    }
}

get メソッドは主キーを指定してデータを取得するメソッドです。似たような働きをするものに load メソッドがあります。get メソッドと load メソッドの違いとしては

get メソッド
  • メソッド呼び出し時に直ちに SQL 文が発行される
  • 返り値は一時的なもので、これに変更を加えてもデータベースには反映されない
  • session を close した後もアクセスできる
  • 該当するデータがなければ null を返す
load メソッド
  • メソッド呼び出し時には SQL 文は発行されず、getter が呼び出されて初めて評価される(Lazy loading)
  • 返り値は永続化されたオブジェクトであり、これに変更を加えるとデータベースにも反映される
  • session を close 後、それ以前に実行していなかった getter を呼び出すと LazyInitializationException が throw される
  • 該当するデータがない場合でも null にはならないが、getter などでアクセスしようとすると ObjectNotFoundException が throw される

このように load メソッドは極めて Hibernate らしい*1挙動をします。この場合は入力値に対応するデータが存在するとは限りませんので get を使うことにします。

package jp.mydns.akanekodou.dao;

import org.hibernate.Session;
import org.hibernate.Query;

import java.util.List;

import jp.mydns.akanekodou.model.Product;

public class ProductDAO {
    private Session session;

    public ProductDAO() {
        session = DaoUtil.getSessionFactory().openSession();
    }

    public List<Product> like(String keyword) {
        Query query = session.createQuery("from Product where name like :keyword");
        query.setString("keyword", "%" + keyword.replace("%", "\\%").replace("_", "\\%") + "%");
        @SuppressWarnings("unchecked")
        List<Product> result = query.list();

        return result;
    }
}

HQL 文を用いてあいまい検索を実行しています。

Query query = session.createQuery("from Product where name like :keyword");
query.setString("keyword", "%" + keyword + "%");

の部分は非常に重要です。何故ならば、これをうっかり

Query query = session.createQuery("from Product where name like '%" + keyword + "%'");

のように書いてしまうととんでもないことになります。もし keyword

xxx';delete from Product;

という文字列が送られて来たらどうなると思いますか ? おそらくこの delete 文は実行されるでしょう。それによって大事なデータが全て消去されてしまうことになります。HQL インジェクションと言ってもいいセキュリティホールの出来上がりです。SQL が HQL になっても、書き方一つでセキュリティホールは出来上がってしまいます。ご注意を。後程 HQL 文を使わない方法をご紹介します。

*1:永続化オブジェクト、というのは Hibernate や後述する JPA に固有の考え方です。