У меня «странное» поведение с кешем второго уровня, когда элементы из коллекции попадают не из кеша, а из базы данных.
Существует три класса для моделирования отношений ManyToMany с атрибутами междупользователь и группа.
Класс Person, от которого наследуются классы User и Group:
@Entity
@DiscriminatorColumn(name = "userType", discriminatorType = DiscriminatorType.INTEGER)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Person implements Serializable {
...
}
@Entity
@DiscriminatorValue("2")
public class User extends Person {
@OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.LAZY)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
protected Set<UserGroup> userGroups = new HashSet<>();
}
@Entity
@DiscriminatorValue("2")
public class Group extends Person {
@OneToMany(mappedBy = "group", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
protected Set<UserGroup> users = new HashSet<>();
}
Класс userGroup, сопоставляющий промежуточную таблицу с дополнительными атрибутами
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@NoArgsConstructor
public class UserGroup implements Serializable, Ientity<PkUserGroup> {
@EmbeddedId
@NotNull
private PkUserGroup id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idUser", referencedColumnName = "id", insertable = false, updatable = false)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idGroup", referencedColumnName = "id", insertable = false, updatable = false)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Group group;
...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserGroup userGroup = (UserGroup) o;
return id.equals(userGroup.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Встраиваемый ключ для идентификатора класса UserGroup
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
@ToString
public class PkUserGroup implements Serializable {
private Long idUser;
private Long idGroup;
}
Все сущности и коллекции настроены для кэширования.
Я сделал тест, в котором извлекаю пользователя из БД изего идентификатор, затем я получаю на коллекции UserGroup и Hibernate сгенерировал запрос из-за конфигурации режима Lazy. Я делал это несколько раз в цикле.
@Test
@Transactional
public void level2QueryCacheSelectTest() {
for (int i = 0; i < 5; i++) {
Optional<User> oUser = this.userRepository.findById(1l);
User user = oUser.get();
logger.info(user.toString());
for (UserGroup ug : user.getUserGroups()) {
logger.info(ug.toString());
}
}
}
Hibernate генерирует два выбора на первой итерации, как и ожидалось, затем использует кэш первого уровня и печатает те же результаты.
пользовательrequest:
select
user0_.id as id2_1_0_,
user0_.name as name7_1_0_,
...
from
users user0_
where
user0_.id=?
and user0_.user_type=1
userGroups, извлеченные с использованием только идентификатора пользователя, элементы коллекции User.userGroups
select
usergroups0_.id_user as id_user2_6_0_,
usergroups0_.id_group as id_group1_6_0_,
...
from
user_group usergroups0_
where
usergroups0_.id_user=?
Вот результат в кеше второго уровня, который я могусм. с Hazelcast mancenter.
![enter image description here](https://i.stack.imgur.com/MEt4G.jpg)
Пользователь является членом 4 групп, поэтому в области oauth2.domain.UserGroup ожидается 4 записи,и 1 коллекция в области oauth2.domain.userGroups.
Теперь при втором выполнении теста все идет не так:)
На первой итерации теста Hibernate делает 4 запросав таблицу UserGroup, используя идентификатор пользователя и идентификатор группы. Таким образом, он может получить коллекцию в кеше (поскольку он уже знает идентификатор группы), но не элементы коллекции, которые должны находиться в области oauth2.domain.UserGroup.
select
usergroup0_.id_group as id_group1_6_0_,
usergroup0_.id_user as id_user2_6_0_,
from
user_group usergroup0_
where
usergroup0_.id_group=?
and usergroup0_.id_user=?
binding parameter [1] as [BIGINT] - [9]
binding parameter [2] as [BIGINT] - [1]
то же самое для 3 других групп пользователей
Теперь в кеше у меня есть 8 элементов группы пользователей
![enter image description here](https://i.stack.imgur.com/3QRM9.jpg)
Я не уверен, почему элементы не найдены в кеше, я реализовал хэш-код и эквиваленты класса UserGroup, используя встроенный идентификатор. Но я не уверен, как Hazelcast делает работу, так что, возможно, есть неправильная конфигурация. Если у вас есть идеи, как я могу решить эту проблему, пожалуйста.
Версии:
Спящий режим: 5.3.12. Окончательный вариант
Hazelcast-hibernate53: 1.3.2
Hazelcast: 3.12.3
Spring boot: 2.0.0.RELEASE
Вот конфигурация Spring-boot для Hazelcast и jpa.
spring:
datasource:
jdbc-url: jdbc:mysql://mysql-server:3306/oauth2
username: ********
password: ********
initialization-mode: never
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
cache:
#required - turn on L2 cache.
use_second_level_cache: true
#optional - turn on query cache.
use_query_cache: true
hazelcast:
use_native_client: true
native_client_address: hazelcast-member:5701
native_client_group: *********
native_client_password: *********
region:
factory_class: com.hazelcast.hibernate.HazelcastCacheRegionFactory
format_sql: true
#optional - generate statistics to check if L2/query cache is actually being used.
generate_statistics: true