在hybris注册流程中,当尝试在customermodel上添加一个新的强制性属性(例如pan,即个人识别码),并在注册表单中收集该值时,常常会遇到de.hybris.platform.servicelayer.exceptions.modelsavingexception: missing values for [pan]错误。这个错误表明在创建新的customermodel实例时,其强制性的pan属性没有被赋值。
即使尝试通过在items.xml中将该属性设置为optional="true"来规避上述错误,虽然可以成功注册,但Pan值却无法被存储到数据库中。这说明问题不仅仅在于属性的强制性,更在于数据从前端用户界面到后端数据模型的完整传递链条中断。核心问题在于,用户在前端表单中输入的值,没有正确地经过Hybris的MVC层级,最终映射并保存到CustomerModel实例中。
要彻底解决此问题,需要确保新添加的Pan属性值能够贯穿Hybris的整个数据流,从UI表单到最终的CustomerModel。这涉及到对以下核心组件的扩展和修改:
首先,确保在你的items.xml文件中正确定义了CustomerModel上的pan属性。如果该属性是业务必需的,则不应将其设置为optional="true"。
Customer's Permanent Account Number (PAN).
注意事项:
RegisterForm是Spring MVC用来绑定前端表单数据的Java Bean。你需要在此类中添加pan字段,以接收来自HTML表单的输入。
// Assuming your extension extends the core Hybris storefront
// e.g., mystorefront/src/com/mycompany/storefront/forms/MyRegisterForm.java
package com.mycompany.storefront.forms;
import de.hybris.platform.acceleratorstorefrontcommons.forms.RegisterForm;
import javax.validation.constraints.NotEmpty; // Example validation
public class MyRegisterForm extends RegisterForm {
@NotEmpty(message = "{register.pan.invalid}") // Add appropriate validation
private String pan;
public String getPan() {
return pan;
}
public void setPan(String pan) {
this.pan = pan;
}
}注意事项:
RegisterData是一个数据传输对象(DTO),用于在控制器和服务层之间传递注册信息。同样,需要在此DTO中添加pan字段。
// e.g., mycore/src/com/mycompany/core/data/MyRegisterData.java
package com.mycompany.core.data;
import de.hybris.platform.commercefacades.user.data.RegisterData;
public class MyRegisterData extends RegisterData {
private String pan;
public String getPan() {
return pan;
}
public void setPan(String pan) {
this.pan = pan;
}
}RegistrationPageController负责处理注册页面的请求和表单提交。你需要修改它以使用你的自定义MyRegisterForm和MyRegisterData,并确保数据从表单正确复制到DTO。
通常,你会通过Spring的@Controller注解覆盖或扩展现有的控制器。
// e.g., mystorefront/src/com/mycompany/storefront/controllers/pages/MyRegistrationPageController.java
package com.mycompany.storefront.controllers.pages;
import com.mycompany.storefront.forms.MyRegisterForm;
import com.mycompany.core.data.MyRegisterData; // Your custom RegisterData
import de.hybris.platform.acceleratorstorefrontcommons.controllers.pages.Abstract='de.hybris.platform.acceleratorstorefrontcommons.controllers.pages.AbstractRegisterPageController';
import de.hybris.platform.cms2.exceptions.CMSItemNotFoundException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.validation.BindingResult;
import org.springframework.ui.Model;
import javax.validation.Valid;
@Controller("myRegistrationPageController")
@RequestMapping(value = "/register")
public class MyRegistrationPageController extends AbstractRegisterPageController {
// You might need to override the initBinder or other methods
// to ensure MyRegisterForm is used for binding.
@RequestMapping(method = RequestMethod.POST)
public String doRegister(@Valid final MyRegisterForm form, final BindingResult bindingResult, final Model model)
throws CMSItemNotFoundException {
// If you're extending, call super.doRegister() or copy its logic
// and adapt it to use MyRegisterForm and MyRegisterData.
// Example of adapting the data transfer
final MyRegisterData registerData = new MyRegisterData();
registerData.setFirstName(form.getFirstName());
registerData.setLastName(form.getLastName());
registerData.setEmail(form.getEmail());
registerData.setPassword(form.getPassword());
registerData.setTitleCode(form.getTitleCode());
registerData.setConsentGiven(form.isConsentGiven());
// *** Crucial: Copy your new field from form to data ***
registerData.setPan(form.getPan());
// Now, pass your custom registerData to the customerFacade
// This part usually involves calling a method like register(registerData)
// on the customerFacade.
// If super.doRegister handles this, ensure it's overridden to accept MyRegisterData.
// Example of calling the facade (simplified)
// try {
// getCustomerFacade().register(registerData);
// // Handle success
// return REDIRECT_PREFIX + "/register/success";
// } catch (final DuplicateUidException e) {
// // Handle duplicate email
// bindingResult.rejectValue("email", "register.email.duplicate");
// return get Controller().register(model, form); // Return to form with error
// }
// For a full override, you'd replicate the logic from the original doRegister method,
// replacing RegisterForm with MyRegisterForm and RegisterData with MyRegisterData.
return super.doRegister(form, bindingResult, model); // If super method can be adapted
}
// You might need to override the getRegisterForm() method as well if it's used
// to instantiate the form in GET requests.
@Override
protected MyRegisterForm getRegisterForm() {
return new MyRegisterForm();
}
}注意事项:
CustomerFacade是业务逻辑层面的接口,它将RegisterData转换为CustomerModel并保存。这是将Pan值最终映射到CustomerModel的关键步骤。通常,CustomerFacade会使用一个或多个Populator来完成数据映射。
// e.g., myfacades/src/com/mycompany/facades/customer/impl/MyCustomerFacade.java
package com.mycompany.facades.customer.impl;
import com.mycompany.core.data.MyRegisterData; // Your custom RegisterData
import de.hybris.platform.commercefacades.customer.impl.DefaultCustomerFacade;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.user.UserService;
import org.springframework.beans.factory.annotation.Required;
public class MyCustomerFacade extends DefaultCustomerFacade {
private ModelService modelService;
private UserService userService; // Inject if needed
@Override
public void register(final MyRegisterData registerData) {
final CustomerModel newCustomer = modelService.create(CustomerModel.class);
// Populate common fields (first name, last name, email, password, etc.)
// You might use a populator chain here, or manually map.
// Example: Manually mapping
newCustomer.setUid(registerData.getEmail());
newCustomer.setName(registerData.getFirstName() + " " + registerData.getLastName());
newCustomer.setCustomerID(registerData.getEmail()); // Or generate unique ID
newCustomer.setSessionCurrency(getCommonI18NService().getCurrentCurrency());
newCustomer.setSessionLanguage(getCommonI18NService().getCurrentLanguage());
newCustomer.setLoginDisabled(false);
newCustomer.setEncodedPassword(getUserService().encodePassword(registerData.getPassword(), newCustomer.getSalt()));
// *** Crucial: Set your new field on the CustomerModel ***
newCustomer.setPan(registerData.getPan());
// Save the customer model
modelService.save(newCustomer);
// Optionally, authenticate the newly registered user
// getCustomerFacade().loginSuccess();
}
@Required
public void setModelService(final ModelService modelService) {
this.modelService = modelService;
}
@Required
public void setUserService(final UserService userService) {
this.userService = userService;
}
}最佳实践:使用Populator 在更复杂的场景中,推荐使用Hybris的Populator机制来映射数据。你可以创建一个新的RegisterDataToCustomerPopulator来处理MyRegisterData到CustomerModel的映射。
定义Populator接口:
// myfacades/src/com/mycompany/facades/populators/MyRegisterDataToCustomerPopulator.java package com.mycompany.facades.populators; import com.mycompany.core.data.MyRegisterData; import de.hybris.platform.converters.Populator; import de.hybris.platform.core.model.user.CustomerModel; import de.hybris.platform.servicelayer.dto.converter.ConversionException; public class MyRegisterDataToCustomerPopulator implements Populator
{ @Override public void populate(final MyRegisterData source, final CustomerModel target) throws ConversionException { // Populate common fields if this populator is the primary one // target.setUid(source.getEmail()); // target.setName(source.getFirstName() + " " + source.getLastName()); // Populate your new field target.setPan(source.getPan()); } }
在Spring配置中注册Populator并将其注入到CustomerFacade中:
注意事项:
完成上述代码修改后,需要进行以下步骤:
在Hybris中添加自定义属性并确保其数据持久化,需要对整个数据流进行端到端的管理。从items.xml定义属性,到前端表单收集数据,再到RegisterForm、RegisterData、RegistrationPageController和CustomerFacade(或其背后的Populator机制)的层层传递和映射,每一步都至关重要。遵循上述步骤,可以有效解决ModelSavingException并确保自定义属性值的正确存储。