在Hybris电商平台中,向注册页面添加自定义属性并确保其正确保存是常见需求。本文旨在解决开发者在添加新属性时遇到的ModelSavingException(缺少强制属性值)以及属性值无法持久化的问题。核心解决方案在于理解并扩展Hybris注册流程中的关键组件,包括Registerform、RegisterData、RegistrationPageController和CustomerFacade,以确保数据从前端到后端模型的完整传递与存储。
问题分析:为什么会遇到ModelSavingException和值无法保存?
当您在hybris的items.xml中为customermodel添加一个新的属性(例如pan),并将其设置为optional=”false”(即强制属性)时,如果在注册流程中没有正确地将该属性的值从前端传递并设置到customermodel实例上,系统在尝试保存customermodel时就会抛出de.hybris.platform.servicelayer.exceptions.modelsavingexception: missing values for [pan]错误。
即使您为了避免上述错误而将属性设置为optional=”true”,虽然错误不再出现,但您会发现该属性的值并未被保存到数据库中。这表明仅仅在items.xml中定义属性和在jsp页面上添加输入框是不足够的,Hybris的注册流程需要一个完整的数据流转机制来处理新的字段。
解决方案核心:扩展Hybris注册流程的数据流
Hybris的注册流程是一个多层架构,数据从用户界面(ui)经过控制器(Controller)、数据传输对象(DTO)、门面(Facade)最终到达服务层(Service)和数据模型(Model)。要成功添加并保存自定义属性,必须在数据流的每个关键节点进行相应的扩展。
具体而言,您需要修改或扩展以下核心组件:
- items.xml: 定义新的属性。
- RegisterForm: 接收前端表单提交的数据。
- RegisterData: 在控制器和门面层之间传递数据。
- RegistrationPageController: 处理表单提交并映射数据。
- CustomerFacade: 将数据从DTO映射到CustomerModel并触发保存。
逐步实现自定义属性的集成与持久化
1. items.xml 中定义新属性
首先,在您的Hybris扩展的items.xml文件中,为Customer类型添加新的属性。例如,添加一个名为pan的字符串类型属性:
<items xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="items.xsd"> <itemtypes> <itemtype code="Customer" autocreate="false" generate="false"> <attributes> <attribute qualifier="pan" type="java.lang.String"> <description>PAN card number for the customer</description> <modifiers optional="false" initial="false" unique="false"/> <!-- 如果该字段是强制的,保持 optional="false";否则设置为 "true" --> </attribute> </attributes> </itemtype> </itemtypes> </items>
注意: 如果pan字段是强制的,请保持optional=”false”。后续步骤将确保其值被正确传递,从而避免ModelSavingException。
完成items.xml修改后,请务必执行ant all并更新系统(update running system)。
2. 扩展 RegisterForm
RegisterForm是用于绑定注册页面表单数据的POJO类。您需要扩展它以包含新的pan字段。
// 在您的核心或自定义扩展中创建或修改 // 例如:yourstorefront/web/src/com/yourcompany/storefront/forms/YourRegisterForm.java package com.yourcompany.storefront.forms; import de.hybris.platform.acceleratorstorefrontcommons.forms.RegisterForm; // 导入Hybris的RegisterForm public class YourRegisterForm extends RegisterForm { // 继承Hybris的RegisterForm private String pan; public String getPan() { return pan; } public void setPan(String pan) { this.pan = pan; } // 您可以添加验证注解,例如 @NotEmpty, @Pattern 等 }
3. 扩展 RegisterData
RegisterData是用于在Web层和Facade层之间传递注册信息的DTO。它也需要包含pan字段。
// 在您的核心或自定义扩展中创建或修改 // 例如:yourcore/src/com/yourcompany/core/data/YourRegisterData.java package com.yourcompany.core.data; import de.hybris.platform.commercefacades.user.data.RegisterData; // 导入Hybris的RegisterData public class YourRegisterData extends RegisterData { // 继承Hybris的RegisterData private String pan; public String getPan() { return pan; } public void setPan(String pan) { this.pan = pan; } }
4. 扩展 RegistrationPageController
RegistrationPageController负责处理注册表单的提交。在这里,您需要将YourRegisterForm中的pan值映射到YourRegisterData中。通常,您会覆盖或扩展Hybris提供的默认控制器。
// 在您的storefront扩展中创建或修改 // 例如:yourstorefront/web/src/com/yourcompany/storefront/controllers/pages/YourRegistrationPageController.java package com.yourcompany.storefront.controllers.pages; import com.yourcompany.core.data.YourRegisterData; import com.yourcompany.storefront.forms.YourRegisterForm; import de.hybris.platform.acceleratorstorefrontcommons.controllers.pages.Abstract='yourstorefront'.controllers.pages.RegisterPageController; // 导入Hybris的RegisterPageController import de.hybris.platform.acceleratorstorefrontcommons.forms.RegisterForm; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import javax.validation.Valid; @Controller @RequestMapping(value = "/register") public class YourRegistrationPageController extends RegisterPageController { // 覆盖默认的注册方法,使用您的自定义表单和数据对象 @RequestMapping(method = RequestMethod.POST) public String doRegister(@Valid final YourRegisterForm form, final BindingResult bindingResult, final Model model) throws CMSItemNotFoundException { // ... (其他验证和逻辑,通常继承自父类) if (bindingResult.hasErrors()) { // 处理验证错误 return getViewForPage(model); // 返回注册页面 } final YourRegisterData registerData = new YourRegisterData(); // 使用ModelMapper或手动复制属性 // 示例:手动复制,推荐使用ModelMapper或其他BeanUtils registerData.setFirstName(form.getFirstName()); registerData.setLastName(form.getLastName()); registerData.setLogin(form.getEmail()); registerData.setPassword(form.getPwd()); registerData.setTitleCode(form.getTitleCode()); registerData.setPan(form.getPan()); // <-- 关键:将pan从表单复制到数据对象 try { getCustomerFacade().register(registerData); // 调用CustomerFacade进行注册 } catch (final DuplicateUidException e) { // 处理重复用户ID异常 bindingResult.rejectValue("email", "registration.error.account.exists.title"); return getViewForPage(model); } // ... (注册成功后的逻辑,例如自动登录,重定向) return redIRECT_PREFIX + get==''/register/success'; } }
注意: 您可能需要配置Spring来使用YourRegisterForm而不是默认的RegisterForm。这通常通过Spring的@Controller注解和@RequestMapping来实现,或者在Spring配置中覆盖默认的RegisterPageController Bean。
5. 扩展 CustomerFacade
这是最关键的一步,负责将RegisterData中的pan值映射到CustomerModel实例上,并调用服务层进行持久化。您需要创建一个新的CustomerFacade实现或扩展现有的实现。
// 在您的core扩展中创建或修改 // 例如:yourcore/src/com/yourcompany/core/facades/customer/impl/YourCustomerFacadeImpl.java package com.yourcompany.core.facades.customer.impl; import com.yourcompany.core.data.YourRegisterData; import de.hybris.platform.commercefacades.user.impl.DefaultCustomerFacade; // 导入Hybris的DefaultCustomerFacade import de.hybris.platform.core.model.user.CustomerModel; import de.hybris.platform.servicelayer.model.ModelService; import org.springframework.beans.factory.annotation.Required; public class YourCustomerFacadeImpl extends DefaultCustomerFacade { private ModelService modelService; // 注入ModelService @Override public void register(final RegisterData registerData) throws DuplicateUidException { // 确保传入的是您的YourRegisterData类型 if (!(registerData instanceof YourRegisterData)) { super.register(registerData); // 如果不是,调用父类方法处理 return; } final YourRegisterData yourRegisterData = (YourRegisterData) registerData; // 核心逻辑:在创建CustomerModel之前或之后设置pan值 final CustomerModel customerModel = getModelService().create(CustomerModel.class); customerModel.setUid(yourRegisterData.getLogin()); customerModel.setName(yourRegisterData.getFirstName() + " " + yourRegisterData.getLastName()); customerModel.setSessionCurrency(getCommonI18NService().getCurrentCurrency()); customerModel.setSessionLanguage(getCommonI18NService().getCurrentLanguage()); customerModel.setPan(yourRegisterData.getPan()); // <-- 关键:设置pan值到CustomerModel // 设置其他标准属性(通常在父类或通过BeanUtils复制) populateCustomerModel(yourRegisterData, customerModel); getModelService().save(customerModel); // 保存CustomerModel // ... (其他注册后的逻辑,例如自动登录) } // 也可以选择覆盖populateCustomerModel方法,将pan字段映射进去 @Override protected void populateCustomerModel(final RegisterData registerData, final CustomerModel customerModel) { super.populateCustomerModel(registerData, customerModel); // 调用父类方法处理通用属性 if (registerData instanceof YourRegisterData) { final YourRegisterData yourRegisterData = (YourRegisterData) registerData; customerModel.setPan(yourRegisterData.getPan()); // 确保pan被设置 } } @Required public void setModelService(final ModelService modelService) { this.modelService = modelService; } }
注意: 您需要在Spring配置文件中将默认的customerFacade Bean替换为您的YourCustomerFacadeImpl。
<!-- yourcore-spring.xml 或其他Spring配置文件 --> <bean id="customerFacade" class="com.yourcompany.core.facades.customer.impl.YourCustomerFacadeImpl" parent="defaultCustomerFacade"> <property name="modelService" ref="modelService"/> <!-- 注入其他依赖 --> </bean>
注意事项与最佳实践
- 避免直接修改核心代码: 始终通过继承和覆盖(或使用Spring的别名和重定义)来扩展Hybris的默认行为,而不是直接修改Hybris OOTB(Out Of The Box)代码。
- 依赖注入: 确保所有自定义组件都通过Spring正确地注入了其依赖项(例如ModelService)。
- 数据验证: 在RegisterForm中使用JSR 303/349注解(如@NotEmpty, @Size, @Pattern)进行前端验证,并在业务逻辑层(Facade或Service)进行更复杂的业务规则验证。
- 错误处理: 妥善处理可能出现的异常,如重复用户ID、数据格式错误等,并向用户提供友好的反馈。
- 更新系统: 每次修改items.xml后,务必执行ant all并更新正在运行的系统,以确保数据库模式和类型系统同步。
- 测试: 在开发过程中,编写单元测试和集成测试来验证您的自定义属性是否正确地从前端传递到后端并持久化。
总结
在Hybris注册页面添加自定义属性并实现其持久化,不仅仅是在items.xml中定义字段和在JSP页面上添加输入框那么简单。它要求开发者深入理解Hybris的数据流机制,并对RegisterForm、RegisterData、RegistrationPageController和CustomerFacade等关键组件进行恰当的扩展。通过遵循本文提供的步骤和最佳实践,您可以有效地解决ModelSavingException和数据无法保存的问题,确保自定义属性在Hybris平台中得到完整且正确的处理。