【发布时间】: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