I can not update the list of dependent objects. I have an API which should update the list of accounts with clients. One Client - many Accounts.
I configured @OneToMany as indicated for proper update:
@OneToMany(mappedBy = "client", orphanRemoval = true, cascade = CascadeType.ALL)
Entities:
@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;
}
What I want to do on the data presented. I already have a client (id = 100) with two accounts (id = 10, 11).
I want to update so that the client has a different list of accounts id: 10, 12.
My test with test data.
<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>
Test:
@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;
}
}
But the update does not work. And the update is not displayed in the sql log. How to update correctly? This is a very frequent case.
You have to set the 'client' in the account or much better - use an addAccount
method:
Your Client 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);
}
}
Your Account 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);
}
}
}
Your Test
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;
}
}