【问题标题】:How to use a ManyToMany collection with Spring Data如何在 Spring Data 中使用 ManyToMany 集合
【发布时间】:2014-05-03 15:47:39
【问题描述】:

我真的很困惑如何在我的应用程序中使用 @ManyToMany 子集合。为了演示我的问题,我在https://bitbucket.org/ctombaugh/test

建立了一个示例项目

主要问题是当我保存父对象时子集合被删除。我能想到的唯一避免这种情况的方法是从数据库中获取所有子集合并在保存之前将它们附加到父对象,但我不敢相信这是正确的方法。所以我想我做错了什么,希望有人能指出它并提高我对 JPA 工作原理的理解。

我想提前提一下,这个问题与我的other question 有关,但我认为最好关注问题的核心,删除所有上下文,并使用一个干净的示例项目。我希望这不是问题。

父对象类Test

@Entity
public final class Test implements Identifiable<Integer> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private volatile Integer id;

    @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
    @JoinTable(name = "test_label", joinColumns = { @JoinColumn(name = "test") }, inverseJoinColumns = { @JoinColumn(name = "label") })
    private Set<Label> labels = new HashSet<Label>();

    private volatile String name = "";

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Test test = (Test) o;
        if (!id.equals(test.id)) return false;
        return true;
    }

    @Override
    public int hashCode() {
        if (id == null) return 0;
        return id.hashCode();
    }

}

子对象类Label

@Entity
public final class Label implements Identifiable<Integer> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private volatile Integer id;

    private volatile String name = "";

    public Label() {
    }

    public Label(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Label label = (Label) o;
        if (!id.equals(label.id)) return false;
        return true;
    }

    @Override
    public int hashCode() {
        if (id == null) return 0;
        return id.hashCode();
    }

}

服务TestServiceImpl

@Service
final class TestServiceImpl implements TestService {

    private final TestRepository testRepository;

    @Autowired
    TestServiceImpl(TestRepository testRepository) {
        this.testRepository = testRepository;
    }

    @Override
    public List<Test> findAll() {
        return testRepository.findAll();
    }

    @Override
    @Transactional
    public Test save(Test test) {
        return testRepository.save(test);
    }

    @Override
    @Transactional
    public Test saveAndAdd(Test test, Label label) {
        test = testRepository.save(test);
        test.getLabels().add(label);
        return testRepository.save(test);
    }

    @Override
    @Transactional
    public Test create() {
        return testRepository.save(new Test());
    }

    @Override
    public Test findOne(Integer id) {
        return testRepository.findOne(id);
    }

}

表单支持对象TestForm

public class TestForm {

    private Test test;
    private Label label;

    public Test getTest() {
        return test;
    }

    public void setTest(Test test) {
        this.test = test;
    }

    public Label getLabel() {
        return label;
    }

    public void setLabel(Label label) {
        this.label = label;
    }

}

控制器TestConroller

@Controller
@RequestMapping("/")
public class TestController {

    private TestService testService;
    private LabelService labelService;

    private static final Logger logger = Logger.getLogger(TestController.class);

    @Autowired
    TestController(TestService testService, LabelService labelService) {
        this.testService = testService;
        this.labelService = labelService;

        // populate labels
        List<Label> labels = labelService.findAll();
        if (labels == null || labels.size() == 0) {
            labelService.create("label a");
            labelService.create("label b");
        }

    }

    @ModelAttribute("labels")
    public List<Label> getLabels() {
        return labelService.findAll();
    }

    @ModelAttribute("user")
    public String getUser() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String name = auth.getName();
        return name;
    }

    @RequestMapping(value="/", method = RequestMethod.GET)
    public String firstPage(ModelMap model) {
        List<Test> tests = testService.findAll();
        model.addAttribute("tests", tests);
        return "index";
    }

    @RequestMapping(value="/login", method = RequestMethod.GET)
    public String login(ModelMap model) {
        return "login";
    }

    @RequestMapping(value="/addtest", method = RequestMethod.GET)
    public String addExpert(ModelMap model) {
        testService.create();
        return "redirect:/";
    }

    @RequestMapping(value="/test/{testId}", method=RequestMethod.GET)
    public String editProject(@PathVariable Integer testId, ModelMap model) {
        TestForm form = new TestForm();
        Test test = testService.findOne(testId);
        form.setTest(test);
        model.addAttribute("testForm", form);
        return "test";
    }

    @RequestMapping(value="/test", method=RequestMethod.POST)
    public String submitTest(@ModelAttribute("testForm") TestForm testForm, BindingResult result) {
        logger.info("labels: " + testForm.getTest().getLabels());
        if (testForm.getTest().getLabels() != null) {
            logger.info("labels size: " + testForm.getTest().getLabels().size());
        }
        testService.save(testForm.getTest());
        return "redirect:/";
    }

    @RequestMapping(value="/addlabel", method=RequestMethod.POST)
    public String addLabel(@ModelAttribute("testForm") TestForm testForm, BindingResult result, ModelMap model) {
        Test test = testForm.getTest();
        Label label = testForm.getLabel();
        test = testService.saveAndAdd(test, label);
        return "redirect:/test/" + test.getId();
    }
}

Thymeleaf 模板表单test.html

<form id="testForm" action="#" th:action="@{/test}" th:object="${testForm}" method="post">

    <input type="hidden" th:field="*{test.id}" />

    <p>
        <label for="name">Name</label>
        <input id="name" type="text" th:field="*{test.name}" />
    </p>

    <p>
        <button id="testbutton" type="submit" name="testbutton">Save</button>
    </p>

    <h4>Labels</h4>

    <table>
        <tr>
            <th>id</th>
            <th>name</th>
        </tr>
        <tr th:each="label : *{test.labels}">
            <td th:text="${label.id}"></td>
            <td th:text="${label.name}"></td>
        </tr>
    </table>

    <p>
        <select id="label" th:field="*{label}">
            <option value="">(select label)</option>
            <option th:each="label : ${labels}"
                    th:value="${label.id}"
                    th:text="${label.name}"></option>
        </select>

        <a href="#" onclick="addLabel();">Add</a>
    </p>
</form>

应用上下文ApplicationContext

@Configuration
@EnableWebMvc
@ComponentScan
@Import({ SecurityConfig.class })
@PropertySource("classpath:application.properties")
@EnableJpaRepositories("test.model")
public class ApplicationContext extends WebMvcConfigurerAdapter {

    @Autowired
    public StringToLabel stringToLabel;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/css/**").addResourceLocations("/resources/css/**");
        registry.addResourceHandler("/js/**").addResourceLocations("/resources/js/**");
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(stringToLabel);
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource datasource = new DriverManagerDataSource();
        datasource.setDriverClassName("org.hsqldb.jdbcDriver");
        datasource.setUrl("jdbc:hsqldb:mem:test");
        datasource.setUsername("sa");
        datasource.setPassword("");
        return datasource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
        LocalContainerEntityManagerFactoryBean factoryBean
                = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setDataSource( dataSource() );
        factoryBean.setPackagesToScan( new String[ ] { "test.model" } );
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        factoryBean.setJpaVendorAdapter( vendorAdapter );
        factoryBean.setJpaProperties( this.additionalProperties() );
        return factoryBean;
    }

    @Bean
    public PlatformTransactionManager transactionManager(){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
                this.entityManagerFactory().getObject() );
        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
        return new PersistenceExceptionTranslationPostProcessor();
    }

    Properties additionalProperties() {
        Properties properties = new Properties();
        properties.setProperty("hibernate.hbm2ddl.auto", "update");
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        return properties;
    }

}

应用初始化器ApplicationInitializer

public class ApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(ApplicationContext.class);
        rootContext.setDisplayName("Test application");

        servletContext.addListener(new ContextLoaderListener(rootContext));

        ServletRegistration.Dynamic dispatcher =
                servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

        FilterRegistration.Dynamic filter = servletContext.addFilter("openEntityManagerInViewFilter", OpenEntityManagerInViewFilter.class);
        filter.setInitParameter("singleSession", "true");
        filter.addMappingForServletNames(null, true, "dispatcher");

    }

}

编辑 1: 显然我无法初始化服务中的任何集合(调用 size() 不起作用)。这是否意味着父对象被分离?这也适用于OneToMany 集合,但我可以通过保存子代而不是父代来添加多个子代。

我想我的配置一定有问题,有什么办法可以解决吗?

【问题讨论】:

  • 嗨,使用 (cascade = CascadeType.ALL) 让我知道效果。
  • 多对多和任何其他类型的关系都来自 Hibernate/JPA,根本不是来自 Spring。
  • Karibasappa G C,恐怕没有效果。
  • Luiggi Mendoza,我正在使用 Spring Data JPA,但在调用 Spring 控制器后初始化惰性集合时遇到问题,这可能是由于错误使用了 Spring 事务管理或 Spring 上下文。所以我不明白为什么我不能提及我正在使用 Spring 的事实。

标签: java spring hibernate spring-mvc jpa


【解决方案1】:

看起来您正在延迟加载 @ManyToMany 集合,然后将旧的分离对象与空子对象(因为子对象从未加载)合并到新会话中并保存父对象。 并且当保存父级时,您还将相同的操作级联到子级,这会导致删除为 hibernate/jpa 假设您手动删除它们并合并父级。

为避免这种情况,要么急切地加载关系中的所有子级,要么不要级联合并和管理独立于父级的子级。

【讨论】:

  • 独立管理子级,去掉级联就够了吗?我尝试了@ManyToMany(fetch = FetchType.LAZY),但是当我保存父级时,该集合仍然被删除。
  • 请尝试这个使收藏成为只读? @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "test_label", joinColumns = { @JoinColumn(name = "test", insertable=false,updatable=false) }, inverseJoinColumns = { @JoinColumn(name = "label ", insertable=false,updatable=false) }) 私有 Set
猜你喜欢
  • 2019-06-16
  • 2019-06-16
  • 2021-03-17
  • 2017-05-22
  • 1970-01-01
  • 1970-01-01
  • 2016-12-01
  • 2011-12-15
  • 1970-01-01
相关资源
最近更新 更多