如何更新@OneToMany Hibernate 关系

How to update @OneToMany Hibernate relationship

我无法更新依赖对象列表。我有一个 API 应该更新客户帐户列表。 一个客户 - 许多帐户。

我按照指示配置了@OneToMany 以进行正确更新:

@OneToMany(mappedBy = "client", orphanRemoval = true, cascade = CascadeType.ALL)

实体:

@Entity
@Getter
@Setter
public class Client {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "id_client")
    private Integer id;

    private String name;
    private int age;

    @OneToMany(mappedBy = "client", orphanRemoval = true, cascade = CascadeType.ALL)
    private List<Account> accounts = new ArrayList<>();
}

@Entity
@Getter
@Setter
public class Account {
    @Id
    @GeneratedValue
    @Column(name = "id_account")
    private Integer id;

    private int amount;
    private String currency;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "id_client")
    private Client client;
}

我想对所提供的数据执行的操作。我已经有一个客户 (id = 100) 有两个帐户 (id = 10, 11)。

我想进行更新,以便客户端具有不同的帐户列表 id:10、12。

我的测试和测试数据。

<dataset>
    <Client id_client="100" name="John" age="23"/>
    <Client id_client="101" name="Mike" age="28"/>
    <Client id_client="102" name="Kevin" age="19"/>

    <Account id_account="10" amount="50" currency="USD" id_client="100"/>
    <Account id_account="11" amount="100" currency="USD" id_client="100"/>
    <Account id_account="12" amount="150" currency="EUR" id_client="101"/>
    <Account id_account="13" amount="200" currency="EUR" id_client="102"/>
</dataset>

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
@TestExecutionListeners({
        TransactionalTestExecutionListener.class,
        DependencyInjectionTestExecutionListener.class,
        DbUnitTestExecutionListener.class
})
@Transactional
@DatabaseSetup("/data.xml")
public class HibTest {

    @PersistenceContext
    protected EntityManager em;

    protected Session session;

    @Before
    public void dbAllSet() {
        session = em.unwrap(Session.class);
    }

    @Test
    @Commit
    public void mergeCollections() {
        Client client = session.get(Client.class, 100); // with accounts: 10, 11

        List<Account> newUpdatedListAccount = newUpdatedListAccount();

        client.getAccounts().clear();
        client.getAccounts().addAll(newUpdatedListAccount);

        session.saveOrUpdate(client);
        session.flush();

        Account account12 = session.get(Account.class, 12);
        System.out.println(account12.getClient().getId()); // 101 nothing has changed, must be 100
    }

    private List<Account> newUpdatedListAccount() {
        ArrayList<Account> accounts = new ArrayList<>();
        accounts.add(session.get(Account.class, 12)); // new account from other client
        accounts.add(session.get(Account.class, 10)); // existing account in updated client
        return accounts;
    }
}

但是更新不起作用。并且更新不会显示在 sql 日志中。 如何正确更新?这种情况很常见。

您必须在帐户中设置 'client' 或更好 - 使用 addAccount 方法:

您的客户class

@Entity @Getter @Setter
public class Client {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "id_client")
    private Integer id;

    private String name;
    private int age;

    // Here you say that hibernate shall use the field `client` in account for mapping !!!
    @OneToMany(mappedBy = "client", orphanRemoval = true, cascade = CascadeType.ALL)
    private List<Account> accounts = new ArrayList<>();

    // This make sure that our bidi-relation works well
    public void addAccount(Account account){
        accounts.add(account);
        if( account.getClient() != this ) {
            account.setClient(this);
        }
    }

    // This is convenient method
    public void addAccounts(Collection<Account> accounts){
        for( Account account : accounts ){
            this.addAccount(account);
        }
    }

    // And if you remove an account you have to remove the `client` from the account
    public void removeAccount(int id){
        for( Account account : accounts ){
            if( Objects.equals(account.getId(), id) ){
                accounts.remove(account);
                account.setClient(null);
                break;
            }
        }
    }

    void clearAccounts() {
        for( Account account : accounts ){
            account.setClient(null);
        }
        accounts.clear();
    }

    // We lose control if anybody can set it's own list.
    public void setAccounts(List<Account> accounts){
        throw new UnsupportedOperationException("Do not use this");
    }

    // Same here - We lose control if anybody can change our List
    public List<Account> getAccounts (){
        return Collections.unmodifiableList(accounts);
    }

}

您的帐户Class

@Entity @Getter @Setter
@Table(name = "accounts")
public class Account {

    @Id
    @GeneratedValue
    @Column(name = "id_account")
    private Integer id;

    private int amount;
    private String currency;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "id_client")
    private Client client;

    // This make sure that our bidi-relation works well
    public void setClient(Client client){
        this.client = client;
        if( client != null && ! client.getAccounts().contains(this) ){
            client.addAccount(this);
        }
    }
}

你的测试

public class HibTest {

    @PersistenceContext
    protected EntityManager em;

    protected Session session;

    @Before
    public void dbAllSet() {
        session = em.unwrap(Session.class);
    }

    @Test
    @Commit
    public void mergeCollections() {
        Client client = (Client) session.get(Client.class, 100); // with accounts: 10, 11

        List<Account> newUpdatedListAccount = newUpdatedListAccount();


        /* YOUR CODE
         *
         * You tell hibernate to clear the list with the account. If you save your changes nothing will happen because
         * you have told hibernate that the relation is the `client` field in the Account class (mappedby="client") and 
         * we didn't change the `client` field in the Account class.
         */
         // client.getAccounts().clear();

        /*
         * Same here - you add accounts with a reference to Client with ID 101 and we did not change it.
         */
        // client.getAccounts().addAll(newUpdatedListAccount);


        // do not use the client.getAccounts() list directly
        client.clearAccounts();       
        client.addAccounts(newUpdatedListAccount);

        session.saveOrUpdate(client);
        session.flush();

        Account account12 = (Account) session.get(Account.class, 12);
        System.out.println(account12.getClient().getId()); // 101 nothing has changed, must be 100
    }

    private List<Account> newUpdatedListAccount() {
        ArrayList<Account> accounts = new ArrayList<>();
        accounts.add((Account) session.get(Account.class, 12)); // new account from other client
        accounts.add((Account) session.get(Account.class, 10)); // existing account in updated client
        return accounts;
    }
}