JpaRepository 在不同的表上使用 Native @Query
JpaRepository with Native @Query on different tables
我刚刚在 spring 数据中偶然发现了一些意外行为。
为了演示,我使用 Initializr (https://start.spring.io/) 设置了一些 spring 启动应用程序,添加了 JPA、Web、H2。
该应用程序包含两个 table 和一些数据:
data.sql:
create table table1 (
id int,
name varchar(50)
);
create table table2 (
id int,
name varchar(50)
);
insert into table1 (id, name) values (1, 'First row from table 1');
insert into table1 (id, name) values (2, 'Second row from table 1');
insert into table1 (id, name) values (3, 'Third row from table 1');
insert into table1 (id, name) values (4, 'Fourth row from table 1');
insert into table2 (id, name) values (1, '** TABLE 2: 1st ROW UPPERCASE **');
insert into table2 (id, name) values (2, '** TABLE 2: 2nd ROW UPPERCASE **');
此 table 结构只有一个模型,因为两个 table 的结构相同。我为此创建了一个 JpaRepository table
示例DAO:
@Repository
public interface SampleDAO extends JpaRepository<SampleModel, Integer> {
@Query(value = "select id, name from table1", nativeQuery = true)
List<SampleModel> findAllFromTable1();
@Query(value = "select id, name from table2", nativeQuery = true)
List<SampleModel> findAllFromTable2();
}
最后我添加了一个控制器(TestController):
@RestController
public class TestController {
@Autowired
private final SampleDAO sampleDAO;
public TestController(SampleDAO sampleDAO) {
this.sampleDAO = sampleDAO;
}
@GetMapping(path = "/")
@ResponseBody
public String testNativeQuery() {
List<SampleModel> list1 = this.sampleDAO.findAllFromTable1();
List<SampleModel> list2 = this.sampleDAO.findAllFromTable2();
SampleModel m1 = list1.get(0);
SampleModel m2 = list2.get(0);
System.out.println("*****************************************");
System.out.println("Data from findAllFromTable1():");
list1.forEach(l -> {
System.out.println(l.getName());
});
System.out.println("*****************************************");
System.out.println("Data from findAllFromTable2():");
list2.forEach(l -> {
System.out.println(l.getName());
});
System.out.println("*****************************************");
return "Done";
}
}
我希望 list1 和 list2 包含我的两个 table 的数据,但令人惊讶的是只获取了第一个 table 的结果:
*****************************************
Data from findAllFromTable1():
First row from table 1
Second row from table 1
Third row from table 1
Fourth row from table 1
*****************************************
Data from findAllFromTable2():
First row from table 1
Second row from table 1
*****************************************
您可以在此处找到示例项目:https://github.com/steinmann321/nativequerydemo
这是预期的行为还是我做错了什么?
(请注意:这只是一个示例项目,实际查询要复杂得多但结果是一样的)
您遇到了跨持久实体的身份冲突问题。您可以看到您从第一个查询中获得了四个记录,从第二个查询中获得了两个与数据库中的记录匹配的记录。但是当 entitymanager 正在处理来自第二组的记录时,它看到已经有 ID 为 1 和 2 的持久实体,因此它不会创建新实体。
如果你想让它工作,你需要指明你有一个由 id 和 name 组成的复合键。这也将匹配您的 equals 方法中的业务逻辑。
创建 JPA 可嵌入密钥:
@Embeddable
public class MyKey implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "id", nullable = false)
private String id;
@Column(name = "name", nullable = false)
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MyKey that = (MyKey) o;
return Objects.equals(id, that.getId()) && Objects.equals(name, that.getName());
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
然后在您的 SampleModel 实体中使用嵌入式密钥:
@Entity
public class SampleModel {
@EmbeddedId
private MyKey myKey;
public MyKey getMyKey() {
return myKey;
}
public void setMyKey(MyKey myKey) {
this.myKey = myKey;
}
}
您的控制器将稍作更改以从键中提取值。
System.out.println("*****************************************");
System.out.println("Data from findAllFromTable1():");
list1.forEach(l -> {
System.out.println(l.getMyKey().getName());
});
System.out.println("*****************************************");
System.out.println("Data from findAllFromTable2():");
list2.forEach(l -> {
System.out.println(l.getMyKey().getName());
});
System.out.println("*****************************************");
然后试试你的 TestController。我确实试过了,结果如下:
*****************************************
Data from findAllFromTable1():
First row from table 1
Second row from table 1
Third row from table 1
Fourth row from table 1
*****************************************
Data from findAllFromTable2():
** TABLE 2: 1st ROW UPPERCASE **
** TABLE 2: 2nd ROW UPPERCASE **
*****************************************
我刚刚在 spring 数据中偶然发现了一些意外行为。 为了演示,我使用 Initializr (https://start.spring.io/) 设置了一些 spring 启动应用程序,添加了 JPA、Web、H2。
该应用程序包含两个 table 和一些数据:
data.sql:
create table table1 (
id int,
name varchar(50)
);
create table table2 (
id int,
name varchar(50)
);
insert into table1 (id, name) values (1, 'First row from table 1');
insert into table1 (id, name) values (2, 'Second row from table 1');
insert into table1 (id, name) values (3, 'Third row from table 1');
insert into table1 (id, name) values (4, 'Fourth row from table 1');
insert into table2 (id, name) values (1, '** TABLE 2: 1st ROW UPPERCASE **');
insert into table2 (id, name) values (2, '** TABLE 2: 2nd ROW UPPERCASE **');
此 table 结构只有一个模型,因为两个 table 的结构相同。我为此创建了一个 JpaRepository table
示例DAO:
@Repository
public interface SampleDAO extends JpaRepository<SampleModel, Integer> {
@Query(value = "select id, name from table1", nativeQuery = true)
List<SampleModel> findAllFromTable1();
@Query(value = "select id, name from table2", nativeQuery = true)
List<SampleModel> findAllFromTable2();
}
最后我添加了一个控制器(TestController):
@RestController
public class TestController {
@Autowired
private final SampleDAO sampleDAO;
public TestController(SampleDAO sampleDAO) {
this.sampleDAO = sampleDAO;
}
@GetMapping(path = "/")
@ResponseBody
public String testNativeQuery() {
List<SampleModel> list1 = this.sampleDAO.findAllFromTable1();
List<SampleModel> list2 = this.sampleDAO.findAllFromTable2();
SampleModel m1 = list1.get(0);
SampleModel m2 = list2.get(0);
System.out.println("*****************************************");
System.out.println("Data from findAllFromTable1():");
list1.forEach(l -> {
System.out.println(l.getName());
});
System.out.println("*****************************************");
System.out.println("Data from findAllFromTable2():");
list2.forEach(l -> {
System.out.println(l.getName());
});
System.out.println("*****************************************");
return "Done";
}
}
我希望 list1 和 list2 包含我的两个 table 的数据,但令人惊讶的是只获取了第一个 table 的结果:
*****************************************
Data from findAllFromTable1():
First row from table 1
Second row from table 1
Third row from table 1
Fourth row from table 1
*****************************************
Data from findAllFromTable2():
First row from table 1
Second row from table 1
*****************************************
您可以在此处找到示例项目:https://github.com/steinmann321/nativequerydemo
这是预期的行为还是我做错了什么?
(请注意:这只是一个示例项目,实际查询要复杂得多但结果是一样的)
您遇到了跨持久实体的身份冲突问题。您可以看到您从第一个查询中获得了四个记录,从第二个查询中获得了两个与数据库中的记录匹配的记录。但是当 entitymanager 正在处理来自第二组的记录时,它看到已经有 ID 为 1 和 2 的持久实体,因此它不会创建新实体。
如果你想让它工作,你需要指明你有一个由 id 和 name 组成的复合键。这也将匹配您的 equals 方法中的业务逻辑。
创建 JPA 可嵌入密钥:
@Embeddable
public class MyKey implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "id", nullable = false)
private String id;
@Column(name = "name", nullable = false)
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MyKey that = (MyKey) o;
return Objects.equals(id, that.getId()) && Objects.equals(name, that.getName());
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
然后在您的 SampleModel 实体中使用嵌入式密钥:
@Entity
public class SampleModel {
@EmbeddedId
private MyKey myKey;
public MyKey getMyKey() {
return myKey;
}
public void setMyKey(MyKey myKey) {
this.myKey = myKey;
}
}
您的控制器将稍作更改以从键中提取值。
System.out.println("*****************************************");
System.out.println("Data from findAllFromTable1():");
list1.forEach(l -> {
System.out.println(l.getMyKey().getName());
});
System.out.println("*****************************************");
System.out.println("Data from findAllFromTable2():");
list2.forEach(l -> {
System.out.println(l.getMyKey().getName());
});
System.out.println("*****************************************");
然后试试你的 TestController。我确实试过了,结果如下:
*****************************************
Data from findAllFromTable1():
First row from table 1
Second row from table 1
Third row from table 1
Fourth row from table 1
*****************************************
Data from findAllFromTable2():
** TABLE 2: 1st ROW UPPERCASE **
** TABLE 2: 2nd ROW UPPERCASE **
*****************************************