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

CriteriaAPIを使った親子テーブルの一括データ更新

注文と注文明細のように、1対多の関連を持つテーブルにおけるデータ更新を実施する場合は、単テーブルにおける更新と同様、 JPAのCriteria APIを使用して実装する。基本的には関連するデータの中で親となるテーブルのデータを取得して、更新したいプロパティに入力値をセットするだけでよい。トランザクションがコミットしたタイミングでUPDATE文が発行される。

例) ユーザテーブルにおけるユーザ名と、関連テーブルとして、住所、メール、電話番号をまとめて更新する場合。

test-javaee6-ejb org.debugroom.test.domain.service.impl.ejb.dbaccess.OneToManyUpdateServiceImpl

 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.domain.service.impl.ejb.dbaccess;

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.OneToManyUpdateService;

@Stateless
public class OneToManyUpdateServiceImpl implements OneToManyUpdateService{

    @EJB
    UserRepository userRepository;

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public User updateUser(User user) throws BusinessException {
        // 変更対象データで、親テーブルに相当するエンティティを取得する。
        User updateUser =  userRepository.find(user.getUserId());

        // 入力パラメータがNULLではなく、かつ変更があるのであれば、プロパティを更新する。
        if(user.getUserName() != null && !user.getUserName().equals(updateUser.getUserName())){
            updateUser.setUserName(user.getUserName());
        }

        // 関連テーブルはList型で保持されているため、ループでキーが一致し、変更があるもののみ更新する実装とする。
        // 件数が多くなる場合は別の実装方法でプロパティ変更してもよい。
        for(Address address : user.getAddresses()){
            for(Address updateAddress: updateUser.getAddresses()){
                // 入力パラメータがNULLではなく、キーが一致し、かつ変更があるのであれば、プロパティを更新する。
                if(address != null && address.getZipCode() != null &&
                        address.getId().equals(updateAddress.getId()) &&
                        !address.getZipCode().equals(updateAddress.getZipCode())){
                    updateAddress.setZipCode(address.getZipCode());
                }
                // 入力パラメータがNULLではなく、キーが一致し、かつ変更があるのであれば、プロパティを更新する。
                if(address != null && address.getAddress() != null &&
                        address.getId().equals(updateAddress.getId()) &&
                        !address.getAddress().equals(updateAddress.getAddress())){
                    updateAddress.setAddress(address.getAddress());
                }
                // 入力パラメータがNULLではなく、キーが一致し、かつ変更があるのであれば、プロパティを更新する。
                if(address != null && address.getAddressDetails() != null &&
                        address.getId().equals(updateAddress.getId()) &&
                        !address.getAddressDetails().equals(updateAddress.getAddressDetails())){
                    updateAddress.setAddressDetails(address.getAddressDetails());
                }
            }
        }

        // 関連テーブルはList型で保持されているため、ループでキーが一致し、変更があるもののみ更新する実装とする。
        // 件数が多くなる場合は別の実装方法でプロパティ変更してもよい。
        for(Email email : user.getEmails()){
            for(Email updateEmail : updateUser.getEmails()){
                // 入力パラメータがNULLではなく、キーが一致し、かつ変更があるのであれば、プロパティを更新する。
                if(email != null && email.getEmail() != null &&
                        email.getId().equals(updateEmail.getId()) &&
                        !email.getEmail().equals(updateEmail.getEmail())){
                    updateEmail.setEmail(email.getEmail());
                }
            }
        }

        // 関連テーブルはList型で保持されているため、ループでキーが一致し、変更があるもののみ更新する実装とする。
        // 件数が多くなる場合は別の実装方法でプロパティ変更してもよい。
        for(Phone phone : user.getPhones()){
            for(Phone updatePhone : updateUser.getPhones()){
                // 入力パラメータがNULLではなく、キーが一致し、かつ変更があるのであれば、プロパティを更新する。
                if(phone != null && phone.getPhoneNumber() != null &&
                        phone.getId().equals(updatePhone.getId()) &&
                        ! phone.getPhoneNumber().equals(updatePhone.getPhoneNumber())){
                    updatePhone.setPhoneNumber(phone.getPhoneNumber());
                }
            }
        }
        return updateUser;
    }

関連テーブルを更新するためには、エンティティクラスの@OneToManyアノテーションにおけるCascadeType属性をMERGEもしくはALLにしておく必要がある。また、ここではアノテーション@Versionを使用して、楽観ロックによるロングトランザクションの排他制御を実現する。

 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
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;

@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;

    // 楽観ロックとして@Versionカラムを指定
    @Version
    private Integer ver;

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

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

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

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

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

    //omitted
 }

このとき、発行するSQLは以下のようになる。 デフォルトでは関連するテーブル毎にLazy-Loadでデータ取得が行われる。 関連するテーブルが多い場合は、JPQLでテーブル結合したデータ取得を検討すること。

 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
select
  user0_.user_id as user_id1_5_0_,
  user0_.is_login as is_login2_5_0_,
  user0_.last_updated_date_and_time as last_upd3_5_0_,
  user0_.user_name as user_nam4_5_0_,
  user0_.ver as ver5_5_0_
from duser user0_
where user0_.user_id=?

select
  addresses0_.user_id as user_id2_5_1_,
  addresses0_.address_no as address_1_0_1_,
  addresses0_.user_id as user_id2_0_1_,
  addresses0_.address_no as address_1_0_0_,
  addresses0_.user_id as user_id2_0_0_,
  addresses0_.address as address3_0_0_,
  addresses0_.address_details as address_4_0_0_,
  addresses0_.ver as ver5_0_0_,
  addresses0_.zip_code as zip_code6_0_0_
from Address addresses0_
  where addresses0_.user_id=?

select
  emails0_.user_id as user_id2_5_1_,
  emails0_.email_no as email_no1_3_1_,
  emails0_.user_id as user_id2_3_1_,
  emails0_.email_no as email_no1_3_0_,
  emails0_.user_id as user_id2_3_0_,
  emails0_.email as email3_3_0_,
  emails0_.ver as ver4_3_0_
from Email emails0_
  where emails0_.user_id=?

select
  phones0_.user_id as user_id2_5_1_,
  phones0_.phone_no as phone_no1_4_1_,
  phones0_.user_id as user_id2_4_1_,
  phones0_.phone_no as phone_no1_4_0_,
  phones0_.user_id as user_id2_4_0_,
  phones0_.phone_number as phone_nu3_4_0_,
  phones0_.ver as ver4_4_0_
from Phone phones0_
  where phones0_.user_id=?

update duser set
  is_login=?,
  last_updated_date_and_time=?,
  user_name=?,
  ver=?
where user_id=?

update Address set
  address=?,
  address_details=?,
  ver=?,
  zip_code=?
where address_no=?
  and user_id=?

update Phone set
  phone_number=?,
  ver=?
where phone_no=?
  and user_id=?

update Email set
  email=?,
  ver=?
where email_no=?
  and user_id=?