第7章 出力データのリッチ化

出力データをリッチ化するためにすぐ使用できる方法は次の 3 つになります。
JDBC データソースを使用してデータベースにアクセスし、SQL ステートメントを使用してデータベースへ読み書きします。この機能は、 Smooks のルーティングカートリッジより提供されます。「SQL を用いたデータベースへのルーティング」の項を参照してください。
エンティティ永続化フレームワーク (Ibatis、Hibernate、JPA 互換フレームワークなど) を使用してデータベースへアクセスし、そのクエリ言語か CRUD メソッドを使用してデータベースへ読み書きします。「エンティティ永続化フレームワーク」の項を参照してください。
カスタムのデータアクセスオブジェクト (DAO) を使用してデータベースにアクセスし、その CRUD メソッドを使用してデータベースへ読み書きします。「DAO サポート」の項を参照してください。
Smooks 1.2 の新しい Smooks 永続カートリッジでは、 Smooks 内より複数のエンティティ永続フレームワークを直接使用することができます (Hibernate、JPA など)。
Hibernate の例を見てみましょう。 別のJPA 対応フレームワークでもプリンシパルは同じです。
この例で処理されるデータは XML 注文メッセージですが、 入力データは CSV、JSON、EDI、Java、これ以外の構造化/階層化されたデータ形式でも可能です。 データの形式に関係なく、同じプリンシパルが適用されます。
<order>
    <ordernumber>1</ordernumber>
    <customer>123456</customer>
    <order-items>
        <order-item>
            <product>11</product>
            <quantity>2</quantity>
        </order-item>
        <order-item>
            <product>22</product>
            <quantity>7</quantity>
        </order-item>
    </order-items>
</order>
Hibernate のエンティティは次の通りです。
@Entity
@Table(name="orders")
public class Order {
 
    @Id
    private Integer ordernumber;
 
    @Basic
    private String customerId;
 
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List orderItems = new ArrayList();
 
    public void addOrderLine(OrderLine orderLine) {
        orderItems.add(orderLine);
    }
 
    // Getters and Setters....
}
 
@Entity
@Table(name="orderlines")
public class OrderLine {
 
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
 
    @ManyToOne
    @JoinColumn(name="orderid")
    private Order order;
 
    @Basic
    private Integer quantity;
 
    @ManyToOne
    @JoinColumn(name="productid")
    private Product product;
 
    // Getters and Setters....
}
 
@Entity
@Table(name = "products")
@NamedQuery(name="product.byId", query="from Product p where p.id = :id")
public class Product {
 
    @Id
    private Integer id;
 
    @Basic
    private String name;
 
    // Getters and Setters....
}
この例では、注文の処理と永続化を行います。最初に、注文データを注文エンティティ (Order、OrderLine および Product) へバインドする必要があります。バインドするには以下が必要となります。
  • Java Binding フレームワークを使用して Order エンティティと OrderLine エンティティを作成し投入します。
  • 各 OrderLine インスタンスを Order インスタンスへワイヤリングします。
  • 各 OrderLine インスタンス内で、関連する注文行の Product エンティティをルックアップしワイアリングする必要があります。
  • 最後に、Order インスタンスを挿入 (永続化) する必要があります。
これには、 次の Smooks 設定が必要になります。
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" 
                      xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd" 
                      xmlns:dao="http://www.milyn.org/xsd/smooks/persistence-1.2.xsd">
 
    <jb:bean BeanId="order" class="example.entity.Order" createOnElement="order">
        <jb:value property="ordernumber" data="ordernumber" />
        <jb:value property="customerId" data="customer" />
        <jb:wiring setterMethod="addOrderLine" BeanIdRef="orderLine" />
    </jb:bean>
 
    <jb:bean BeanId="orderLine" class="example.entity.OrderLine" createOnElement="order-item">
        <jb:value property="quantity" data="quantity" />
        <jb:wiring property="order" BeanIdRef="order" />
        <jb:wiring property="product" BeanIdRef="product" />
    </jb:bean>
 
    <dao:locator BeanId="product" lookupOnElement="order-item" onNoResult="EXCEPTION" uniqueResult="true">
        <dao:query>from Product p where p.id = :id</dao:query>
        <dao:params>
            <dao:value name="id" data="product" decoder="Integer" />
        </dao:params>
    </dao:locator>
 
    <dao:inserter BeanId="order" insertOnElement="order" />
 
</smooks-resource-list>
クエリ文字列の代わりに productById という名前のクエリを使用したい場合は、 DAO ロケーターを次のように設定します。
<dao:locator BeanId="product" lookupOnElement="order-item" lookup="product.byId" onNoResult="EXCEPTION" uniqueResult="true">
    <dao:params>
        <dao:value name="id" data="product" decoder="Integer"/>
    </dao:params>
</dao:locator>
次のコードは Smooks を実行します。SessionRegister オブジェクトを使用するため、Smooks 内より Hibernate セッションへアクセスできることに注意してください。
Smooks smooks = new Smooks("smooks-config.xml");
 
ExecutionContext executionContext = smooks.createExecutionContext();
 
// The SessionRegister provides the bridge between Hibernate and the
// Persistence Cartridge. We provide it with the Hibernate session.
// The Hibernate Session is set as default Session.
DaoRegister register = new SessionRegister(session);
 
// This sets the DAO Register in the executionContext for Smooks
// to access it.
PersistenceUtil.setDAORegister(executionContext, register);
 
Transaction transaction = session.beginTransaction();
 
smooks.filterSource(executionContext, source);
 
transaction.commit();
ここで DAO ベースの例を見てみましょう。この例は、注文情報が含まれている XML ファイルを読み取ります (EDI や CSV などでも同じように動作します)。 javabean カートリッジを使用して、 XML データをエンティティ Bean のセットへバインドします。 注文商品内の商品 ID を使用して (product 要素)、 商品エンティティを見つけ、order エンティティ Bean へバインドします。 最終的に注文 Bean が永続化されます。
注文の XML メッセージは次のようになります。
<order>
    <ordernumber>1</ordernumber>
    <customer>123456</customer>
    <order-items>
        <order-item>
            <product>11</product>
            <quantity>2</quantity>
        </order-item>
        <order-item>
            <product>22</product>
            <quantity>7</quantity>
        </order-item>
    </order-items>
</order>
次のカスタム DAO は Order エンティティを永続化するために使用されます。
@Dao
public class OrderDao {
 
    private final EntityManager em;
 
    public OrderDao(EntityManager em) {
        this.em = em;
    }
 
    @Insert
    public void insertOrder(Order order) {
        em.persist(order);
    }
}
このクラスを検証すると、 @Dao アノテーションと @Insert アノテーションがあることが分かります。 @Dao アノテーションは OrderDao が DAO オブジェクトであることを宣言します。 @Insert アノテーションは insertOrder メソッドを使用して Order エンティティを挿入すべきであることを宣言します。
次のカスタム DAO は、Product エンティティを検索するために使用されます。
@Dao
public class ProductDao {
 
    private final EntityManager em;
 
    public ProductDao(EntityManager em) {
        this.em = em;
    }
 
    @Lookup(name = "id")
    public Product findProductById(@Param("id")int id) {
        return em.find(Product.class, id);
    }
}
このクラスを見ると、 @Lookup アノテーションと @Param アノテーションの存在に気付くはずです。@Lookup アノテーションは、 ProductDao#findByProductId メソッドを使用して Product エンティティをルックアップすることを宣言します。 @Lookup アノテーションの name パラメータは、メソッドに対するルックアップ名の参照を設定します。 この名前が宣言されないと、 メソッド名が使用されます。 任意の @Param アノテーションはパラメータを命名できるようにします。 これにより、 Smooks と DAO 間の抽象化が向上されます。 @Param アノテーションが宣言されないと、 パラメータはポジションによって解決されます。
Smooks の設定は次のようになります。
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
                      xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.4.xsd"
                      xmlns:dao="http://www.milyn.org/xsd/smooks/persistence-1.2.xsd">
 
    <jb:bean BeanId="order" class="example.entity.Order" createOnElement="order">
        <jb:value property="ordernumber" data="ordernumber"/>
        <jb:value property="customerId" data="customer"/>
        <jb:wiring setterMethod="addOrderLine" BeanIdRef="orderLine"/>
    </jb:bean>
 
    <jb:bean BeanId="orderLine" class="example.entity.OrderLine" createOnElement="order-item">
        <jb:value property="quantity" data="quantity"/>
        <jb:wiring property="order" BeanIdRef="order"/>
        <jb:wiring property="product" BeanIdRef="product"/>
    </jb:bean>
 
    <dao:locator BeanId="product" dao="product" lookup="id" lookupOnElement="order-item" onNoResult="EXCEPTION">
        <dao:params>
            <dao:value name="id" data="product" decoder="Integer"/>
        </dao:params>
    </dao:locator>
 
    <dao:inserter BeanId="order" dao="order" insertOnElement="order"/>
 
</smooks-resource-list>
次のコードが Smooks を実行します。
Smooks smooks=new Smooks("./smooks-configs/smooks-dao-config.xml");
ExecutionContext executionContext=smooks.createExecutionContext();
 
// The register is used to map the DAO's to a DAO name. The DAO name isbe used in
// the configuration.
// The MapRegister is a simple Map like implementation of the DaoRegister.
DaoRegister<object>register = MapRegister.builder()
        .put("product",new ProductDao(em))
        .put("order",new OrderDao(em))
        .build();
 
PersistenceUtil.setDAORegister(executionContext,mapRegister);
 
// Transaction management from within Smooks isn't supported yet,
// so we need to do it outside the filter execution
EntityTransaction tx=em.getTransaction();
tx.begin();
 
smooks.filter(new StreamSource(messageIn),null,executionContext);
 
tx.commit();