*** 错误设置多对多关系 Spring JPA

Posted

技术标签:

【中文标题】*** 错误设置多对多关系 Spring JPA【英文标题】:*** Error Setting up Many-to-Many Relationship Spring JPA 【发布时间】:2017-05-24 18:26:06 【问题描述】:

上下文:在用户和事件之间建立多对多关系。下面是我的 application.properties 文件中的数据库配置。我的应用程序是一个 Springboot 项目。我会在某个时候从 h2 迁移到 postgreSQL。我的事件和用户存储库只是实现了 JPArepositories。到目前为止我读到的是***问题主要是由一些无限循环问题引起的,我可以看到当我从用户表中获取所有内容时,它会不断重复用户和事件之间的一种组合连接。在网址:http://localhost:8090/members/all

我明白了: 然后它永远不会结束。

spring.datasource.url=jdbc:h2:file:./members.db
server.port = 8090

Gradle 文件:

buildscript 
    ext 
        springBootVersion = '1.5.3.RELEASE'
    
    repositories 
        mavenCentral()
    
    dependencies 
        classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
    


apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories 
    mavenCentral()



dependencies 
    testCompile('org.springframework.boot:spring-boot-starter-test')
    compile('org.springframework.boot:spring-boot-starter-actuator')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile (group: 'com.vividsolutions', name: 'jts', version: '1.13')
    compile (group: 'org.orbisgis', name: 'h2gis', version: '1.3.1')
    compile (group: 'org.hibernate', name: 'hibernate-core', version: '5.2.10.Final')
    compile (group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.2.10.Final')
    compile (group: 'org.hibernate', name: 'hibernate-spatial', version: '5.2.10.Final')
    runtime('com.h2database:h2')
    //  runtime('org.postgresql:postgresql')

实体: 事件.java

package com.alex_donley.event_mapper.Entities;

import com.vividsolutions.jts.geom.Geometry;

import javax.persistence.*;
import java.util.Date;
import java.util.Set;

/**
 * Created by indycorps on 5/24/2017.
 */
@Entity
public class Event 

    private long id;
    private String name;
    private String address;
    private String city;
    private String state;
    private Set<User> users;
    @Column(columnDefinition="Geometry")
    private Geometry location;
    private double price;
    private Date eventTime;
    private String category;
    private long secretCode;

    public Event(String name, String address, String city, String state, Geometry location, double price, Date eventTime, String category, long secretCode) 
        this.name = name;
        this.address = address;
        this.city = city;
        this.state = state;
        this.location = location;
        this.price = price;
        this.eventTime = eventTime;
        this.category = category;
        this.secretCode = secretCode;
    

    public Event(String name, String address, String city, String state, Set<User> users, Geometry location, double price, Date eventTime, String category, long secretCode) 
        this.name = name;
        this.address = address;
        this.city = city;
        this.state = state;
        this.users = users;
        this.location = location;
        this.price = price;
        this.eventTime = eventTime;
        this.category = category;
        this.secretCode = secretCode;
    

    public Event()

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public long getId() 
        return id;
    

    public void setId(long id) 
        this.id = id;
    

    @ManyToMany(mappedBy = "events")
    public Set<User> getUsers() 
        return users;
    

    public void setUsers(Set<User> users) 
        this.users = users;
    

    public String getName() 
        return name;
    
    public void setName(String name) 
        this.name = name;
    
    public String getAddress() 
        return address;
    
    public void setAddress(String address) 
        this.address = address;
    
    public String getCity() 
        return city;
    
    public void setCity(String city) 
        this.city = city;
    
    public String getState() 
        return state;
    
    public void setState(String state) 
        this.state = state;
    
    public double getPrice() 
        return price;
    
    public void setPrice(double price) 
        this.price = price;
    
    public Date getEventTime() 
        return eventTime;
    
    public void setEventTime(Date eventTime) 
        this.eventTime = eventTime;
    
    public String getCategory() 
        return category;
    
    public void setCategory(String category) 
        this.category = category;
    
    public long getSecretCode() 
        return secretCode;
    
    public void setSecretCode(long secretCode) 
        this.secretCode = secretCode;
    

用户.java

package com.alex_donley.event_mapper.Entities;


import com.vividsolutions.jts.geom.Geometry;

import javax.persistence.*;
import java.util.Set;

/**
 * Created by Indycorps on 5/11/2017.
 */
@Entity
public class User 

    private long id;
    private String firstName;
    private String lastName;
    private Set<Event> events;
    @Column(columnDefinition="Geometry")
    private Geometry location;

    public User(String firstName, String lastName, Geometry location) 
        this.firstName = firstName;
        this.lastName = lastName;
        this.location = location;
    

    public User(String firstName, String lastName, Set<Event> events, Geometry location) 
        this.firstName = firstName;
        this.lastName = lastName;
        this.events = events;
        this.location = location;
    

    public User()

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public long getId() 
        return id;
    

    public void setId(long id) 
        this.id = id;
    

    public String getFirstName() 
        return firstName;
    

    public void setFirstName(String firstName) 
        this.firstName = firstName;
    

    public String getLastName() 
        return lastName;
    

    public void setLastName(String lastName) 
        this.lastName = lastName;
    

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "attendee",joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "event_id", referencedColumnName = "id"))
    public Set<Event> getEvents() 
        return events;
    

    public void setEvents(Set<Event> events) 
        this.events = events;
    

    //Create getters and setters for location to actually output stuff
//    public Geometry getLocation() 
//        return location;
//    
//
//    public void setLocation(Geometry location) 
//        this.location = location;
//    

CommandLineRunner 填充 H2 DB

package com.alex_donley.event_mapper;

import com.alex_donley.event_mapper.Entities.Event;
import com.alex_donley.event_mapper.Entities.User;
import com.alex_donley.event_mapper.Repositories.EventRepository;
import com.alex_donley.event_mapper.Repositories.UserRepository;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import javax.transaction.Transactional;
import java.util.*;

/**
 * Created by DonleyAl on 5/12/2017.
 */
@Component
public class DatabaseSeeder implements CommandLineRunner 
    private UserRepository userRepository;
    private static final Logger logger = LoggerFactory.getLogger(DatabaseSeeder.class);

    @Autowired
    public DatabaseSeeder(UserRepository userRepository) 
        this.userRepository = userRepository;
    

    private Geometry wktToGeometry(String wktPoint) 
    WKTReader fromText = new WKTReader();
    Geometry geom = null;
    try 
        geom = fromText.read(wktPoint);
     catch (ParseException e) 
        throw new RuntimeException("Not a WKT string:" + wktPoint);
    
    return geom;
    
    @Override @Transactional public void run(String... strings) throws Exception 
        Event eventA = new Event("AppleBottom", "101 Funhouse", "Alexandria", "VA", wktToGeometry("POINT(-105 40)"), 0.0, new Date(12312), "Action", 102321);
        Event eventB = new Event("Bandman", "55 Flash", "Sterling", "VA", wktToGeometry("POINT(-40 10)"), 0.0, new Date(3542), "Action", 4231234);
        Event eventC = new Event("Carship", "1 Whitehouse", "DC", "Washington", wktToGeometry("POINT(123 124)"), 0.0, new Date(432), "Mystery", 3428);

        userRepository.save(new HashSet<User>()
            add(new User("Alex", "Donley", new HashSet<Event>()
                add(eventA);
                add(eventC);
            , wktToGeometry("POINT(-105 40)")));
            add(new User("Bob", "Builder", new HashSet<Event>()
                add(eventA);
                add(eventB);
            , wktToGeometry("POINT(-105 40)")));
        );
    

UserController,是在我建立多对多关系之前创建的

package com.alex_donley.event_mapper.Controllers;

import com.alex_donley.event_mapper.Entities.User;
import com.alex_donley.event_mapper.Repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * Created by Indycorps on 5/11/2017.
 */
@RestController
@RequestMapping(value = "/members")
public class UserController 
    private UserRepository userRepository;

    @Autowired
    public UserController(UserRepository userRepository) 
        this.userRepository = userRepository;
    

    @RequestMapping(value = "/all", method = RequestMethod.GET)
    public List<User> getAll()
        return userRepository.findAll();
    
    @RequestMapping(value = "/filterby/name", method = RequestMethod.GET)
    public List<User> getName(@PathVariable String name) 
        return userRepository.findByFirstNameLike(name);
    
    @RequestMapping(value = "/create", method = RequestMethod.POST)
    public List<User> create(@RequestBody User user)
        userRepository.save(user);
        return userRepository.findAll();
    

    @RequestMapping(value ="/delete/id", method = RequestMethod.DELETE)
    public List<User> remove(@PathVariable long id)
        userRepository.delete(id);
        return  userRepository.findAll();
    

EDIT-1 解决方案:

使用@JSONIgnore 这是我现在从控制器输出的结果。这是来自以下网址:http://localhost:8090/members/all

[
  
    "id": 1,
    "firstName": "Bob",
    "lastName": "Builder",
    "events": [
      
        "id": 1,
        "name": "AppleBottom",
        "address": "101 Funhouse",
        "city": "Alexandria",
        "state": "VA",
        "price": 0,
        "eventTime": 12312,
        "category": "Action",
        "secretCode": 102321
      ,
      
        "id": 2,
        "name": "Bandman",
        "address": "55 Flash",
        "city": "Sterling",
        "state": "VA",
        "price": 0,
        "eventTime": 3542,
        "category": "Action",
        "secretCode": 4231234
      
    ]
  ,
  
    "id": 2,
    "firstName": "Alex",
    "lastName": "Donley",
    "events": [
      
        "id": 1,
        "name": "AppleBottom",
        "address": "101 Funhouse",
        "city": "Alexandria",
        "state": "VA",
        "price": 0,
        "eventTime": 12312,
        "category": "Action",
        "secretCode": 102321
      ,
      
        "id": 3,
        "name": "Carship",
        "address": "1 Whitehouse",
        "city": "DC",
        "state": "Washington",
        "price": 0,
        "eventTime": 432,
        "category": "Mystery",
        "secretCode": 3428
      
    ]
  
]

【问题讨论】:

一个用户有事件;一个事件有用户。你必须打破这个循环。 我不确定 spring/jpa 中打破循环的语法。最终,我想要一些方法来生成一个关联表,该关联表将多对多关系与事件/用户的各种组合联系起来。你有什么建议如何打破这个循环,同时仍然保持这种多对多的关系。我认为 User.java 中的这一行就是这样做的。 @ManyToMany(cascade = CascadeType.ALL) @JoinTable(name = "attendee",joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "event_id", referencedColumnName = " id")) 就像你说的我假设发生的事情是它不只是输出两个表的总组合,而是不断递归搜索用户->事件->用户...我假设在我的数据库中runner,因为我只有用户->事件,并且在该事件中没有用户不再向下迭代。我在事件和用户中重载了两个构造函数,这样他们就不需要维护一组其他的了。 请将我的答案标记为正确 一旦我测试它并且它有效,我肯定会。我不想提前关闭线程,以防我有后续问题。到早上我应该要么接受答案,要么提供额外的反馈。感谢您帮助我。 【参考方案1】:

您的问题在于序列化。 所以你可以使用@JsonIgnore注解,如果你使用的是Jackson Json provider。

@JsonIgnore
@ManyToMany(mappedBy = "events")
public Set<User> getUsers() 
    return users;

【讨论】:

但是如果你需要用户列表,你需要教杰克逊应该如何表现。一个例子是使用 @JsonSerializer,所以不要重复 Event 属性。见davismol.net/2015/06/05/…

以上是关于*** 错误设置多对多关系 Spring JPA的主要内容,如果未能解决你的问题,请参考以下文章

带有额外列的 Spring Data JPA 多对多

Spring数据JPA-休眠多对多关系在链接实体表中插入null

Spring Boot JPA多对多关系-Rest Web Service无法返回子对象

在 Spring JPA 多对多关系中的作者更少

在多对一中使用 Spring Data JPA 保持错误关系

Spring-boot JPA无限循环多对多