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

来源:互联网 发布:上海博科资讯java待遇 编辑:程序博客网 时间:2024/05/18 00:23
学生、课程,多对多的关系,学生是关系维护端。
我在学生类里的课程集合上设置了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<Course> 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<Course> getCourses() {
        return courses;
    }
 
    public void setCourses(Set<Course> 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<Student> 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<Student> getStudents() {
        return students;
    }
 
    public void setStudents(Set<Student> 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>());
                 
        Course c1 = new Course();
        c1.setCname("c++");
        c1.setStudents(new HashSet<Student>());
         
        Course c2 = new Course();
        c2.setCname("java");
        c2.setStudents(new HashSet<Student>());
                 
        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.

@Entitypublic 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<Address>.

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.


0 0
原创粉丝点击