Итак, я пишу тесты для некоторой логики вокруг некоторых Spring JpaRepositories, которые я создал. Я делаю это, используя встроенную базу данных H2 в памяти. Я вижу странный сценарий, когда мои сопоставленные объекты OneToOne не работают должным образом.
My User
и SSOUser
- это объекты, объединенные с помощью отношения OneToOne, причем объект SSOUser
просто имеет дополнительные метаданные для пользователя, который выполняет вход в наше решение SSO. В приведенном ниже тестовом коде, когда @Transactional
НЕ применяется, все работает. Однако, когда я добавляю @Transactional
, в закомментированной строке произойдет сбой:
@Test
public void test_saveFindUserAndSsoUser() throws Exception {
User user = mockUser();
user = userCarrierRepository.saveAndFlush(user);
SSOUser ssoUser = new SSOUser();
ssoUser.setUser(user);
ssoUser = ssoUserRepository.saveAndFlush(ssoUser);
assertNotNull(user.getId());
assertNotNull(ssoUser.getId());
SSOUser ssoUser2 = ssoUserRepository.findById(ssoUser.getId())
.orElseThrow(() -> new Exception("Could not find SSO User by ID"));
assertThat(ssoUser2, allOf(
hasProperty("id", equalTo(ssoUser.getId())),
hasProperty("user", notNullValue())
));
assertThat(ssoUser2.getUser(), hasProperty("id", equalTo(user.getId())));
final User user2 = userCarrierRepository.findById(user.getId())
.orElseThrow(() -> new Exception("Could not find User by ID"));
assertThat(user2, allOf(
hasProperty("id", equalTo(user.getId())),
hasProperty("ssoUser", notNullValue()) // FAILS HERE BECAUSE ssoUser IS NULL
));
assertThat(user2.getSsoUser(), hasProperty("id", equalTo(ssoUser.getId())));
}
Субъект User, использующий Lombok, имеет вид:
@Data
@Entity
@Table(name="user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Long id;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
private SSOUser ssoUser;
public boolean isSSOUser() {
return ssoUser != null;
}
}
Объект SSOUser, использующий Lombok, имеет вид:
@Data
@Entity
@Table(name = "sso_user")
public class SSOUser implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
А вот оператор SQL DDL для добавления соответствующей таблицы в нашу базу данных:
CREATE TABLE sso_user (
id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES public.user (id)
);
Итак, вернемся к исходному коду, объект User
сохраняется с полем ssoUser
как ноль. Однако объект SSOUser
сохраняется с действительным объектом User
. Я ожидаю, что таблица sso_user
будет содержать соответствующее значение в столбце user_id
.
Из-за того, что данные в таблице sso_user
верны, я бы ожидал, что userCarrierRepository.findById(...)
вернет User
с включенным ssoUser
, как я ожидал, sslUserRepository.findById(...)
вернет SSOUser
с включенным user
в комплекте.
Однако это не то, что происходит. Только SSOUser возвращается с присоединенной сущностью, пользователь - нет.
Опять же, это происходит только в том случае, если я добавлю @Transactional к приведенному здесь коду. Без этой аннотации все работает так, как я ожидал.
Я надеюсь, что кто-то может объяснить это для меня. Большое вам спасибо.