В моем проекте Spring Boot у меня есть следующее поле отношения @ManyToOne
, ссылающееся на тот же @Entity
, что и у родителя.
@Entity
@Table(name="workspace")
public class Workspace extends AbstractEntity {
@NaturalId
@Column(nullable=false, unique=true)
private String code;
@ManyToOne(fetch=FetchType.LAZY, optional=true)
@LazyToOne(LazyToOneOption.NO_PROXY)
@JoinColumn(name="default_workspace", referencedColumnName="code")
private Workspace parent;
private String name;
private String shortName;
}
Режим отложенной загрузки работает, как и ожидалось.Однако каждый раз, когда я вызываю @Repository.save()
для этого @Entity
, поле default_workspace
будет установлено на NULL
.
ОБНОВЛЕНИЕ: Включите некоторые конфигурации, запрошенные в комментарии
Чтобы включить отложенную загрузку с помощью Hibernate, я просто включил плагин hibernate-enhance-maven-plugin
в свой pom.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ft</groupId>
<artifactId>com.ft.admin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.ft</groupId>
<artifactId>com.ft.common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${hibernate.version}</version>
<executions>
<execution>
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Для выполнения процедур CRUD на моем Workspace
я использую следующее @Repository
.
@Repository
public interface WorkspaceRepo extends CrudRepository<Workspace, Integer> {
Optional<Workspace> findByCode(String workspaceCode);
}
По умолчанию в Spring Boot граница транзакции остается в пределахкаждый звонок @Repository
.Следовательно, чтобы иметь возможность доступа к моему загруженному ленивому полю без использования LazyInitializationException: no Session
, я добавил @Transactional
к своему @Service
, чтобы расширить границу транзакции следующим образом.
@Service
@Transactional
public class AppAdminService {
@Autowired
private WorkspaceRepo workspaceRepo;
public void updatePsyWorkspace() {
Workspace childWorkspace = workspaceRepo.findByCode("PSY").get();
childWorkspace.setShortName("PSY SHORT");
workspaceRepo.save(childWorkspace);
}
}
Когда я выполняю WorkspaceRepo.findByCode(code)
, я возвращаю дочернюю запись с допустимым ненулевым parent
.Однако всякий раз, когда я выполняю WorkspaceRepo.save(childWorkspace)
, parent
устанавливается в NULL
, а столбец default_workspace
становится NULL
в базе данных, даже если я только обновил поле ShortName
, как показано в примере выше.
ОБНОВЛЕНИЕ: временное решение
В качестве временного решения мне пришлось включить простое поле String
для столбца default_workspace
и исключить parent
поле из операций Hibernate следующим образом.
@Entity
@Table(name="workspace")
@Getter @Setter
public class Workspace extends AbstractEntity {
...
@ManyToOne(fetch=FetchType.LAZY, optional=true)
@LazyToOne(LazyToOneOption.NO_PROXY)
@JoinColumn(
name="default_workspace",
referencedColumnName="code",
insertable=false,
updatable=false)
private Workspace parent;
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
@Column(name="default_workspace")
private String defaultWorkspace;
public void setParent(Workspace parent) {
this.parent = parent;
this.defaultWorkspace = (parent != null) ? parent.getCode() : null;
}
...
}
Используя AccessLevel.NONE
, я полностью скрываю это поле от внешнего мира.Любые вызовы для обновления поля parent
дополнительно обновят поле defaultWorkspace
.Даже если это решение работает, оно мне не подходит.
Буду очень признателен, если вы покажете мне лучший способ достичь той же цели.