データの更新

今回はレコード更新についてです。

【前提条件】

[環境]
[その他]
  • JPAの設定が完了している*1

【概要】

EntityManagerによって管理されているオブジェクトに対するpersistは更新が行われ、
管理されていないオブジェクトに対するpersistは登録が行われます。

そのため、JPAでレコードの更新を行う場合、エンティティを取得する必要があります。

EntityManager#mergeメソッドを使うと
EntityManagerによって管理されていなくても更新できるようですが、
そもそもの用途として異なるようです。

非常に参考になるStackoverflowの議論。
JPA EntityManager: Why use persist() over merge()? - Stack Overflow

【サンプルコード】

[テーブル]

今回は以前作成した
「userdata」テーブルと「user_authority」テーブルを使います。

CREATE TABLE userdata
(
    user_id character varying(10) NOT NULL,
    name character varying(60),
    last_update timestamp with time zone NOT NULL DEFAULT now(),
    CONSTRAINT user_primary_key PRIMARY KEY (user_id )
)
CREATE TABLE user_authority
(
  seq_number serial NOT NULL,
  auth_user_id character varying(10) NOT NULL,
  authority_type integer,
  CONSTRAINT authority_primary_key PRIMARY KEY (seq_number )
)
[Entity]

エンティティのクラスです。
UserData,UserAuthorityともに以前と変更はありません。

package jp.glory.persistence.entity;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "userdata")
@NamedQueries({
        @NamedQuery(name = "findAll", query = "SELECT u FROM UserData u"),
        @NamedQuery(name = "findByUserId", query = "SELECT u FROM UserData u WHERE u.userId = :userId"),
})
public class UserData implements Serializable {

    private static final long serialVersionUID = 6528642302239465812L;

    @Id
    @Column(name = "user_id")
    private String userId = null;

    private String name = null;

    @Column(name = "last_update")
    private Timestamp lastUpdate = null;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumns({
        @JoinColumn(name = "auth_user_id", referencedColumnName = "user_id")
    })
    private List<UserAuthority> authorityList = null;

    // アクセサメソッドは省略
}
package jp.glory.persistence.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name = "user_authority")
@NamedQueries({
    @NamedQuery(name = "deleteAuthorityByUserId", query = "DELETE FROM UserAuthority ua WHERE ua.authUserId = :userId")
})
public class UserAuthority implements Serializable {

    private static final long serialVersionUID = 1625221760062551052L;

    @Id
    @Column(name = "seq_number")
    @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "user_authority_seq_number_seq")
    private int seqNumber = 0;

    @Column(name = "auth_user_id")
    private String authUserId = null;

    @Column(name = "authority_type")
    private int authorityType = 0;

    // アクセサメソッドは省略
}
[更新クラス]

更新を行うEJBのクラスです。

package jp.glory.application;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import jp.glory.persistence.entity.UserData;

@Stateless
public class UserEntryApplication {

    @PersistenceContext(unitName = "blog")
    private EntityManager manager = null;

    public void merge(final UserData userData) {

        mergeVersion2(userData);
    }

    private void mergeVersion1(final UserData userData) {

        final UserData updateData = manager.find(UserData.class, userData.getUserId());
        if (updateData == null) {

            manager.persist(userData);
        } else {

            Query query = manager.createNamedQuery("deleteAuthorityByUserId");
            query.setParameter("userId", userData.getUserId());
            query.executeUpdate();

            updateData.getAuthorityList().clear();
            updateData.getAuthorityList().addAll(userData.getAuthorityList());

            updateData.setName(userData.getName());
            updateData.setLastUpdate(userData.getLastUpdate());

            manager.persist(updateData);
        }
    }

    private void mergeVersion2(final UserData userData) {

        Query query = manager.createNamedQuery("deleteAuthorityByUserId");
        query.setParameter("userId", userData.getUserId());
        query.executeUpdate();

        manager.merge(userData);
    }
}

サンプルのmergeVersion1メソッドでは
persistメソッドを使用する場ので
一度、EntityManagerからエンティティのオブジェクトを取得しています。

サンプルのmergeVersion2メソッドでは
mergeメソッドを使用しているので、事前の取得を行っていません。

余談
サンプルコードを作ってから、
UserAuthorityエンティティの設計がいけてないなぁと反省したり・・・
削除フラグで制御すれば、事前のDELETEはいらなかったのではと思います・・・

[ManagedBean]

登録画面のManagedBeanです。

package jp.glory.ui;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;

import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import jp.glory.application.UserEntryApplication;
import jp.glory.persistence.entity.UserAuthority;
import jp.glory.persistence.entity.UserData;

@ManagedBean(name = "userEntryPage")
@RequestScoped
public class UserEntryPage {

    @EJB
    private UserEntryApplication application = null;

    private String userId = null;

    private String name = null;

    private boolean authority1 = false;

    private boolean authority2 = false;

    private boolean authority3 = false;

    private boolean authority4 = false;

    public void entry() {

        UserData userData = createData();

        application.merge(userData);

        clearInputData();
    }

    private UserAuthority createAuthority(final int authorityType) {

        final UserAuthority authority = new UserAuthority();
        authority.setAuthorityType(authorityType);
        authority.setAuthUserId(userId);

        return authority;
    }

    private void clearInputData() {

        userId = null;
        name = null;
        authority1 = false;
        authority2 = false;
        authority3 = false;
        authority4 = false;
    }

    private UserData createData() {

        final UserData userData = new UserData();
        userData.setUserId(userId);
        userData.setName(name);
        userData.setLastUpdate(new Timestamp(System.currentTimeMillis()));

        final List<UserAuthority> authorityList = new ArrayList<>();
        if (authority1) {
            authorityList.add(createAuthority(1));
        }

        if (authority2) {
            authorityList.add(createAuthority(2));
        }

        if (authority3) {
            authorityList.add(createAuthority(3));
        }

        if (authority4) {
            authorityList.add(createAuthority(4));
        }
        userData.setAuthorityList(authorityList);

        return userData;
    }

    // アクセサメソッドは省略
}
[ページ]

登録画面のページです。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html">
    <f:view>
        <h:head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <title>登録サンプル</title>
        </h:head>
        <h:body>
            <h:form id="entryForm">
                <div>
                    ユーザID:<h:inputText id="userId" value="#{userEntryPage.userId}" />
                </div>
                <div>
                    ユーザ名:<h:inputText id="name" value="#{userEntryPage.name}" />
                </div>
                <div>
                    権限1:<h:selectBooleanCheckbox value="#{userEntryPage.authority1}" />
                </div>
                <div>
                    権限2:<h:selectBooleanCheckbox value="#{userEntryPage.authority2}" />
                </div>
                <div>
                    権限3:<h:selectBooleanCheckbox value="#{userEntryPage.authority3}" />
                </div>
                <div>
                    権限4:<h:selectBooleanCheckbox value="#{userEntryPage.authority4}" />
                </div>
                <div>
                    <h:commandButton action="#{userEntryPage.entry()}" value="登録" />
                </div>
            </h:form>
        </h:body>
    </f:view>
</html>