@Ilya Dyoshin 在很多方面都是对的,但也有一些陷阱。
首先,@OneToOne 是这样一种关联类型,在双向映射的情况下不能轻易获取惰性。这是因为 Hibernate 没有其他方法可以知道是否将 null 或 Proxy 分配给此变量。
在这种情况下,至少有 3 个选项:
说到单向关联,它也不是第一眼看上去那么容易。正如@Ilya Dyoshin 之前所写,EntityGraph 是一件非常好的事情,但您应该记住,有两个提示定义了图形选择行为:"javax.persistence.loadgraph" 和 “javax.persistence.fetchgraph”。 In theory 必须按照EntityGraphType 工作:
- FETCH: 当使用javax.persistence.fetchgraph属性指定实体图时,实体图的属性节点指定的属性被视为FetchType.EAGER,未指定的属性被视为FetchType.LAZY
- LOAD: 使用javax.persistence.loadgraph属性指定实体图时,实体图的属性节点指定的属性按FetchType.EAGER处理,未指定的属性按FetchType.EAGER处理到他们指定或默认的 FetchType
但在实践中,它几乎总是依赖于一些微妙之处,包括 JPA 提供程序(Hibernate、EclipseLink 等)及其优化器
让我们看一个小例子
数据库
create table debt(
id bigint identity(1,1),
contract nvarchar(128),
constraint PK_DEBT primary key(id)
)
create table debt_detail(
r_debt_id int not null unique,
dict_value_1 int,
number_value_1 money,
constraint PK_DEBT_DETAIL primary key(r_debt_id),
constraint FK_DEBT_DETAIL_DEBT_ID foreign key (r_debt_id)references debt(id)
)
insert into debt(contract)
values(N'aaa',N'bbb',N'ccc')
insert into debt_detail(r_debt_id, dict_value_1, number_value_1)
select id, 1, 2.2
from debt
where id in (1, 2, 3)
实体
@Table(name = "debt")
@Entity
@NamedEntityGraphs({
@NamedEntityGraph(name = "testDebt", attributeNodes = {
@NamedAttributeNode(value = "id"),
@NamedAttributeNode(value = "contract")
})
})
public class Debt {
@Id
@Basic(optional = false)
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ID")
private Long id;
@Column(name = "contract")
private String contract;
@OneToOne()
@JoinColumn(name = "ID", referencedColumnName = "R_DEBT_ID", insertable = false, updatable = false, nullable = false)
private DebtDetail debtDetail;
************
}
@Table(name = "debt_detail")
@Entity
public class DebtDetail {
@Id
@Column(name = "R_DEBT_ID")
@Basic(optional = false)
private Long refDebtId;
@Column(name = "dict_value_1")
private String dictValue1;
@Column(name = "number_value_1")
private String numberValue1;
******************
}
回购
@Repository
public interface DebtRepository extends CrudRepository<Debt, Long> {
@Override
Debt findOne(Long id);
@EntityGraph(value = "testDebt", type = EntityGraphType.LOAD)
Debt findById(Long id);
@EntityGraph(value = "testDebt", type = EntityGraphType.FETCH)
Debt findDebtById(Long id);
}
经理
@Component
public class DebtManager {
private static final Logger logger = LoggerFactory.getLogger(DebtManager.class);
@Autowired
private DebtRepository debtRepository;
public void debt() {
logger.info("findOne start");
Debt debt = debtRepository.findOne(1L);
logger.info("findOne stop. {}", debt.getDebtDetail().getClass());
//logger.info("findOne stop. {}", debt.getDebtDetail());
logger.info("load start");
Debt debtLoad = debtRepository.findById(2L);
logger.info("load stop. {}", debtLoad.getDebtDetail().getClass());
//logger.info("load stop. {}", debtLoad.getDebtDetail());
logger.info("fetch start");
Debt debtFetch = debtRepository.findDebtById(3L);
logger.info("fetch stop. {}", debtFetch.getDebtDetail().getClass());
//logger.info("fetch stop. {}", debtFetch.getDebtDetail());
}
}
现在让我们调用debtManager.debt() 并查看日志:
debtRepository.findOne(1L) 会产生这样的结果:
select
debt0_.id as id1_0_0_,
debt0_.contract as contract2_0_0_,
debtdetail1_.r_debt_id as r_debt_i1_4_1_,
debtdetail1_.dict_value_1 as dict_val2_4_1_,
debtdetail1_.number_value_1 as number_v3_4_1_
from
debt debt0_
left outer join
debt_detail debtdetail1_
on debt0_.id=debtdetail1_.r_debt_id
where
debt0_.id=?
...INFO ... - findOne 停止。类 ....DebtDetail
和其他查询:
select
debt0_.id as id1_0_,
debt0_.contract as contract2_0_
from
debt debt0_
where
debt0_.id=?
select
debtdetail0_.r_debt_id as r_debt_i1_4_0_,
debtdetail0_.dict_value_1 as dict_val2_4_0_,
debtdetail0_.number_value_1 as number_v3_4_0_
from
debt_detail debtdetail0_
where
debtdetail0_.r_debt_id=?
...INFO ... - 加载停止。类 ....DebtDetail
...INFO ... - 获取停止。类 ....DebtDetail
所有请求都包含对debt_detail 的查询。它发生的原因是 JPA 中 @OneToOne 的默认行为是 FetchType.EAGER 并且映射没有强制标志(可选 = false)。 “图形化”和“非图形化”查询之间的差异是由于最后一个没有强定义,Hibernate 获取所有连接到Debt 实体,而“图形化”查询总是获取 certan 图中的所有实体,但在我们的例子中@NamedEntityGraph 注释不包含任何实体。
更重要的一件事 - 在所有情况下,获取的类型都是 DebtDetail 即实体类
现在让我们在Debt 类的@OneToOne 定义中添加一些内容:
@OneToOne(fetch = FetchType.LAZY, optional = false)
日志已更改 - 对于所有情况,结果相同:
select
debt0_.id as id1_0_,
debt0_.contract as contract2_0_
from
debt debt0_
where
debt0_.id=?
这是因为我们直接定义了LAZY 策略。但这不是唯一的变化。 DebtDetail 类日志记录必须如下所示:
...INFO ... - 加载停止。类 .... DebtDetail_$$_jvst2bb_0
这意味着连接的实体已被代理,并且父实体(债务)具有对代理对象的引用,该代理对象不包含除主键之外的具体实体的任何数据。这就是为什么对getDebtDetail().getClass() 的处理不会导致DebtDetail 获取 - 我们读取的是代理类属性,而不是具体的实体类。
现在让我们添加 DebtDetail 直读:使用 debt.getDebtDetail() 取消注释代码日志就足够了。
日志已再次更改 - 对于所有情况,结果相同:
select
debt0_.id as id1_0_,
debt0_.contract as contract2_0_
from
debt debt0_
where
debt0_.id=?
...INFO ... - load stop. class ....DebtDetail_$$_jvstadd_0
select
debtdetail0_.r_debt_id as r_debt_i1_4_0_,
debtdetail0_.dict_value_1 as dict_val2_4_0_,
debtdetail0_.number_value_1 as number_v3_4_0_
from
debt_detail debtdetail0_
where
debtdetail0_.r_debt_id=?
正如您现在所看到的,链条是:读取父级 -> 记录子级的类类型(代理)-> 延迟读取子级,其中代理对象将“未代理”
如果我们将传播策略更改为EAGER,那么这两个实体都将在父实体读取期间被读取。
总结 EntityGraphes 的使用情况,花时间调查您的 JPA 提供程序行为及其根据注释定义的变化。
Here is 又一篇关于实体图的好文章
说到Projections:
没有必要创建一个新类——一个接口就足够了:
public interface DebtProjection {
Long getId();
String getContract();
}
@Repository
public interface DebtRepository extends CrudRepository<Debt, Long> {
DebtProjection getById(Long id);
}
它的明显优势是独立于任何关系 - 它只会返回已定义的属性