In our project we are using hibernate (4.3.11) and hazelcast 3.6.5 for distributed second lvl cache.
Here is sample test that explains problem we are facing:
Code:
@Test
public void cacheTest() {
// Step 1: create account with someValue=111
Account account = account().withName("test hz account").withSomeValue(111).build();
account = restClient.upsertAccount(account);
//Step 2: create user with ref to account
User user = user().withAccount(account).build();
restClient.upsertUser(user);
//Step 3: update account someValue to 222
account.setSomeValue(222);
restClient.upsertAccount(account);
//search users by account id
List<User> users = restClient.searchUsersByAccountId(BRAND, account.getId());
assertThat(users.size(), is(1));
//assertion below fails, user.account.someValue=111
assertThat(users.get(0).getAccount().getSomeValue(), is(222));
}
test is run on application with two nodes. Here is how requests are balanced to different nodes:
node1:
172.20.0.1 - - [12:30:46 +0200] "POST /accounts/BRAND/ HTTP/1.1" 200 321
172.20.0.1 - - [12:30:47 +0200] "POST /accounts/BRAND/ HTTP/1.1" 200 321
node2:
172.20.0.1 - - [12:30:47 +0200] "POST /users/BRAND/ HTTP/1.1" 200 542
172.20.0.1 - - [12:30:48 +0200] "POST /users/BRAND/search?start=0&limit=1 HTTP/1.1" 200 617
I have found that 2nd lvl cache contains two instances of same account. One with old field value 111 and one with new one 222.
First instance is added to cache on node1 on account insert, and second on node2 on user insert (we have account get call on user insert). As I understand these two account instances in cache causes inconsistency.
Any ideas why this could happen and how to debug this issue?
Here is data model:
Code:
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "accounts")
@Cache(usage = READ_WRITE)
public class Account extends BrandIdEntity {
@Column(name = "name")
private String name;
@Column(name = "some_value")
private Integer someValue;
}
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "users")
@Cache(usage = READ_WRITE)
public class User extends BrandIdEntity {
@Column(name = "account_id")
private String accountId;
@ManyToOne(cascade = ALL)
@JoinColumns({
@JoinColumn(name = "brand", nullable = false, insertable = false, updatable = false),
@JoinColumn(name = "account_id", nullable = false, insertable = false, updatable = false) })
private Account account;
}
@EqualsAndHashCode(of = "pk", callSuper = false)
@MappedSuperclass
public abstract class BrandIdEntity extends AuditedEntity<BrandIdPK> {
@EmbeddedId
private final BrandIdPK pk;
public BrandIdEntity() {
super();
pk = new BrandIdPK();
}
public BrandIdEntity(BrandType brand, String id, IdGenType idGenType) {
super();
pk = new BrandIdPK(brand, id, idGenType);
}
}
@Embeddable
@Getter
@Setter
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor
public class BrandIdPK extends BaseObject {
@Column(name = "id")
private String id;
@Column(name = "brand")
private String brand;
public BrandIdPK(BrandType brand, String id, IdGenType idGenType) {
this.brand = brand.name();
if (id != null) {
this.id = id;
}
else {
this.id = next(idGenType);
}
}
}