使用 Hibernate 和 JPA 触发意外查询
Unexpected Query Getting Fired With Hibernate And JPA
我已经编写了从数据库获取数据的代码,但它也触发了意外查询:
@SuppressWarnings("unchecked")
@Transactional
public List<Job> getAppliedPositionsById(Long userId) {
// String currentDate = SQLDateFormator.getCurrentDateInSqlFormat();
String strQuery = "from Job x left join x.applications a where a.applicant.id = :userId";
Query query = entityManager.createQuery(strQuery);
query.setParameter("userId", userId);
return query.getResultList();
}
并且在 return query.getResultList();
上它触发了两个查询。由于第二个查询,我遇到了异常。
两个查询
休眠:
select
job0_.id as id1_5_0_,
applicatio1_.id as id1_1_1_,
job0_.close_date as close_da2_5_0_,
job0_.committee_chair_id as committe6_5_0_,
job0_.description as descript3_5_0_,
job0_.publish_date as publish_4_5_0_,
job0_.title as title5_5_0_,
applicatio1_.applicant_id as applican6_1_1_,
applicatio1_.current_job_institution as current_2_1_1_,
applicatio1_.current_job_title as current_3_1_1_,
applicatio1_.current_job_year as current_4_1_1_,
applicatio1_.cv_id as cv_id7_1_1_,
applicatio1_.job_id as job_id8_1_1_,
applicatio1_.research_statement_id as research9_1_1_,
applicatio1_.submit_date as submit_d5_1_1_,
applicatio1_.teaching_statement_id as teachin10_1_1_
from
jobs job0_
left outer join
applications applicatio1_
on job0_.id=applicatio1_.job_id
where
applicatio1_.applicant_id=?
休眠:
select
user0_.id as id1_8_0_,
user0_.address as address2_8_0_,
user0_.email as email3_8_0_,
user0_.first_name as first_na4_8_0_,
user0_.last_name as last_nam5_8_0_,
user0_.password as password6_8_0_,
user0_.phone as phone7_8_0_
from
users user0_
where
user0_.id=?
关于 Users
table 的第二个查询完全没有必要。
Job
实体
@Id
@GeneratedValue
private Long id;
private String title;
private String description;
@Column(name = "publish_date")
private Date publishDate;
@Column(name = "close_date")
private Date closeDate;
@ManyToOne
@JoinColumn(name = "committee_chair_id")
private User committeeChair;
@ManyToMany
@JoinTable(name = "job_committee_members",
joinColumns = @JoinColumn(name = "job_id") ,
inverseJoinColumns = @JoinColumn(name = "user_id") )
@OrderBy("lastName asc")
private List<User> committeeMembers;
@OneToMany(mappedBy = "job")
@OrderBy("date asc")
private List<Application> applications;
}
Application
实体:
@Id
@GeneratedValue
private Long id;
@ManyToOne
private Job job;
@ManyToOne
private User applicant;
@Column(name = "submit_date")
private Date submitDate;
@Column(name = "current_job_title")
private String currentJobTitle;
@Column(name = "current_job_institution")
private String currentJobInstitution;
@Column(name = "current_job_year")
private Integer currentJobYear;
@ElementCollection
@CollectionTable(name = "application_degrees",
joinColumns = @JoinColumn(name = "application_id") )
@OrderBy("year desc")
private List<Degree> degrees;
@OneToOne
private File cv;
@OneToOne
@JoinColumn(name = "research_statement_id")
private File researchStatement;
@OneToOne
@JoinColumn(name = "teaching_statement_id")
private File teachingStatement;
@OneToMany(mappedBy = "application",
cascade = { CascadeType.MERGE, CascadeType.PERSIST })
@OrderColumn(name = "round_index")
private List<Round> rounds;
}
User
实体:
@Id
@GeneratedValue
private Long id;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
private String address;
private String phone;
@OneToMany(mappedBy = "applicant")
@OrderBy("id desc")
private List<Application> applications;
}
根据 JPA 2.0 规范,默认值如下:
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
您已在申请中class
@ManyToOne
private User applicant;
如果您将其切换为 LAZY
@ManyToOne(fetch = FetchType.LAZY)
它应该按照您想要的方式工作。
正如 luksch 指出的那样,您的模型定义了与 User 的 @ManyToOne
关系,默认情况下 Eagerly 每次加载 Job 实例(或 Application 实例模型)。
但是,切换它 FetchType.LAZY
现在可能会产生预期的结果。使用 oneToOne 或 manyToOne hibernate 将不得不进行额外的查询,即使它是 LAZY。只有当您指定关系的 optional=false
属性以及 FetchType.LAZY
时,它才会自动将 Proxy 对象设置为用户属性的值。这是因为 Hibernate 在检查数据库之前无法知道用户属性是否存在或为空。
根据您的模型,一种更合适的解决方案是更新您的查询以在一个查询中获取用户对象,如下所示:
String strQuery = "from Job x left join fetch x.committeeChair u left join x.applications a where a.applicant.id = :userId"
重要的部分是 left join fetch x.committeeChair u 它告诉休眠添加一个额外的连接并获取相关对象。
这修复了使用 JPQL 获取作业实例时的行为。
如果您尝试通过 EntityManager.find
方法通过其 id 加载单个 Job 实例。它仍然会为 User committeeChair 生成一个额外的查询。您可以使用 Hibernate specific(not JPA standard yet) fetch modes 进一步优化您的加载策略
请注意,获取模式可能会禁用延迟加载可能不需要的内容。
我建议首先决定需要加载哪些数据,哪些数据始终存在,并基于此优化您的查询。通常,一个额外的查询比在一个查询中加载整个实体图要好。
祝你好运
我已经编写了从数据库获取数据的代码,但它也触发了意外查询:
@SuppressWarnings("unchecked")
@Transactional
public List<Job> getAppliedPositionsById(Long userId) {
// String currentDate = SQLDateFormator.getCurrentDateInSqlFormat();
String strQuery = "from Job x left join x.applications a where a.applicant.id = :userId";
Query query = entityManager.createQuery(strQuery);
query.setParameter("userId", userId);
return query.getResultList();
}
并且在 return query.getResultList();
上它触发了两个查询。由于第二个查询,我遇到了异常。
两个查询
休眠:
select
job0_.id as id1_5_0_,
applicatio1_.id as id1_1_1_,
job0_.close_date as close_da2_5_0_,
job0_.committee_chair_id as committe6_5_0_,
job0_.description as descript3_5_0_,
job0_.publish_date as publish_4_5_0_,
job0_.title as title5_5_0_,
applicatio1_.applicant_id as applican6_1_1_,
applicatio1_.current_job_institution as current_2_1_1_,
applicatio1_.current_job_title as current_3_1_1_,
applicatio1_.current_job_year as current_4_1_1_,
applicatio1_.cv_id as cv_id7_1_1_,
applicatio1_.job_id as job_id8_1_1_,
applicatio1_.research_statement_id as research9_1_1_,
applicatio1_.submit_date as submit_d5_1_1_,
applicatio1_.teaching_statement_id as teachin10_1_1_
from
jobs job0_
left outer join
applications applicatio1_
on job0_.id=applicatio1_.job_id
where
applicatio1_.applicant_id=?
休眠:
select
user0_.id as id1_8_0_,
user0_.address as address2_8_0_,
user0_.email as email3_8_0_,
user0_.first_name as first_na4_8_0_,
user0_.last_name as last_nam5_8_0_,
user0_.password as password6_8_0_,
user0_.phone as phone7_8_0_
from
users user0_
where
user0_.id=?
关于 Users
table 的第二个查询完全没有必要。
Job
实体
@Id
@GeneratedValue
private Long id;
private String title;
private String description;
@Column(name = "publish_date")
private Date publishDate;
@Column(name = "close_date")
private Date closeDate;
@ManyToOne
@JoinColumn(name = "committee_chair_id")
private User committeeChair;
@ManyToMany
@JoinTable(name = "job_committee_members",
joinColumns = @JoinColumn(name = "job_id") ,
inverseJoinColumns = @JoinColumn(name = "user_id") )
@OrderBy("lastName asc")
private List<User> committeeMembers;
@OneToMany(mappedBy = "job")
@OrderBy("date asc")
private List<Application> applications;
}
Application
实体:
@Id
@GeneratedValue
private Long id;
@ManyToOne
private Job job;
@ManyToOne
private User applicant;
@Column(name = "submit_date")
private Date submitDate;
@Column(name = "current_job_title")
private String currentJobTitle;
@Column(name = "current_job_institution")
private String currentJobInstitution;
@Column(name = "current_job_year")
private Integer currentJobYear;
@ElementCollection
@CollectionTable(name = "application_degrees",
joinColumns = @JoinColumn(name = "application_id") )
@OrderBy("year desc")
private List<Degree> degrees;
@OneToOne
private File cv;
@OneToOne
@JoinColumn(name = "research_statement_id")
private File researchStatement;
@OneToOne
@JoinColumn(name = "teaching_statement_id")
private File teachingStatement;
@OneToMany(mappedBy = "application",
cascade = { CascadeType.MERGE, CascadeType.PERSIST })
@OrderColumn(name = "round_index")
private List<Round> rounds;
}
User
实体:
@Id
@GeneratedValue
private Long id;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
private String address;
private String phone;
@OneToMany(mappedBy = "applicant")
@OrderBy("id desc")
private List<Application> applications;
}
根据 JPA 2.0 规范,默认值如下:
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
您已在申请中class
@ManyToOne
private User applicant;
如果您将其切换为 LAZY
@ManyToOne(fetch = FetchType.LAZY)
它应该按照您想要的方式工作。
正如 luksch 指出的那样,您的模型定义了与 User 的 @ManyToOne
关系,默认情况下 Eagerly 每次加载 Job 实例(或 Application 实例模型)。
但是,切换它 FetchType.LAZY
现在可能会产生预期的结果。使用 oneToOne 或 manyToOne hibernate 将不得不进行额外的查询,即使它是 LAZY。只有当您指定关系的 optional=false
属性以及 FetchType.LAZY
时,它才会自动将 Proxy 对象设置为用户属性的值。这是因为 Hibernate 在检查数据库之前无法知道用户属性是否存在或为空。
根据您的模型,一种更合适的解决方案是更新您的查询以在一个查询中获取用户对象,如下所示:
String strQuery = "from Job x left join fetch x.committeeChair u left join x.applications a where a.applicant.id = :userId"
重要的部分是 left join fetch x.committeeChair u 它告诉休眠添加一个额外的连接并获取相关对象。
这修复了使用 JPQL 获取作业实例时的行为。
如果您尝试通过 EntityManager.find
方法通过其 id 加载单个 Job 实例。它仍然会为 User committeeChair 生成一个额外的查询。您可以使用 Hibernate specific(not JPA standard yet) fetch modes 进一步优化您的加载策略
请注意,获取模式可能会禁用延迟加载可能不需要的内容。
我建议首先决定需要加载哪些数据,哪些数据始终存在,并基于此优化您的查询。通常,一个额外的查询比在一个查询中加载整个实体图要好。
祝你好运