@EntityGraph/JOIN FETCH 不支持字节码增强

@EntityGraph/JOIN FETCH are not working with Bytecode Enanhcement

我正在使用字节码增强通过基本注解延迟加载用户实体的头像属性;我还想延迟加载与成员实体(扩展用户)相关的身份证。 到目前为止一切正常,我的代码如下:

用户class:

@Data
@Entity
@Table(name = "users")
@Inheritance(strategy = InheritanceType.JOINED)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @NotEmpty
    private String name;

    @NotEmpty
    private String surname;

    @Lob
    @NotNull
    @Basic(fetch = FetchType.LAZY)
    private byte[] avatar;

}

会员class:

@Data
@Entity
@Table(name = "members")
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Member extends User {

    @NotNull
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @LazyToOne(value = LazyToOneOption.NO_PROXY)
    private IdentityCard identityCard;

}

身份证class:

@Data
@Entity
@Table(name = "identityCards")
public class IdentityCard {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Lob
    @NotNull
    private byte[] backImage;

    @Lob
    @NotNull
    private byte[] frontImage;

}

pom.xml

<plugins>
    <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>

application.yml:

...
spring:
     jpa:
          open-in-view: false
...

现在,在我的控制器中,我想通过存储库中的 EntityGraph 注释获取会员的头像和身份证

控制器:

@Controller
@RequestMapping("members")
public class MemberController {

    @Autowired
    MemberService memberService;

    @GetMapping("/{id}")
    public String get(@PathVariable String id, Model model) {
        Optional<Member> member = memberService.findFullMemberById(Long.valueOf(id));

        // Using model to pass member and let thymeleaf process it
        return "members/form";
    }

}

会员资料库:

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {

    ...

    @EntityGraph(attributePaths = {"avatar", "identityCard"})
    Optional<Member> findFullMemberById(Long id);

}

但是我得到的Member 对象总是将avatar 和identityCard 属性都设置为null。 我也尝试使用 Query 注释和 LEFT JOIN FETCH 得到相同的结果。

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {

    ...

    @Query("select m from Member m LEFT JOIN FETCH m.identityCard identityCard where m.id=:id")
    Optional<Member> findFullMemberById(Long id);

}

感谢您的帮助。谢谢。

从 Hibernate 5.4.26 开始,有 2 个明显的问题。

1。懒人基本属性

您正在尝试使用 @EntityGraph 急切地获取惰性基本属性,这在 Hibernate 5 中不受支持,但是 Hibernate 6 有一个功能请求允许 EntityGraphs 控制基本实体状态的加载(不仅是协会):https://hibernate.atlassian.net/browse/HHH-9270

我不知道有没有解决方法,我认为它不存在。 enableLazyInitialization 的替代方法是使用 子实体 或更好的 投影 ,如 https://vladmihalcea.com/the-best-way-to-lazy-load-entity-attributes-using-jpa-and-hibernate[= 中所述44=]

2。 ToOne 协会

确实,enableLazyInitialization 允许延迟加载 @OneToOne@LazyToOne(NO_PROXY),但是 JOIN FETCH 变得更难:您必须在会话打开时初始化关联才能在会话外使用它,它仍然会产生 N+1 SELECT!

初始加载计划中排除了所有惰性单数属性(包括惰性 ToOne 关联的连接列)。

没有join fetch

当通过 id 查找成员时,Hibernate 将生成:

  1. 先SELECT获得急切属性:id,name,surname
  2. 需要的时候,一个N+1SELECT去读取对应隐含的惰性属性@LazyGroupavatar,identity_card_id
  3. a N+1 SELECT 获取关联的 identityCard

加入fetch

当连接和获取 identityCard 时,Hibernate 生成正确的 SQL JOIN 来获取关联的 identityCard,但它错过了 member.identity_card_id 连接列,导致 LazyInitializationException(我没有一个 null,如您的问题中所述,但完全相同)。

  1. 首先SELECT获取eager属性id,name,surname和加入的实体属性:identityCard.id,identityCard.frontImage,identityCard.backImage
  2. 需要的时候,一个N+1SELECT读取对应隐式@LazyGroup的惰性属性:avatar,identity_card_id(和没有join fetch一样! )

存在与此错误(或功能...)相关的未解决问题:

启用这个新选项:

hibernate.bytecode.allow_enhancement_as_proxy=true

您将获得与未启用字节码增强和 enableLazyInitialization 相同的行为,并且 JOIN FETCH 将按预期工作,但您仍然能够延迟加载基本属性和 @OneToOne

这样,您甚至不需要在所有惰性 ToOne.

上添加 @LazyToOne(NO_PROXY)@OneToOne 除外)和 @LazyGroup

请参阅我对此问题的评论。 https://hibernate.atlassian.net/browse/HHH-13134?focusedCommentId=107604

有关此新功能的更多信息,请查看 https://in.relation.to/2019/07/30/bytecode-proxy/


另一个建议

当不使用新的 hibernate.bytecode.allow_enhancement_as_proxy@LazyGroup 时,所有惰性属性都隐含地属于同一组。因此,当延迟加载 identityCard 时,Hibernate 也会加载 avatar,这可能不是您想要的。

所以你应该把lob和身份证用@LazyGroup("lob")@LazyGroup("identityCard")分开或者用hibernate.bytecode.allow_enhancement_as_proxy.