【问题标题】:Spring Data Rest testing with JPA Relationship Mapping : Odd behaviour with update使用 JPA 关系映射进行 Spring Data Rest 测试:更新的奇怪行为
【发布时间】:2023-03-17 23:47:01
【问题描述】:

我按照本教程 (https://spring.io/guides/tutorials/react-and-spring-data-rest/#react-and-spring-data-rest-part-5) 来试验 Spring Data REST,我想用 TestRestTemplate 测试 CRUD。

  • 添加(postForEntity)没问题。
  • delete(删除)就ok了。
  • 读取 (getForEntity) 没问题。
  • Update (template.exchange(URL, HttpMethod.PUT, entity, String.class, ID)) 仅在我与其他实体没有任何关系时才有效...我不明白为什么。李>

这是一个例子:

@Data
@Entity
public class Dojo {

    private @Id @GeneratedValue Long id;
    private String name;
    private String location;
    private Date created;
    @OneToMany(mappedBy = "dojo")
    @JsonIgnore
    private List<Workshop> workshops;

    private Dojo() {}

    public Dojo(String name, String location) {
        this.name = name;
        this.location = location;
        this.created = new Date();
        this.workshops = new ArrayList<>();
    }
//getters and setters ...
}

@Data
@Entity
public class Workshop {

    private @Id @GeneratedValue Long id;
    private String name;
    @ManyToOne
    private Dojo dojo;

    private Workshop() {}

    public Workshop(String name, Dojo dojo) {
        this.name = name;
        this.dojo = dojo;
    }
}

所以,我在 Dojo 和 Workshop 之间有一个双向的 1:n 关系。 @JsonIgnore 注释在这里是为了避免 JSON Marshaller 的无限循环。 存储库是标准的

 public interface WorkshopRepository extends CrudRepository<Workshop, Long> {}

现在我的测试:我想更新一个研讨会。听起来不错,但不起作用。

@Test
public void testUpdateWorkshop() throws Exception {

    final String DOJO_NAME="My Dojo";
    final String DOJO_LOCATION="Liege";
    final String WORKSHOP_NAME="Stuff";
    final String HOST_PORT="http://localhost:8080";

     //creation of a dojo
    final Dojo DOJO = dojoRep.save(new Dojo(DOJO_NAME,DOJO_LOCATION));
     //creation of a workshop
    Workshop workshop = workshopRep.save(new Workshop(WORKSHOP_NAME,DOJO));

    String newValue = "After Test";

    System.out.println("before update");
    System.out.println(workshop.getName()+" == "+WORKSHOP_NAME);

    Long oldID = workshop.getId();

    //As you can see I didn't modify the workshop object
    HttpEntity<Workshop> entity = new HttpEntity<Workshop>(workshop);
    ResponseEntity<String> response = template.exchange(HOST_PORT+"/api/workshops/"+oldID, HttpMethod.PUT, entity, String.class, oldID);

    assert response.getStatusCodeValue() == 200;

    //re-Get the updated workshop
    workshop = workshopRep.findOne(oldID);

    System.out.println("after update");
    System.out.println(workshop.getName()+" == "+WORKSHOP_NAME);

    // as I didn't set the newValue, it must fail and workshop.getName() must stay equal to "Stuff".
    Assert.assertEquals("Update does not work",newValue,workshop.getName());
}

我运行mvn clean test

before update
Stuff == Stuff

after update
My Dojo == Stuff

Failed tests: 
  WorkshopTests.testUpdateWorkshop:218 Update not work expected:<[After Test]> but was:<[My Dojo]>

所以基本上,我没有对我的对象进行任何更改,但是

  1. 结果代码为 200。
  2. 它改变了我的对象的一个​​属性。
  3. 名称已修改为采用 dojo.name 值!

只是……为什么?

更多信息:

  • 当我使用新名称(使用 newValue ;-))和新的 Dojo 创建一个新的工作室对象并尝试更新现有的工作室时,结果仍然相同。 Workshop.dojo 未更改,名称从 dojo.name 复制。所以基本上,我的更新不起作用。
  • 我也尝试使用mockMvc,而不是像这样的TestRestTemplate

    mockMvc.perform(put(HOST_PORT+"/api/workshops/"+oldID)
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .content(convertObjectToJsonBytes(workshop))
    );
    

    用函数

    private byte[] convertObjectToJsonBytes(Object object) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        System.out.println("log my face ");
        System.out.println(mapper.writeValueAsString(object));
        return mapper.writeValueAsBytes(object);
    }
    

    并且日志似乎在更新之前正确解析了我的对象...

    {"id":1,"name":"Stuff","dojo":{"id":1,"name":"My Dojo","location":"Liege","created":1500799092330}}
    

    但还是不行:(

  • 当我运行应用程序 (mvn spring-boot:run) 时,localhost:8080/api/workshops/1 上的 GET 会返回

    {
      "name" : "Stuff",
      "_links" : {
        "self" : {
          "href" : "http://localhost-core:8080/api/workshops/1"
        },
        "workshop" : {
          "href" : "http://localhost-core:8080/api/workshops/1"
        },
        "dojo" : {
          "href" : "http://localhost-core:8080/api/workshops/1/dojo"
        }
      }
    }
    
  • 如果我通过 nameD 更改我的 Dojo 类的属性名称并使用新名称和新 Dojo(之前保存到 DB 中)进行更新,则名称会更新,但不会更新 dojo。

总结一下我的问题是:

  • 只是……为什么?
  • 使用 HTTP 请求更新像 Workshop 这样的对象的正确方法是什么?
  • 测试此更新的正确方法是什么?

谢谢大家,祝你有美好的一天! :-)

【问题讨论】:

    标签: jpa spring-boot spring-data-rest


    【解决方案1】:

    我认为这是因为您使用的是bidirectional one-to-many 关联。在这种情况下,您必须自己提供实体的链接/取消链接。例如在集合设置器中,像这样:

    @Data
    @ToString(exclude = "slaves")
    @Entity
    public class Master {
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String name;
    
        @OneToMany(mappedBy = "master", cascade = {PERSIST, MERGE})
        private List<Slave> slaves;
    
        public void setSlaves(List<Slave> slaves) {
    
            // link new slaves to this master
            slaves.forEach(slave -> slave.setMaster(this)); 
    
            // unlink prev slaves
            if (this.slaves != null) this.slaves.forEach(slave -> slave.setMaster(null)); 
    
            this.slaves = slaves;
        }
    }
    
    @Data
    @Entity
    public class Slave {
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String name;
    
        @ManyToOne
        private Master master;
    }
    

    然后你可以存储Slave:

    POST http://localhost:8080/api/slaves
    {
        "name": "slave1"
    }
    
    // the same for salve2, slave3, slave4
    

    商店Master:

    POST http://localhost:8080/api/masters
    {
        "name": "master1", 
        "slaves": [
            "http://localhost:8080/api/slaves/1",
            "http://localhost:8080/api/slaves/2"
            ]
    }
    

    更新Master

    PUT http://localhost:8080/api/masters/1
    {
        "name": "master1u", 
        "slaves": [
            "http://localhost:8080/api/slaves/3",
            "http://localhost:8080/api/slaves/4"
            ]
    }
    
    PUT http://localhost:8080/api/masters/2
    {
        "name": "master2"
    }
    

    或更新Slave

    PUT http://localhost:8080/api/slaves/1
    {
        "name": "slave1u", 
        "master": "http://localhost:8080/api/masters/2"
    }
    
    PUT http://localhost:8080/api/slaves/2
    {
        "name": "slave2u", 
        "master": "http://localhost:8080/api/masters/2"
    }
    

    查看工作example

    补充info

    【讨论】:

    • 谢谢。我使用 curl 命令得到了正确的结果。但是我尝试使用 TestRestTemplate 进行测试仍然不正确。 ://
    猜你喜欢
    • 2015-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-04
    • 2016-04-14
    • 1970-01-01
    • 2018-05-25
    相关资源
    最近更新 更多