manyToMany,设置了“CascadeType.PERSIST”,无法级联存储

学生、课程,多对多的关系,学生是关系维护端。
我在学生类里的课程集合上设置了CascadeType.PERSIST,但存储学生对象时无法自动存储级联的课程对象,
除非将CascadeType.PERSIST改成CascadeType.ALL,但ALL里面包含了REMOVE操作,我又不想要这个REMOVE操作。

(我舍去注解的形式采用hbm配置文件重新配置了一遍,在Student.hbm.xml文件里的课程集合上配置了cascade="save-update",然后存储学生对象时就能自动存储级联的课程对象,为什么配置文件形式就可以,而注解形式就不行?)


为了表述的更加清楚,请看以下代码:
学生类:
Java code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package  my.bean;
 
import  java.util.Set;
 
import  javax.persistence.CascadeType;
import  javax.persistence.Entity;
import  javax.persistence.GeneratedValue;
import  javax.persistence.Id;
import  javax.persistence.JoinColumn;
import  javax.persistence.JoinTable;
import  javax.persistence.ManyToMany;
import  javax.persistence.SequenceGenerator;
 
@Entity
public  class  Student {
     private  int  id;
     private  String sname;
     
     private  Set courses;
     
     @Id
     @SequenceGenerator (name= "idGenerator" , sequenceName= "seq_student_id" )
     @GeneratedValue (generator= "idGenerator" )
     public  int  getId() {
         return  id;
     }
 
     public  void  setId( int  id) {
         this .id = id;
     }
 
     public  String getSname() {
         return  sname;
     }
 
     public  void  setSname(String sname) {
         this .sname = sname;
     }
     
     @ManyToMany (cascade={CascadeType.PERSIST, CascadeType.MERGE, 
             CascadeType.REFRESH})
     @JoinTable (name= "student_course" ,
             joinColumns={ @JoinColumn (name= "student_id" )},
             inverseJoinColumns={ @JoinColumn (name= "course_id" )})
     public  Set getCourses() {
         return  courses;
     }
 
     public  void  setCourses(Set courses) {
         this .courses = courses;
     }
}


课程类:
Java code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package  my.bean;
 
import  java.util.Set;
 
import  javax.persistence.CascadeType;
import  javax.persistence.Entity;
import  javax.persistence.GeneratedValue;
import  javax.persistence.Id;
import  javax.persistence.ManyToMany;
import  javax.persistence.SequenceGenerator;
 
@Entity
public  class  Course {
     private  int  id;
     private  String cname;
     
     private  Set students;
     
     @Id
     @SequenceGenerator (name= "idGenerator" , sequenceName= "seq_course_id" )
     @GeneratedValue (generator= "idGenerator" )
     public  int  getId() {
         return  id;
     }
 
     public  void  setId( int  id) {
         this .id = id;
     }
 
     public  String getCname() {
         return  cname;
     }
 
     public  void  setCname(String cname) {
         this .cname = cname;
     }
     
     @ManyToMany (cascade={CascadeType.PERSIST, CascadeType.MERGE, 
             CascadeType.REFRESH}, mappedBy= "courses" )
     public  Set getStudents() {
         return  students;
     }
 
     public  void  setStudents(Set students) {
         this .students = students;
     }
}


测试类:
Java code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package  my.test;
 
import  java.util.HashSet;
import  java.util.Set;
 
import  my.bean.Course;
import  my.bean.Student;
import  my.util.HibernateSessionFactory;
 
import  org.hibernate.HibernateException;
import  org.hibernate.Session;
import  org.hibernate.Transaction;
import  org.hibernate.cfg.Configuration;
import  org.hibernate.tool.hbm2ddl.SchemaExport;
 
 
public  class  Test {
 
     public  static  void  main(String[] args) {
         
         cascadeSave();
     }
         
     public  static  void  cascadeSave() {
         Student s1 =  new  Student();
         s1.setSname( "zhangsan" );
         s1.setCourses( new  HashSet());
                 
         Course c1 =  new  Course();
         c1.setCname( "c++" );
         c1.setStudents( new  HashSet());
         
         Course c2 =  new  Course();
         c2.setCname( "java" );
         c2.setStudents( new  HashSet());
                 
         Session session =  null ;
         Transaction transaction =  null ;
         try  {
             session = HibernateSessionFactory.getSession();
             transaction = session.beginTransaction();
             
             s1.getCourses().add(c1);
             s1.getCourses().add(c2);
             
             session.save(s1);  //存储student对象时,无法自动存储级联的course对象,why?
 
             transaction.commit();
         catch (HibernateException e) {
             e.printStackTrace();
             if (transaction !=  null ) transaction.rollback();
         finally  {
             if (session !=  null ) session.close();
         }
     }
     
             
}

解:@OneToMany(cascade = {CascadeType.PERSIST}),发现级联不起作用,如果更改为Hibernate的注解 @Cascade({org.hibernate.annotations.CascadeType.PERSIST}),依然不起作用,但改为 @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})之后,注解生效。
原因如下:
如果使用javax.persistence.*里面的注解,只有调用相应的方法才生效,如PERSIST,只有调用persist方法才生效,Hibernate的注解也是如此。
查看我的代码,我保存对象用的是save方法,因此要用SAVE_UPDATE,级联才能生效。

2、使用方法
如果在ManyToOne的一端使用,如下(Vote类):
@ManyToOne
    @Cascade({CascadeType.SAVE_UPDATE})
    private VoteSubject voteSubject;

那么在保存该Vote对象时,如果对象的voteSubject属性是一个新对象,则会在保存Vote对象时,顺便把voteSubject对象保存;反之,如果one2Many的一端如果没有设置关联,则one的一端保存时,不会保存集合中的新对象。

也就是说在哪个对象中的相应属性中设置了级联,那么在操作该对象时级联生效。
如果想让有关系的双方同时级联生效,那么级联要在两个对象中同时设置。

@ManyToOne(cascade = { CascadeType.PERSIST,CascadeType.MERGE}) 
@JoinColumn(name = "HOSPITAL_ID") 

改为: 
@ManyToOne() 
@Cascade(value={CascadeType.SAVE_UPDATE}) 
@JoinColumn(name = "HOSPITAL_ID") 

并把 
import javax.persistence.CascadeType; 
import javax.persistence.Cascade; 
改成 
import org.hibernate.annotations.Cascade; 
import org.hibernate.annotations.CascadeType; 







I have three entities (taken from the Spring Data REST Exporter Example): Person, Address and Profile. A Person can have addresses and profiles.

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    private String name;
    @Version
    private Long version;
    @OneToMany
    private List<Address> addresses;
    @OneToMany
    private Map<String, Profile> profiles;

    // getters and setters
}

In the client side I use Spring's RestTemplate. I added the Jackson2HalModule to the ObjectMapper used by the MappingJackson2HttpMessageConverter used by my RestTemplate.

Since Address and Profile do not have references to other entities I can POST them to my Spring Data REST server, and they are successfully saved:

final ResponseEntity<Resource<Address>> response = restTemplate.postForEntity("http://localhost:8080/addresses",
                addressInstance, AddressResource.class);

where AddressResource extends org.springframework.hateoas.Resource

.

But when I try to POST a Person instance

final ResponseEntity<Resource<Person>> response = restTemplate.postForEntity("http://localhost:8080/people",
                personInstance, PersonResource.class);

I get a org.springframework.web.client.HttpClientErrorException: 400 Bad Request and I think the cause is the associated Addresses and Profiles are serialized as normal POJOs instead as their resource URIs.

Here is the actual body of the POST request:

{
   "id":null,
   "name":"Jongjin Han",
   "version":null,
   "addresses":[
      {
         "id":1,
         "lines":[
            "1111",
            "coder's street"
         ],
         "city":"San Diego",
         "province":"California",
         "postalCode":"60707"
      },
      {
         "id":2,
         "lines":[
            "1111",
            "coder's street"
         ],
         "city":"San Diego",
         "province":"California",
         "postalCode":"60707"
      }
   ],
   "profiles":{
      "key1":{
         "type":"a type of profile",
         "url":"http://www.profileurl.com"
      },
      "key2":{
         "type":"a type of profile",
         "url":"http://www.profileurl.com"
      }
   }
}

I think it should be --> EDIT: It should be

{
   "id":null,
   "name":"Jongjin Han",
   "version":null,
   "addresses":[
      "http://localhost:8080/addresses/1",
      "http://localhost:8080/addresses/2"
   ],
   "profiles":{
      "key1":"http://localhost:8080/profiles/1",
      "key2":"http://localhost:8080/profiles/2"
   }
}

in fact the response body from the server is

{
  "cause" : {
    "cause" : {
      "cause" : {
        "cause" : null,
        "message" : "Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable."
      },
      "message" : "Failed to convert from type java.net.URI to type org.springframework.data.rest.example.model.Address for value 'id'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable."
    },
    "message" : "Failed to convert from type java.net.URI to type org.springframework.data.rest.example.model.Address for value 'id'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable. (through reference chain: org.springframework.data.rest.example.model.Person[\"addresses\"]->java.util.ArrayList[1])"
  },
  "message" : "Could not read document: Failed to convert from type java.net.URI to type org.springframework.data.rest.example.model.Address for value 'id'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable. (through reference chain: org.springframework.data.rest.example.model.Person[\"addresses\"]->java.util.ArrayList[1]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Failed to convert from type java.net.URI to type org.springframework.data.rest.example.model.Address for value 'id'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI id. Is it local or remote? Only local URIs are resolvable. (through reference chain: org.springframework.data.rest.example.model.Person[\"addresses\"]->java.util.ArrayList[1])"
}

The possible solution I'd like to implement

Since I can access the REST repositories from the client side I am looking for a way to customize the Jackson Json Serializer in order to:

  • check if the object I am serializing is a REST exported entity (easy with reflection, if only I could know where to put the code) and
  • If I am serializing an entity, serialize the non-association fields as usual (e.g. person's name) and the association fields as their Resource URI (e.g. person's addresses) (with reflection it should be easy to convert from an entity to its resource URI, but I do not know where to put the code once again)

I tried with Jackson's JsonSerializer and PropertyFilters for Address and Profile, but I want a serializer which serialize them as resource URI only when they are in an association.

Any hint or aternative solution will be helpful.


你可能感兴趣的:(java)