1対多の関係テーブルにおけるデータ追加

CriteriaAPIを使った親子テーブルへのデータ追加

注文と注文明細のように、1対多の関連を持つテーブルにおいてデータ追加を実施する場合は、単テーブルにおける追加と同様、 JPAのCriteria APIを使用して実装する。GenericDaoの様に共通化したメソッドを経由して実行するとよい。

基本的な所作としては、1対多の関連のうち、1に相当する(親子関係で言えば、親に相当する)オブジェクトに必要な関連テーブルのオブジェクト(子に相当する)をセットして、persist()メソッドを実行する。 関連テーブルのオブジェクトも合わせて登録するためには、親となるエンティティクラスのOneToManyアノテーションにcascade属性を設定しておくことが必要である。

例) Userオブジェクトに加えて、関連テーブルである、Address、Email、Phoneも一緒にデータ登録する場合。

BackingBeanにて、リクエストパラメータを受け取り、Address、Email、Phoneをリストに含めて保持するUserオブジェクト作成し、ユーザ保存のロジックを呼び出す。

test-javaee6-web org.debugroom.test.app.web.dbaccess.OneToManyInsertFormBackingBean

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package org.debugroom.test.app.web.dbaccess;

import java.util.List;
import java.util.ArrayList;

import javax.ejb.EJB;
import javax.enterprise.inject.Model;

import org.debugroom.test.common.exception.BusinessException;
import org.debugroom.test.common.exception.SystemException;
import org.debugroom.test.common.support.dozer.DozerInstantiator;
import org.debugroom.test.domain.model.Address;
import org.debugroom.test.domain.model.AddressPK;
import org.debugroom.test.domain.model.EmailPK;
import org.debugroom.test.domain.model.Phone;
import org.debugroom.test.domain.model.PhonePK;
import org.debugroom.test.domain.model.User;
import org.debugroom.test.domain.model.Email;
import org.debugroom.test.domain.service.dbaccess.OneToManyInsertService;
import lombok.Data;

@Data
@Model
public class OneToManyInsertFormBackingBean {

    @EJB
    OneToManyInsertService oneToManyInsertService;

    @EJB
    DozerInstantiator dozerInstantiator;

    // オブジェクトの初期化
    private User user;
    { user = new User(); }

    private Email email;
    { email = new Email();
      email.setId(new EmailPK());
      email.getId().setEmailNo(0);
    }

    private Address address;
    { address = new Address();
      address.setId(new AddressPK());
      address.getId().setAddressNo(0);
    }

    private Phone phone;
    { phone = new Phone();
      phone.setId(new PhonePK());
      phone.getId().setPhoneNo(0);
    }

    public String register() throws SystemException{

        // データ保存用のsaveUserオブジェクトを作成し、リクエストパラメータをコピーする。
        // データのマッピングにはDozerライブラリを用いるが、SingletonBeanとしてdozerInstantiatorでラップしてマッピング実行
        // マッピングが煩雑なので、Helperのようなクラスを本当は作ったほうがよい。今回はサンプルのためそのまま記述。
        User saveUser = dozerInstantiator.getMapper().map(user, User.class);
        List<Email> emails = new ArrayList<Email>();
        List<Phone> phones = new ArrayList<Phone>();
        List<Address> addresses = new ArrayList<Address>();
        saveUser.setAddresses(addresses);
        saveUser.setPhones(phones);
        saveUser.setEmails(emails);

        Address saveAddress = dozerInstantiator.getMapper().map(address, Address.class);
        Email saveEmail = dozerInstantiator.getMapper().map(email, Email.class);
        Phone savePhone = dozerInstantiator.getMapper().map(phone, Phone.class);
        addresses.add(saveAddress);
        phones.add(savePhone);
        emails.add(saveEmail);

        try {
            // リクエストパラメータをマッピングした後、保存ロジックを実行
            saveUser = oneToManyInsertService.saveUser(saveUser);
            // 実行した結果をビュー向けのオブジェクトにマッピングしなおす。
            dozerInstantiator.getMapper().map(saveUser, user);
        } catch (BusinessException e) {
            throw new SystemException("E0003", e);
        }
        return "/jsf/dbaccess/oneToManyInsertOutput??faces-redirect-true";
    }
 }

test-javaee6-ejb org.debugroom.test.domain.service.impl.OneToManyInsertServiceImpl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package org.debugroom.test.domain.service.impl.ejb.dbaccess;

import java.util.Date;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;

import org.debugroom.test.common.exception.BusinessException;
import org.debugroom.test.domain.model.Address;
import org.debugroom.test.domain.model.Email;
import org.debugroom.test.domain.model.Phone;
import org.debugroom.test.domain.model.User;
import org.debugroom.test.domain.repository.UserRepository;
import org.debugroom.test.domain.service.dbaccess.OneToManyInsertService;

@Stateless
public class OneToManyInsertServiceImpl implements OneToManyInsertService{

    @EJB
    UserRepository userRepository;

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public User saveUser(User user) throws BusinessException {
        // Userに新しいユーザIDと更新日時を設定する。
        Long numberOfUser = userRepository.count();
        String idFormat = new StringBuilder()
                                .append("00000000")
                                .append(numberOfUser)
                                .toString();
        String newUserId = idFormat.substring(idFormat.length() - 8, idFormat.length());
        user.setUserId(newUserId);
        user.setLastUpdatedDateAndTime(new Date());

        // Addressオブジェクトに新しいユーザIDを設定する。
        for(Address address : user.getAddresses()){
           address.getId().setUserId(newUserId);
        }

        // Emailオブジェクトに新しいユーザIDを設定する。
        for(Email email : user.getEmails()){
           email.getId().setUserId(newUserId);
        }

        // Phoneオブジェクトに新しいユーザIDを設定する。
        for(Phone phone : user.getPhones()){
           phone.getId().setUserId(newUserId);
        }

       // GenericDaoImplのsave()メソッドを実行する。
        if(!userRepository.save(user)){
           throw new BusinessException("E-0001");
        };
        return user;
    }
 }

データ保存のメソッドは単一テーブル保存のときと変わらない。

test-javaee6-ejb org.debugroom.test.domain.service.impl.OneToManyInsertServiceImpl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.debugroom.test.domain.repository.impl.jpa;

import java.util.Date;
import java.util.List;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import org.debugroom.test.domain.model.User;
import org.debugroom.test.domain.repository.UserRepository;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class UserRepositoryImpl extends GenericDaoImpl<User, String> implements UserRepository{

    @Override
    public boolean save(User user) {
        if(entityManager.contains(user)){
            return false;
        }
        persist(user);
        return true;
    }
}

関連テーブルのデータもまとめて追加するには、エンティティクラスの@OneToManyアノテーションにおけるcascade属性を変更する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package org.debugroom.test.domain.model;

import java.io.Serializable;
import javax.persistence.*;
import java.util.Date;
import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Builder;

/**
 * The persistent class for the duser database table.
 *
 */
@AllArgsConstructor
@Builder
@Entity
@Table(name="duser")
@NamedQuery(name="User.findAll", query="SELECT u FROM User u")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

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

    @Column(name="is_login")
    private Boolean isLogin;

    @Temporal(TemporalType.DATE)
    @Column(name="last_updated_date_and_time")
    private Date lastUpdatedDateAndTime;

    @Column(name="user_name")
    private String userName;

    private Integer ver;

    @OneToMany(mappedBy="duser", cascade = CascadeType.ALL)
    private List<Address> addresses;

    @OneToMany(mappedBy="duser", cascade = CascadeType.ALL)
    private List<Affiliation> affiliations;

    @OneToMany(mappedBy="duser", cascade = CascadeType.ALL)
    private List<Credential> credentials;

    @OneToMany(mappedBy="duser", cascade = CascadeType.ALL)
    private List<Email> emails;

    @OneToMany(mappedBy="duser", cascade = CascadeType.ALL)
    private List<Phone> phones;

    //omitted
}

このとき、発行されるSQLは以下のようになる。

1
2
3
4
5
6
7
insert into duser (is_login, last_updated_date_and_time, user_name, ver, user_id) values (?, ?, ?, ?, ?)

insert into Address (address, address_details, ver, zip_code, address_no, user_id) values (?, ?, ?, ?, ?, ?)

insert into Email (email, ver, email_no, user_id) values (?, ?, ?, ?)

insert into Phone (phone_number, ver, phone_no, user_id) values (?, ?, ?, ?)

Userに加えて、Address, Email, Phoneも追加される。Credentialなどは値が設定されていないので更新されない。