LoginSignup
1
3

More than 5 years have passed since last update.

GenericDaoとHibernateでパラメーター多相な仕組みを作ってみる

Last updated at Posted at 2017-08-09

一回調べたけどお蔵入りになったのでQiitaに公開しとく

動機

  • 同じコード、SQLをたくさん書きたくない
  • RailsのActive RecordみたいなものをJavaでやってみたい

GenericDaoについての資料

結構昔の記事なので、実用性とかはよくわからない

GenericDaoを 非使用/使用 の比較

実戦投入したことがないので予測です

GenericDaoを? 使用する 使用しない
ボイラープレートコード(言語仕様上省く事ができない定型的なコード) 減らせる 頑張って書いて
SQLのチューニング できる できる
デバッグ めんどくさい めんどくさい

サンプルとコーディング

GenericDao(interface)

お手本通りinterfaceを定義する、アプリからデータベースに対して基本的な CRUD処理 を行うようにする。

  • 簡単な説明(ツッコミどころは明らかにあるのですが、面倒なので詳しくは書かない)。

    • CREATE: モデルのインスタンスを受け取ってデータベースにINSERT
    • READ: 主キーありならそれをキーにしてSELECT、無いなら全てSELECT
    • UPDATE: モデルのインスタンスを受け取ってデータベースにUPDATE
    • DELETE: 主キーを受け取ってデータベースからDELETE
  • T ← これはデータベースのテーブルにマップされたオブジェクトのクラスで、宣言時にわかります

  • PK ← これは主キーとなるもののクラスで、だいたいの場合 String, Integer, Long で宣言されるのではないでしょうか

    • 複数キー使いたいときはどうするんですかね
import java.io.Serializable;
import java.util.List;

/**
 * This is the article 10 years ago, we should follow this
 * @see https://www.ibm.com/developerworks/jp/java/library/j-genericdao/
 */
public interface GenericDao<T, PK extends Serializable> {

    /** Persist the newInstance object into database */
    PK create(T newInstance);

    /**
     * Retrieve an object that was previously persisted to the database using
     * the indicated id as primary key
     */
    T read(PK id);
    List<T> read();

    /** Save changes made to a persistent object. */
    void update(T transientObject);

    /** Remove an object from persistent storage in the database */
    void delete(PK id) throws Exception;
    void delete(T persistentObject) throws Exception;
}

GenericDaoHibernateImpl(class)

  • Springと連携してSessionFactory はDIで入れてもらいます
  • sessionFactory.getCurrentSession() することで、取得したセッションを途中から使うことができるようにします
  • private Class<T> type; を宣言しておいて、コンストラクタでそのクラスの実体を受け取ります
    • 後で実際のクラスを使用するためのトリックです
GenericDaoHibernateImpl.java
import java.io.Serializable;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

public class GenericDaoHibernateImpl<T, PK extends Serializable> implements GenericDao<T, PK> {

    private Class<T> type;

    @Autowired
    private SessionFactory sessionFactory;

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public GenericDaoHibernateImpl(Class<T> type) {
        this.type = type;
    }

    // Not showing implementations of getSession() and setSessionFactory()
    private Session getSession() {
        Session session = sessionFactory.getCurrentSession();
        return session;
    }

    @Transactional(readOnly = false, rollbackFor = RuntimeException.class)
    public PK create(T o) {
        return (PK) getSession().save(o);
    }

    @Transactional(readOnly = false, rollbackFor = RuntimeException.class)
    public void update(T o) {
        getSession().update(o);
    }

    @Transactional(readOnly = true)
    public T read(PK id) {
        return (T) getSession().get(type, id);
    }

    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true)
    public List<T> read() {
        return (List<T>) getSession().createCriteria(type).list();
    }

    @Transactional(readOnly = false, rollbackFor = RuntimeException.class)
    public void delete(PK id) {
        T o = getSession().load(type, id);
        getSession().delete(o);
    }

    @Transactional(readOnly = false, rollbackFor = RuntimeException.class)
    public void delete(T o) {
        getSession().delete(o);
    }
}

EmployeesDao - Model層サンプル

では実際にモデルと連携するためのDao(Data Access Object)を書きます

  • サンプルとしてMySQLの以下のようなテーブルからCRUD処理を行うDaoを作ってみます
    • emp_no が主キーなので、これでSELECTなどできるはずです

SQL

CREATE TABLE employees (
    emp_no      INT             NOT NULL,  -- UNSIGNED AUTO_INCREMENT??
    birth_date  DATE            NOT NULL,
    first_name  VARCHAR(14)     NOT NULL,
    last_name   VARCHAR(16)     NOT NULL,
    gender      ENUM ('M','F')  NOT NULL,  -- Enumeration of either 'M' or 'F'  
    hire_date   DATE            NOT NULL,
    PRIMARY KEY (emp_no)                   -- Index built automatically on primary-key column
                                           -- INDEX (first_name)
                                           -- INDEX (last_name)
);

Model層

O/R Mapするためのクラスを書きましょう。Getter/Setterを書くのは止めてlombokで生成しましょう。

ポイント

Employees.java
import java.io.Serializable;
import java.sql.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@Table(name = "employees")
public class Employees implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "emp_no", unique = true)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer empNo;

    @Column(name = "birth_date")
    private Date birthDate;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "gender")
    @Enumerated(EnumType.STRING)
    private Gender gender;

    @Column(name = "hire_date")
    private Date hireDate;
}

Dao

  • 実際のDaoクラス
    • やっと楽できた感じがする
    • Daoをinterfaceとして用意しておき、実装はSpringで注入する(←すごい)
EmployeesDao.java
public interface EmployeesDao extends GenericDao<Employees, Integer> {
}

DaoのSpring設定

  • DaoとSpring AOPとHibernateを設定するための前準備です
    • 本質的に必要なのは、 <bean id="employeesDao" parent="abstractDao"> の部分のみです
applicationContext.xml
    <!-- DataSourceの設定 -->
    <bean id="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    <!-- sessionFactory -->
    <bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <!-- 上で設定したDataSource -->
        <property name="dataSource" ref="myDataSource" />

        <!-- モデルクラスがある名前空間を指定 -->
        <property name="packagesToScan" value="package.to.your.models" />

        <!-- Hibernateの接続設定 -->
        <property name="hibernateProperties">
            <props>
                <prop key="dialect">org.hibernate.dialect.MySQLDialect</prop>
                <!-- ここは本番でもテストでもtrueにしておかないとデバッグできなさそう -->
                <prop key="show_sql">true</prop>
                <prop key="format_sql">true</prop>
                <prop key="connection.CharSet">utf8</prop>
                <prop key="connection.characterEncoding">utf8</prop>
                <prop key="connection.useUnicode">true</prop>
            </props>
        </property>
    </bean>

    <!-- transactionManagerを使う -->
    <tx:annotation-driven />
    <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="mySessionFactory" />
    </bean>

    <!-- GenericDaoHibernateImpl に 上で作った sessionFactory を注入する -->
    <bean id="abstractDaoTarget" class="jp.gr.java_conf.hangedman.dao.GenericDaoHibernateImpl" abstract="true">
        <property name="sessionFactory">
            <ref bean="mySessionFactory" />
        </property>
    </bean>

    <!-- Spring AOPを使って実装を注入する準備 -->
    <bean id="abstractDao" class="org.springframework.aop.framework.ProxyFactoryBean"
        abstract="true">
    </bean>

    <!-- Dao, Modelを設定、やっと終わり。ここから下の<bean>タグはDaoごとに必要か -->
    <bean id="employeesDao" parent="abstractDao">
        <!-- You need to configure the interface for Dao -->
        <property name="proxyInterfaces">
            <value>jp.gr.java_conf.hangedman.dao.EmployeesDao</value>
        </property>
        <property name="target">
            <bean parent="abstractDaoTarget">
                <constructor-arg>
                    <value>jp.gr.java_conf.hangedman.models.Employees</value>
                </constructor-arg>
            </bean>
        </property>
    </bean>

サンプル

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3