为什么 Hibernate 不使用 @ManyToMany 和 @JoinTable 注释对这个 MANY TO MANY 关联 table 执行 JOIN?
Why Hibernate is not performing the JOIN on this MANY TO MANY association table using @ManyToMany and @JoinTable annotation?
我正在使用 Spring Data JPA 和 Hibernate 映射开发 Spring 启动应用程序,我遇到了以下问题。
我有这个 网络 table:
CREATE TABLE IF NOT EXISTS public.network
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
name character varying(50) COLLATE pg_catalog."default" NOT NULL,
description text COLLATE pg_catalog."default",
CONSTRAINT network_pkey PRIMARY KEY (id)
)
和这个链 table:
CREATE TABLE IF NOT EXISTS public.chain
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
name character varying(50) COLLATE pg_catalog."default" NOT NULL,
fk_chain_type bigint NOT NULL,
description text COLLATE pg_catalog."default",
CONSTRAINT chain_pkey PRIMARY KEY (id),
CONSTRAINT "chain_to_chain_type_FK" FOREIGN KEY (fk_chain_type)
REFERENCES public.chain_type (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID
)
这 2 个 table 通过 多对多 关系相互关联,由 network_chain[= 实现88=] table:
CREATE TABLE IF NOT EXISTS public.network_chain
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
fk_network_id bigint NOT NULL,
fk_chain_id bigint NOT NULL,
CONSTRAINT network_chain_pkey PRIMARY KEY (id),
CONSTRAINT chain_id_fk FOREIGN KEY (fk_chain_id)
REFERENCES public.chain (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID,
CONSTRAINT network_id_fk FOREIGN KEY (fk_network_id)
REFERENCES public.network (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID
)
基本上 fk_network_id 字段表示进入 network table 的特定记录的 ID,而fk_chain_id表示进入链的特定记录的id table.
我将这些数据库 table 映射到以下 Hibernate 实体 classes,首先我创建了这个 Network class 映射网络 table:
@Entity
@Table(name = "network")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Network implements Serializable {
private static final long serialVersionUID = -5341425320975462596L;
@Id
@Column(name = "id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column(name = "name")
private String name;
@Column(name = "description")
private String description;
@ManyToMany(cascade = { CascadeType.MERGE })
@JoinTable(
name = "network_chain",
joinColumns = { @JoinColumn(name = "fk_network_id") },
inverseJoinColumns = { @JoinColumn(name = "fk_chain_id") }
)
Set<Chain> chainList;
}
如您所见,它包含 @ManyToMany 注释,使用 @JoinTable 注释来加入我的 network table 与我的 chain table(由 Chain 实体 class 映射) 使用前面的network_chain table(实现多对多关系)。
所以在这个 @JoinTable 注释中我指定:
- 合并 table 实现多对多关系:network_chain.
- 这个table上的两个FK是fk_network_id和fk_chain_id.
然后我将这个 Spring 数据 JPA 存储库 class 命名为 NetworkRepository:
public interface NetworkRepository extends JpaRepository<Network, Integer> {
/**
* Retrieve a Network object by its ID
* @param id of the network
* @return the retrieve Network object
*/
Network findById(String id);
/**
* Retrieve a Network object by its name
* @param name of the network
* @return a Network object
*/
Network findByName(String name);
/**
* Retrieve the list of all the possible networks
* @return a List<Network> object: the list of the all the networks
*/
List<Network> findAll();
}
最后我创建了一个 JUnit 测试 class 包含一个方法来测试以前的存储库 findAll() 方法,这个:
@SpringBootTest()
@ContextConfiguration(classes = GetUserWsApplication.class)
@TestMethodOrder(OrderAnnotation.class)
public class NetworkTest {
@Autowired
private NetworkRepository networkRepository;
/**
* Retrieve the networks list
*/
@Test
@Order(1)
public void findAllTest() {
List<Network> networksList = this.networkRepository.findAll();
assertTrue(networksList.size() == 5, "It retrieved 5 networks");
Set<Chain> networkChain = networksList.get(0).getChainList();
assertTrue(networkChain != null, "The network chains list are not null");
}
}
问题是 findAll() 方法执行这个 SQL 语句(我可以在我的堆栈跟踪中看到它):
Hibernate:
select
network0_.id as id1_6_,
network0_.description as descript2_6_,
network0_.name as name3_6_
from
network network0_
并检索预期的 List 对象,但正如您在以下打印屏幕中看到的,我的 chainList 字段给出错误:
它似乎没有从我的 MANY TO MANY table 中检索到 chainList(事实上,之前的 Hibernate 语句似乎没有对 network_chain 然后 chain tables).
我也尝试通过这一行直接访问这个字段(我的想法是,当显式执行对该字段的访问时,可能是 Hibernate 执行了这个连接:
Set<Chain> networkChain = networksList.get(0).getChainList();
当我尝试检索该字段时,似乎出现了 com.sun.jdi.InvocationException 异常。基本上似乎从未执行 MANY TO MANY table 和 chain table 之间的 JOINS。
为什么?我的代码有什么问题?我错过了什么?我该如何尝试修复它?
发生这种情况是因为您在 table (network_chain
) 中有一个额外的列 (id
),它负责 many-to-many
关系。
@JoinTable
注解将只支持定义2个字段,通常是id
中的2个table。 2 个 id
一起,将是 primary key
用于连接 table,也称为 composite primary key
。
在您的情况下,您必须创建一个实体来表示 table network_chain
。
@Entity
@Table(name = "network_chain")
public class NetworkChain {
@Id
@Column(name = "id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@ManyToOne
private Network network;
@ManyToOne
private Chain chain;
}
public class Network implements Serializable {
//somewhere in the code
@OneToMany
List<NetworkChain> networkChains;
}
public class Chain implements Serializable {
//somewhere in the code
@OneToMany
List<NetworkChain> networkChains;
}
现在,实体 NetworkChain
将额外的列 id
作为主键,hibernate 将进行适当的连接以获取数据。
题外话 但不确定为什么 findAll()
方法有一个 javadoc,它通常是 JpaRepository
和奇怪的 [=24] 的一部分=] 还有..
我正在使用 Spring Data JPA 和 Hibernate 映射开发 Spring 启动应用程序,我遇到了以下问题。
我有这个 网络 table:
CREATE TABLE IF NOT EXISTS public.network
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
name character varying(50) COLLATE pg_catalog."default" NOT NULL,
description text COLLATE pg_catalog."default",
CONSTRAINT network_pkey PRIMARY KEY (id)
)
和这个链 table:
CREATE TABLE IF NOT EXISTS public.chain
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
name character varying(50) COLLATE pg_catalog."default" NOT NULL,
fk_chain_type bigint NOT NULL,
description text COLLATE pg_catalog."default",
CONSTRAINT chain_pkey PRIMARY KEY (id),
CONSTRAINT "chain_to_chain_type_FK" FOREIGN KEY (fk_chain_type)
REFERENCES public.chain_type (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID
)
这 2 个 table 通过 多对多 关系相互关联,由 network_chain[= 实现88=] table:
CREATE TABLE IF NOT EXISTS public.network_chain
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
fk_network_id bigint NOT NULL,
fk_chain_id bigint NOT NULL,
CONSTRAINT network_chain_pkey PRIMARY KEY (id),
CONSTRAINT chain_id_fk FOREIGN KEY (fk_chain_id)
REFERENCES public.chain (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID,
CONSTRAINT network_id_fk FOREIGN KEY (fk_network_id)
REFERENCES public.network (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID
)
基本上 fk_network_id 字段表示进入 network table 的特定记录的 ID,而fk_chain_id表示进入链的特定记录的id table.
我将这些数据库 table 映射到以下 Hibernate 实体 classes,首先我创建了这个 Network class 映射网络 table:
@Entity
@Table(name = "network")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Network implements Serializable {
private static final long serialVersionUID = -5341425320975462596L;
@Id
@Column(name = "id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column(name = "name")
private String name;
@Column(name = "description")
private String description;
@ManyToMany(cascade = { CascadeType.MERGE })
@JoinTable(
name = "network_chain",
joinColumns = { @JoinColumn(name = "fk_network_id") },
inverseJoinColumns = { @JoinColumn(name = "fk_chain_id") }
)
Set<Chain> chainList;
}
如您所见,它包含 @ManyToMany 注释,使用 @JoinTable 注释来加入我的 network table 与我的 chain table(由 Chain 实体 class 映射) 使用前面的network_chain table(实现多对多关系)。
所以在这个 @JoinTable 注释中我指定:
- 合并 table 实现多对多关系:network_chain.
- 这个table上的两个FK是fk_network_id和fk_chain_id.
然后我将这个 Spring 数据 JPA 存储库 class 命名为 NetworkRepository:
public interface NetworkRepository extends JpaRepository<Network, Integer> {
/**
* Retrieve a Network object by its ID
* @param id of the network
* @return the retrieve Network object
*/
Network findById(String id);
/**
* Retrieve a Network object by its name
* @param name of the network
* @return a Network object
*/
Network findByName(String name);
/**
* Retrieve the list of all the possible networks
* @return a List<Network> object: the list of the all the networks
*/
List<Network> findAll();
}
最后我创建了一个 JUnit 测试 class 包含一个方法来测试以前的存储库 findAll() 方法,这个:
@SpringBootTest()
@ContextConfiguration(classes = GetUserWsApplication.class)
@TestMethodOrder(OrderAnnotation.class)
public class NetworkTest {
@Autowired
private NetworkRepository networkRepository;
/**
* Retrieve the networks list
*/
@Test
@Order(1)
public void findAllTest() {
List<Network> networksList = this.networkRepository.findAll();
assertTrue(networksList.size() == 5, "It retrieved 5 networks");
Set<Chain> networkChain = networksList.get(0).getChainList();
assertTrue(networkChain != null, "The network chains list are not null");
}
}
问题是 findAll() 方法执行这个 SQL 语句(我可以在我的堆栈跟踪中看到它):
Hibernate:
select
network0_.id as id1_6_,
network0_.description as descript2_6_,
network0_.name as name3_6_
from
network network0_
并检索预期的 List 对象,但正如您在以下打印屏幕中看到的,我的 chainList 字段给出错误:
它似乎没有从我的 MANY TO MANY table 中检索到 chainList(事实上,之前的 Hibernate 语句似乎没有对 network_chain 然后 chain tables).
我也尝试通过这一行直接访问这个字段(我的想法是,当显式执行对该字段的访问时,可能是 Hibernate 执行了这个连接:
Set<Chain> networkChain = networksList.get(0).getChainList();
当我尝试检索该字段时,似乎出现了 com.sun.jdi.InvocationException 异常。基本上似乎从未执行 MANY TO MANY table 和 chain table 之间的 JOINS。
为什么?我的代码有什么问题?我错过了什么?我该如何尝试修复它?
发生这种情况是因为您在 table (network_chain
) 中有一个额外的列 (id
),它负责 many-to-many
关系。
@JoinTable
注解将只支持定义2个字段,通常是id
中的2个table。 2 个 id
一起,将是 primary key
用于连接 table,也称为 composite primary key
。
在您的情况下,您必须创建一个实体来表示 table network_chain
。
@Entity
@Table(name = "network_chain")
public class NetworkChain {
@Id
@Column(name = "id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@ManyToOne
private Network network;
@ManyToOne
private Chain chain;
}
public class Network implements Serializable {
//somewhere in the code
@OneToMany
List<NetworkChain> networkChains;
}
public class Chain implements Serializable {
//somewhere in the code
@OneToMany
List<NetworkChain> networkChains;
}
现在,实体 NetworkChain
将额外的列 id
作为主键,hibernate 将进行适当的连接以获取数据。
题外话 但不确定为什么 findAll()
方法有一个 javadoc,它通常是 JpaRepository
和奇怪的 [=24] 的一部分=] 还有..