[英]Vaadin, JPA, Spring - simple form for entity with composed primary key
I have simple database model, with ability to nest product categories, like this:我有简单的数据库 model,能够嵌套产品类别,如下所示:
And I want to create basic form in Vaadin, that has two ComboBoxes to create this decomposed entity .我想在 Vaadin 中创建基本表单,它有两个 ComboBoxes 来创建这个分解的实体。
I cannot bind composed key that si constructed with @Embeddable and @EmbeddedId to my form fields .我无法将使用@Embeddable 和@EmbeddedId 构造的组合键绑定到我的表单字段。
When I click on button "Add category" which should bring up an empty form, I get this error :当我单击“添加类别”按钮时,应该会显示一个空表单,我收到此错误:
java.lang.NullPointerException: null
at java.base/java.lang.reflect.Method.invoke(Method.java:559) ~[na:na]
at com.vaadin.flow.data.binder.BeanPropertySet.invokeWrapExceptions(BeanPropertySet.java:516) ~[flow-data-2.1.8.jar:2.1.8]
at com.vaadin.flow.data.binder.BeanPropertySet.access$600(BeanPropertySet.java:48) ~[flow-data-2.1.8.jar:2.1.8]
at com.vaadin.flow.data.binder.BeanPropertySet$NestedBeanPropertyDefinition.lambda$getGetter$3ec26976$1(BeanPropertySet.java:200) ~[flow-data-2.1.8.jar:2.1.8]
at com.vaadin.flow.data.binder.Binder$BindingImpl.initFieldValue(Binder.java:1130) ~[flow-data-2.1.8.jar:2.1.8]
at com.vaadin.flow.data.binder.Binder$BindingImpl.access$200(Binder.java:972) ~[flow-data-2.1.8.jar:2.1.8]
at com.vaadin.flow.data.binder.Binder.lambda$setBean$1(Binder.java:1677) ~[flow-data-2.1.8.jar:2.1.8]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) ~[na:na]
at com.vaadin.flow.data.binder.Binder.setBean(Binder.java:1677) ~[flow-data-2.1.8.jar:2.1.8]
at cz.cvut.fit.manufacturingservices.ui.admin.view.product.category.ProductCategoryProductCategoryForm.setProductCategoryProductCategory(ProductCategoryProductCategoryForm.java:62) ~[main/:na]
at cz.cvut.fit.manufacturingservices.ui.admin.view.product.category.ProductCategoryProductCategoryView.editProductCategoryProductCategory(ProductCategoryProductCategoryView.java:64) ~[main/:na]
at cz.cvut.fit.manufacturingservices.ui.admin.view.product.category.ProductCategoryProductCategoryView.addProductCategoryProductCategory(ProductCategoryProductCategoryView.java:94) ~[main/:na]
at cz.cvut.fit.manufacturingservices.ui.admin.view.product.category.ProductCategoryProductCategoryView.lambda$getToolbar$2f54d9f7$1(ProductCategoryProductCategoryView.java:78) ~[main/:na]
at com.vaadin.flow.component.ComponentEventBus.fireEventForListener(ComponentEventBus.java:205) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.component.ComponentEventBus.handleDomEvent(ComponentEventBus.java:373) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.component.ComponentEventBus.lambda$addDomTrigger$dd1b7957$1(ComponentEventBus.java:264) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.internal.nodefeature.ElementListenerMap.lambda$fireEvent$2(ElementListenerMap.java:441) ~[flow-server-2.1.8.jar:2.1.8]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) ~[na:na]
at com.vaadin.flow.internal.nodefeature.ElementListenerMap.fireEvent(ElementListenerMap.java:441) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.server.communication.rpc.EventRpcHandler.handleNode(EventRpcHandler.java:59) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.server.communication.rpc.AbstractRpcInvocationHandler.handle(AbstractRpcInvocationHandler.java:64) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocationData(ServerRpcHandler.java:409) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.server.communication.ServerRpcHandler.lambda$handleInvocations$1(ServerRpcHandler.java:390) ~[flow-server-2.1.8.jar:2.1.8]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) ~[na:na]
at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:390) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:317) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:89) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1540) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:247) ~[flow-server-2.1.8.jar:2.1.8]
at com.vaadin.flow.spring.SpringServlet.service(SpringServlet.java:120) ~[vaadin-spring-12.1.4.jar:na]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:459) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:352) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.springframework.web.servlet.mvc.ServletForwardingController.handleRequestInternal(ServletForwardingController.java:141) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:177) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:52) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.33.jar:9.0.33]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
My composte key implmentation:我的堆肥关键实施:
@Embeddable
public class ProductCategoryProductCategoryIdentity implements Serializable {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "product_category_parent_id", nullable = false)
private ProductCategory productCategoryParent;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "product_category_child_id", nullable = false)
private ProductCategory productCategoryChild;
public ProductCategory getProductCategoryParent() {
return productCategoryParent;
}
public void setProductCategoryParent(ProductCategory productCategoryParent) {
this.productCategoryParent = productCategoryParent;
}
public ProductCategory getProductCategoryChild() {
return productCategoryChild;
}
public void setProductCategoryChild(ProductCategory productCategoryChild) {
this.productCategoryChild = productCategoryChild;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ProductCategoryProductCategoryIdentity)) return false;
ProductCategoryProductCategoryIdentity that = (ProductCategoryProductCategoryIdentity) o;
return Objects.equals(getProductCategoryParent(), that.getProductCategoryParent()) &&
Objects.equals(getProductCategoryChild(), that.getProductCategoryChild());
}
@Override
public int hashCode() {
return Objects.hash(getProductCategoryParent(), getProductCategoryChild());
}
}
Repository implmentation:存储库实现:
public interface ProductCategoryProductCategoryRepository extends JpaRepository<ProductCategoryProductCategory, ProductCategoryProductCategoryIdentity> {
}
Service implementation:服务实现:
@Service
public class ProductCategoryProductCategoryService {
private static final Logger LOGGER = Logger.getLogger(ProductCategoryProductCategory.class.getName());
@Autowired
private ProductCategoryProductCategoryRepository productCategoryProductCategoryRepository;
public List<ProductCategoryProductCategory> findAll() {
return productCategoryProductCategoryRepository.findAll();
}
public void save(ProductCategoryProductCategory productCategoryProductCategory) {
if (productCategoryProductCategory == null) {
LOGGER.log(Level.SEVERE, "Cannot save null product category decomposition");
return;
}
productCategoryProductCategoryRepository.save(productCategoryProductCategory);
}
}
Form component implementation:表单组件实现:
public class ProductCategoryProductCategoryForm extends FormLayout {
ComboBox<ProductCategory> productCategoryParent = new ComboBox<>("Parent product category");
ComboBox<ProductCategory> productCategoryChild = new ComboBox<>("Child product category");
Button save = new Button("Save");
Button close = new Button("Close");
Binder<ProductCategoryProductCategory> binder = new BeanValidationBinder<>(ProductCategoryProductCategory.class);
public ProductCategoryProductCategoryForm(List<ProductCategory> productCategories) {
addClassName("product_category-form");
productCategoryParent.setItems(productCategories);
productCategoryParent.setItemLabelGenerator(ProductCategory::getLabel);
productCategoryChild.setItems(productCategories);
productCategoryChild.setItemLabelGenerator(ProductCategory::getLabel);
//binder.bind(productCategoryParent, "id.productCategoryParent");
//binder.bind(productCategoryChild, "id.productCategoryChild");
//binder.bindInstanceFields(this);
add(productCategoryParent, productCategoryChild, createButtonsLayout());
}
private Component createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
close.addClickListener(event -> fireEvent(new CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, close);
}
public void setProductCategoryProductCategory(ProductCategoryProductCategory productCategoryProductCategory) {
binder.setBean(productCategoryProductCategory);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new SaveEvent(this, binder.getBean()));
}
}
public static abstract class ProductCategoryProductCategoryFormEvent extends ComponentEvent<ProductCategoryProductCategoryForm> {
private final ProductCategoryProductCategory productCategoryProductCategory;
protected ProductCategoryProductCategoryFormEvent(ProductCategoryProductCategoryForm source, ProductCategoryProductCategory productCategoryProductCategory) {
super(source, false);
this.productCategoryProductCategory = productCategoryProductCategory;
}
public ProductCategoryProductCategory getProductCategoryProductCategory() {
return productCategoryProductCategory;
}
}
public static class SaveEvent extends ProductCategoryProductCategoryForm.ProductCategoryProductCategoryFormEvent {
SaveEvent(ProductCategoryProductCategoryForm source, ProductCategoryProductCategory productCategoryProductCategory) {
super(source, productCategoryProductCategory);
}
}
public static class DeleteEvent extends ProductCategoryProductCategoryForm.ProductCategoryProductCategoryFormEvent {
DeleteEvent(ProductCategoryProductCategoryForm source, ProductCategoryProductCategory productCategoryProductCategory) {
super(source, productCategoryProductCategory);
}
}
public static class CloseEvent extends ProductCategoryProductCategoryForm.ProductCategoryProductCategoryFormEvent {
CloseEvent(ProductCategoryProductCategoryForm source) {
super(source, null);
}
}
public <T extends ComponentEvent<?>> Registration addListener(Class<T> eventType, ComponentEventListener<T> listener) {
return getEventBus().addListener(eventType, listener);
}
}
View implementation:查看实现:
@Route(value = "product-category-product-category", layout = MainLayout.class)
public class ProductCategoryProductCategoryView extends VerticalLayout {
private final Grid<ProductCategoryProductCategory> grid = new Grid<>(ProductCategoryProductCategory.class);
private final ProductCategoryProductCategoryForm form;
@Autowired
private ProductCategoryProductCategoryService productCategoryProductCategoryService;
public ProductCategoryProductCategoryView(ProductCategoryService productCategoryService) {
form = new ProductCategoryProductCategoryForm(productCategoryService.findAll());
}
@PostConstruct
public void init() {
addClassName("product_category-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
closeEditor();
}
private void configureForm() {
form.addListener(ProductCategoryProductCategoryForm.SaveEvent.class, this::saveProductCategoryProductCategory);
form.addListener(ProductCategoryProductCategoryForm.CloseEvent.class, e -> closeEditor());
}
private void configureGrid() {
grid.addClassName("product_category-grid");
grid.setSizeFull();
//grid.setColumns("parentId", "childId");
grid.asSingleSelect().addValueChangeListener(evn -> editProductCategoryProductCategory(evn.getValue()));
}
private void editProductCategoryProductCategory(ProductCategoryProductCategory productCategoryProductCategory) {
if (productCategoryProductCategory == null)
closeEditor();
else {
form.setProductCategoryProductCategory(productCategoryProductCategory);
form.setVisible(true);
addClassName("editing");
}
}
private void closeEditor() {
form.setProductCategoryProductCategory(null);
form.setVisible(false);
removeClassName("editing");
}
private HorizontalLayout getToolbar() {
Button addProductCategoryButton = new Button("Add category");
addProductCategoryButton.addClickListener(click -> addProductCategoryProductCategory());
HorizontalLayout toolbar = new HorizontalLayout(addProductCategoryButton);
toolbar.addClassName("toolbar");
return toolbar;
}
private Component getContent() {
Div content = new Div(grid, form);
content.setSizeFull();
content.addClassName("content");
return content;
}
private void addProductCategoryProductCategory() {
grid.asSingleSelect().clear();
editProductCategoryProductCategory(new ProductCategoryProductCategory());
}
private void updateList() {
grid.setItems(productCategoryProductCategoryService.findAll());
}
private void saveProductCategoryProductCategory(ProductCategoryProductCategoryForm.SaveEvent event) {
productCategoryProductCategoryService.save(event.getProductCategoryProductCategory());
updateList();
closeEditor();
}
}
Could you help me solve it or provide me simple entity with composed primary (foreign) key together with simple form to create that entity?您能帮我解决它还是为我提供带有组合主(外)键的简单实体以及创建该实体的简单表单?
Thank you very much.非常感谢。
The error log says that there was a NullPointerException while trying to access a bound attribute of the ProductCategoryProductCategory
instance during binder::setBean
.错误日志显示在
binder::setBean
期间尝试访问ProductCategoryProductCategory
实例的绑定属性时出现 NullPointerException。 I guess its the embeddable id thats null (without reading all the code), but you should either initialize the object with all needed values before binding, or check for null value of item.getId()
in the binding.我猜它的可嵌入 id 是 null(不阅读所有代码
item.getId()
,但您应该在绑定之前使用所有需要的值初始化 object,或者检查项目中的 null 值。
Because you have a nested property bound, I think you need to declare the binding not via property-string, so you can add null-checks for item.getId()
before calling item.getId().getProductCategoryParent()
or its setter respectively.因为你有一个嵌套的属性绑定,我认为你需要不通过属性字符串声明绑定,所以你可以在分别调用
item.getId().getProductCategoryParent()
或其设置器之前为item.getId()
添加空检查.
binder.forField(productCategoryParent)
.bind( // but if the id itself is null, don't try to get its parent
(item) -> item.getId() == null ? null : item.getId().getProductCategoryParent(),
(item, value) -> {
if(item.getId() != null){
item.getId().setProductCategoryParent(value);
}
}
);
edit: removed mentioning of nullRepresentation as its a combobox and not a textfield.编辑:删除提及 nullRepresentation 作为其 combobox 而不是文本字段。 If the productCategoryParent were null, it would work, but its the
ProductCategoryProductCategoryIdentity
that is null.如果 productCategoryParent 是 null,它会起作用,但它的
ProductCategoryProductCategoryIdentity
是 null。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.