Service Layer is a layer of services on top of the persistence layer.
Characteristics of the ServiceLayer
- Based on service-oriented architecture.
- Provides a clean separation of business logic and persistence logic.
- Provides a number of services, each with its well-defined responsibilities.
- Provides a framework to develop your own services and to extend existing ones.
- Based on the Spring Framework.
- Based on common patterns, such as interface-oriented design and dependency injection.
- Provides hooks into model life-cycle events for performing custom logic.
- Provides hooks into system life-cycle events such as init and update process.
- Provides a framework for publishing and receiving events.
ServiceLayer Architecture
Client
A client is any software component that uses the ServiceLayer, such as:
- Page Controllers of an MVC framework
- Web Service clients
- Scripts
- Other services
Services
A service holds the logic to perform business processes.
Services only contain functional logic and no persistence-related code. That means a service has to be implemented as loosely coupled to the persistence layer as possible.
SAP Hybris Commerce exposes all of its functionality through services.
Extensions must provide their functionality as services.
Services may use other services to perform their tasks but should keep their interdependencies to a minimum to avoid overly tight coupling with other components.
Hybris recommends implementing services in terms of interfaces.
Services interact with other components through models.
Strategies
A service may delegate parts of its tasks to smaller micro-services, called strategies. The service then serves as a kind of facade to the strategies.
Clients still use the service and its stable API. But under the hood the functionality is split into multiple parts.
DAOs
A DAO (Data Access Object) is an interface to the storage back end system. DAOs store and retrieve objects. You use DAOs to save, remove, and find models.
DAOs are the place to put SQL or FlexibleSearch statements. This is to ensure further decoupling from the underlying storage facility.
DAOs interact with services via models and with the database via FlexibleSearch and SQL statements.
DAOs use the Hybris Type System for persistence. This means that DAOs do not implement any individual logic and simply call the underlying persistence layer.
Models
Models are a way to represent Hybris items. Each model contains all item attributes from all extensions thus unifying access to an item's data.
Models are generated from the type system of SAP Hybris Commerce. Furthermore they are simple POJOs (Plain Old Java Objects) that can be used without any storage facility.
Models are used by DAOs, services, strategies, converters, and facades.
Basic Model Aspects
There are two basic aspects of Models, depending on the phase of SAP Hybris Commerce:
- Model class generation, that is, at compile time.
- Model life cycle, that is, at run time.
Model Class Generation
During a Hybris build, the build framework generates Model classes and configuration files for each item type. Models are generated across all extensions, no matter whether the extension is available in source code or in binary only. The Model generation process also ignores the value of the generated attribute of the <coremodule> element in extensioninfo.xml file of the extension.
Models are generated into the bootstrap/gensrc directory.
Why Are Models Generated into the gensrc directory?
This is due to the way the Hybris Build Framework operates.
For example, let's take the cms extension. In this extension, the Catalog type is extended with an attribute called Store. The Storetype is defined in the cms extension but the Catalog type is defined in the catalog extension. In the build order of Hybris, the catalogextension precedes the cms extension. Therefore, the catalog extension is unaware of the attributes defined in the cms extension.
By consequence, putting the generated CatalogModel into the catalog extension causes a build failure: CatalogModel has an attribute of type StoreModel that is defined in the cms extension but has to be available in the catalog extension already.
For every type, an individual Model class is generated. A Model owns the following:
- The name of the type from which the Model is generated, plus the suffix Model. For example, the Model for the Product type has the name ProductModel.
- A similar package as the type from which the Model was generated :
- The string model is added to the package after the extension root, and jalo is eliminated from the package. For example, de.hybris.platform.europe1.jalo.TaxRow has the Model de.hybris.platform.europe1.model.TaxRowModel.
- All attributes that the type has, represented as private fields
- Getter and setter methods for all attributes
As Models are generated during a very early phase in the Hybris build process - before actually building any extension - the Models are available by compile time.
As Models are generated to match the type system definitions in the items.xml files, you can use them to check the data model you have defined. This is faster than updating or initializing the Hybris system and checking the data model in HMC.
Modifying the Model Generation
Attributes for Models are generated automatically by default based on attributes of the type, including getter and setter methods for those attributes. You can explicitly exclude attributes of a type from the generation process, or exclude entire types from the generation so that no Model is created for the type.
- To exclude an entire type (including all subtypes) from Model generation:
<itemtype
generate="true"
code="ContactRequest"
... >
<model generate="false"/>
<attributes>...</attributes>
</itemtype>
- To exclude an attribute from Model generation, you have to explicitly define the exclusion. The result is that neither the private field nor getter and setter methods are generated.
<attribute qualifier="message" type="java.lang.String">
<persistence type="property"/>
<model generate="false"/>
</attribute>
You can specify a constructor to be generated by specifying the attributes part of the constructor signature:
<itemtype
generate="true"
code="ContactRequest"
... >
<model>
<constructor signature="message"/>
</model>
<attributes>...</attributes>
</itemtype>
This results in a constructor at the model:
public ContactRequestModel(final String _message) {
super();
setMessage(_message);
}
You can add alternative getter and setter methods for an attribute:
<attribute qualifier="message" type="java.lang.String">
<persistence type="property"/>
<model>
<getter name="myMessage"/>
</model>
</attribute>
An additional getter method getMyMessage() is generated for the corresponding Model:
public String getMessage() {
...
}
public String getMyMessage() {
return this.getMessage();
}
You can replace the original getter and setter methods by using default flag:
<attribute qualifier="message" type="java.lang.String">
<persistence type="property"/>
<model/>
<getter name="myMessage" default="true"/>
</model>
</attribute>
As the result, getMyMessage method is generated and the original getMessage method does not exist in the Model anymore.
public String getMyMessage() {
// now executes logic of former getMessage() method
...
}
Another option is to mark alternative getter and setter methods as deprecated:
<attribute qualifier="message" type="java.lang.String">
<persistence type="property"/>
<model>
<getter name="myMessage" default="deprecated"/>
</model>
</attribute>
The generated getMyMessage method in the Model has @deprecated annotation:
public String getMessage() {
...
}
@Deprecated
public String getMyMessage() {
return this.getMessage();
}
Model Life Cycle
A Model represents a state in the database.
The representation is not live, that means that modified Model values are not written to the database automatically. Instead, when you modify a Model, you must explicitly save it to the database to have its state reflected there.
The phases in a Model's life cycle include:
- Instantiating the Model: This can be done by either creating a new Model instance or by loading a Model from the database.
- Creating a Model instance: This can be done in either of these ways.
- Through its constructor.
- Through the factory method in the ModelService.
- Loading an existing Model from the database: This can be done by
- Using the pk
- Using a query expression
- Using an example Model as search parameter
- Modifying Model Values: Set the properties of a Model.
- Saving Model Values if created or modified: You save back the Model to update the database. If you have used a new Model, a new record is created in the database; otherwise the existing record is updated.
- Removing the Model: If the Model is no longer needed, the database record is deleted.
You can use Interceptors to hook into the Model's life cycle.
Lazy Loading
Lazy loading is the process of not setting all values of an object right away on the object's instantiation. The Model loading mechanism uses lazy loading.
When a Model is loaded, no Model values are loaded right away. The Model consists only of a blank Java object instance with no values set. All Model values are only loaded when any value of the Model is retrieved.
To change the loading mechanism, set the servicelayer.prefetch property in your local.properties file to all or literal.
'literal' : pre-fetching only atomic attribute values and not pre-fetching reference attribute values.
'all' : all attribute values are pre-fetched upon model loading.
Since the relationships of a Model are loaded on demand, in certain circumstances you can avoid calling their getter and setter methods.
Model Context
As soon as you load a Model or create it through the modelService, it is put in the Model context. The Model context keeps track of all changes to the Model.
If you choose to save a Model individually, only unsaved Models are automatically saved. Models that are already created are not saved.
For example, let us assume you have a CategoryModel that holds a reference to a ProductModel. You modify the CategoryModel:
- If the ProductModel is created and not yet saved, then saving the CategoryModel saves the ProductModel as well. This is because the reference to the ProductModel is new and new references are always saved.
- If the ProductModel is already saved, then saving the CategoryModel does not save the ProductModel. This is because the reference to the ProductModel points to an existing Model, and existing Models are not saved.
Because the Model context keeps track of your changes, it is possible to save all changes at once. You do not have to save each Model separately. You can save all Models at once.
If you create a Model by using its constructor, the Model is not attached to the Model context.
You can manually modify the Model context in order to:
- Add a Model to the context:
modelService.attach(model)
- Remove a Model from the Model context, if you do not want your modifications to be saved automatically:
modelService.detach(model)
Model context is bind to the HybrisRequestScope, which is similar to the standard request scope but belongs to a single thread only.
The Model context is automatically cleared if the related session or the related request is closed or times out. Hence unsaved Models are removed and cannot pose a threat of memory leak.
The Model created or modified during request is stored in the context. If you will load this kind of model from database, for example by using flexible search, you get the same instance of the Model that is stored in the context. You must be aware that this behavior is not guaranteed for loaded but unmodified Models. If you try to display the same object twice without modifying it, you usually get the same object but you can not depend on this. Better keep reference to this object in your own code.
The Model context is thread local and Models are not thread safe. You should synchronize access to the setters if you are passing your Models to different threads, for example by using SessionService.
You should not use the Model context after transaction rollback. If you try to use Models saved during a transaction which is rolled back, you may get an exception. It happens because Models can be in an inconsistent state with their database representation.
ModelService
The ModelService is a service that deals with all aspects of a Model's life-cycle. It is available in Spring under the ID modelService and implements the de.hybris.platform.servicelayer.model.ModelService interface. Its main tasks include the following:
- Loading Models by pk
- Loading Models from items
- Creating Models
- Updating Models
- Deleting Models
Creating a Model Instance
There are two ways to create a new Model instance:
- Using a constructor
- Using a factory method
Using a Constructor
You do not need a special create method or other kind of factory. You simply create the new instance using new:
ProductModel product = new ProductModel();
Values of Models are not written to the database directly but only on explicit save. Due to this, you do not have to specify values for mandatory attributes when instantiating the Model. However, by the time you try to save the Model, the values for mandatory attributes must be set, except for the default values.
Model Constructor Methods
Constructor methods for mandatory attributes are deprecated.
To instantiate Models, use only the non-argument constructor method, and set the values afterwards:
ProductModel product = new ProductModel();
product.setCatalogVersion(catalogVersion);
product.setCode(code);
You can use constructor defined at items.xml.
When you create a Model like this, it is not attached to the Model context. There are two ways to do it:
- Using the ModelService 's save(Object) method: The Model is saved to the database and automatically attached.
modelService.save(Object)
- Using the ModelService 's attach(Object) method: The Model is attached but not saved.
modelService.attach(Object)
A Model created that way is not filled with default values as defined in the items.xml file. You can fill it as follows:
modelService.initDefaults(model);
If you do not explicitly call it, the default values are automatically applied during the save process.
Using a Factory Method
You also can use the ModelService to create a Model instance, by specifying the Model class:
ProductModel product = modelService.create(ProductModel.class)
Or, you can specify the type's identifier (code):
ProductModel product = modelService.create("Product")
This is very useful at runtime if you dynamically wish to determine the type of a Model to create. Also, this method immediately puts the Model in the Model context and the default values are assigned automatically.
Loading an Existing Model
To load an existing Model,
- Using the Model pk
- Using a FlexibleSearch query
- Using an example Model as search parameter
Loading by Primary Key
To load a Model based on its primary key (pk), you call the get method from ModelService:
ProductModel product = modelService.get(pk)
Loading by Query Expression
To look up Models based on a FlexibleSearch query, use the flexibleSearchService. It implements the de.hybris.platform.servicelayer.search.FlexibleSearchService interface. It is available as a Spring bean with the ID flexibleSearchService:
FlexibleSearchQuery query = new FlexibleSearchQuery(
"SELECT {pk} FROM {Product} WHERE {code}=?" + Product.CODE);
query.addQueryParameter(Product.CODE, code);
SearchResult<ProductModel> result = flexibleSearchService.search(query);
List<ProductModel> = result.getResult();
If no Model is found, search() method may throw ModelNotFoundException.
You may use searchUnique() method from the FlexibleSearchService that is similar to search() method. The difference is that searchUnique() method returns exactly one model or throws one of two types of exceptions:
- ModelNotFoundException: If no Model is found
- AmbiguousIdentifierException: If more than one Model fulfilling search parameters is found
Loading by Example Model
Instead of searching for an existing Model using a FlexibleSearch query, you can create a new example Model, change its attributes and search by this example for an existing Model in the system. Essentially, a Model of the same kind with matching values is returned.
Loading by an example Model is done by FlexibleSearchService and is divided into two methods: getModelByExample() and getModelsByExample().
To search existing Models using an example Model:
- Create a new Model.
- Change its attributes to match the values you want to search for.
- When you expect only one result, call the flexibleSearchService.getModelByExample(...) method, passing the Model.
- When you expect more than one result, call the flexibleSearchService.getModelsByExample(...) method, passing the Model.
getModelByExample() method can throw two kinds of exception:
- ModelNotFoundException: If no Model is found
- AmbiguousIdentifierException: If more than one search result is found
To avoid getting AmbiguousIdentifierException, use getModelsByExample() method
create() Method vs new Operator
If you create an example Model using the modelService.create() method, the Init Defaults Interceptor of this Model is called. As a consequence, default values of the Model are set and used as search parameters.
If you create an example Model using new ProductModel(), no default values are set and only specified values of parameters are used for search.
To search for localized attributes, attach the example Model to the ModelContext. Without it, no LocaleProvider is set, which would result in java.lang.IllegalStateException: got no locale provider - cannot access default localized getters and setters.
ProductModel exampleProduct = new ProductModel();
exampleProduct.setName("uniqueName_deutsch", Locale.GERMAN);
exampleProduct.setName("uniqueName_english", Locale.ENGLISH);
modelService.attach(exampleProduct); // <- important
Saving a Model
There are two basic means of saving Models:
- Saving an Individual Model with Referenced Models
To save a Model, call the modelService 's save(...) method:
modelService.save(model);
If a Model holds a reference to another Model and is to be saved, the referenced Models are also saved if they have not been saved before. The referenced Models that have already been saved before are not saved. Other, non-referenced Models are not saved.
For example, if a catalog version holds a new, unsaved CategoryModel and the catalog version is saved, then the CategoryModel is also saved. This function relies on the Model context.
- Saving all Models at Once
modelService.saveAll();
This saves all modifications as registered with the Model context.
Collections
There is a special behavior when using collections. You cannot simply get a collection-based attribute of a Model, modify the collection's contents and call the ModelService 's save(...) method. The getter methods of Models return unmodifiable lists, so you cannot modify the collection. Instead, you have to:
- Create a new collection object.
- Add existing, non-modified values.
- Add new or modified values.
- Set the collection to the attribute.
- Store the attribute by calling the save(...) for the Model.
Removing a Model
To remove a Model, call the remove method of the modelService:
modelService.remove(product)
Refresh a Model
modelService.refresh(product)
Refreshing retrieves the Model's values directly from the database, thus overriding the current values. Therefore unsaved changes are lost.
Converting Between Models and SAP Hybris Commerce Items
In some cases, you need to convert a Model into a Hybris item, or vice versa. That is, you might need to switch from a ServiceLayer-based data Model to a Jalo Layer-based data Model, or vice versa.
- Converting a Model to an Item
Sometimes you need to get access to the underlying item of a Model, for example to perform some logic only available in the item class and not yet ported to a service. To convert a Model to an item, use the getSource(...) method of the modelService:
Product productItem = modelService.getSource(productModel)
- Converting an Item to a Model
Sometimes, instead of using a Model, you get access to an item only. This typically occurs if legacy, Jalo Layer-based code is involved. To make use of this item in your service layer related code, you have to convert the item to a Model. Use the special get method in the modelService that takes an item as parameter and returns a Model, such as:
final Cart cart = JaloSession.getCurrentSession().getCart();
final CartModel result = modelService().get(cart);
return result;
Defining Enums for Models
Models can optionally use Java Enums. That way, you can pre-define potential values for the Model's attributes.
To define an Enum value for a Model:
- Define the enum values in the items.xml file
<enumtypes>
<enumtype code="ArticleApprovalStatus" autocreate="true" generate="true">
<value code="check"/>
<value code="approved"/>
<value code="unapproved"/>
</enumtype>
<enumtypes>
- Define an attribute of the enum type:
<attribute qualifier="approvalStatus" type="ArticleApprovalStatus">
<modifiers read="true" write="true" search="true" optional="false"/>
<persistence type="property"/>
</attribute>
- Trigger the Model generation. The Model is generated with getter and setter methods for the enum:
public ArticleApprovalStatus getApprovalStatus() {
if( !isAttributeLoaded(APPROVALSTATUS)) {
this._approvalStatus = (ArticleApprovalStatus) loadAttribute(APPROVALSTATUS);
}
throwLoadingError(APPROVALSTATUS);
return this._approvalStatus;
}
public Map getArticleStatus() {
return getLocalizedValue(this._articleStatus, ARTICLESTATUS, getCurrentLocale());
}
public Map getArticleStatus(final Locale loc) {
return getLocalizedValue(this._articleStatus, ARTICLESTATUS, loc);
}
public void setApprovalStatus(final ArticleApprovalStatus value) {
this._approvalStatus = value;
markDirty(APPROVALSTATUS);
}
public void setArticleStatus(final Mapvalue) {
setLocalizedValue( this._articleStatus,ARTICLESTATUS, getCurrentLocale(), value );
}
public void setArticleStatus(final Mapvalue, final Locale loc) {
setLocalizedValue( this._articleStatus,ARTICLESTATUS, loc, value );
}
Interceptors
When the life cycle of a model reaches a certain step, a corresponding interceptor is activated. During the interception, it is possible to modify the model or raise an exception to interrupt the step.
Implement an Interceptor
To implement an interceptor you have to implement one of the following interfaces:
Load Interceptor
Called whenever a model is loaded from the database.
Used to change values of the model after load.
An exception raised during execution prevents the model from being loaded.
public interface LoadInterceptor extends Interceptor {
void onLoad(Object model, InterceptorContext ctx)
throws InterceptorException;
}
Init Defaults Interceptor
Called when a model is filled with its default values.
This happens either when it is created via the modelService.create method or when the modelService.initDefaults method is called.
Used to fill the model with additional default values, apart from the values defined in the items.xml file.
public interface InitDefaultsInterceptor extends Interceptor {
void onInitDefaults(Object model, InterceptorContext ctx)
throws InterceptorException;
}
Prepare Interceptor
Called before a model is saved to the database and before it is validated by Validate interceptors.
Use this to add values to the model or modify existing ones before they are saved.
An exception raised during execution prevents the model from being saved.
public interface PrepareInterceptor extends Interceptor {
void onPrepare(Object model, InterceptorContext ctx)
throws InterceptorException;
}
Do not use this interceptor to perform validation. Use the Validate Interceptor instead.
Validate Interceptor
Called before a model is saved to the database after been prepared by Prepare interceptors.
Used to validate values of the model and to raise an InterceptorException if any values are not valid.
public interface ValidateInterceptor extends Interceptor {
void onValidate(Object model, InterceptorContext ctx)
throws InterceptorException;
}
Remove Interceptor
Called before a model is removed from the database.
Used to remove models that are related to the model but are not in the model context.
public interface RemoveInterceptor extends Interceptor {
void onRemove(Object model, InterceptorContext ctx)
throws InterceptorException;
}
Apart from the model that is being intercepted, a second argument is passed to the interceptor method, the interceptor context. It provides information about other models in the current context as well as useful utility methods.
Register an Interceptor
After implementing an interceptor, register it as a Spring bean.
- Add the interceptor to the Spring application context XML:
myextension-spring.xml
<bean id="myValidateInterceptor"
class="mypackage.MyValidateInterceptor" autowire="byName"/>
- Add a de.hybris.platform.servicelayer.interceptor.impl.InterceptorMapping to the XML file:
myextension-spring.xml
<bean id="MyValidateInterceptorMapping"
class="de.hybris.platform.servicelayer.interceptor.impl.InterceptorMapping">
<property name="interceptor" ref="myValidateInterceptor"/>
<property name="typeCode" value="MyType"/>
<property name="replacedInterceptors" ref="uniqueCatalogItemValidator"/>
<property name="order" value="5000"/>
</bean>
interceptor : A reference to the actual interceptor.
typeCode : The code of the type to intercept. All subtypes are also intercepted.
order : (Optional) The running order of all registered Interceptors for a given typecode and its subtypes can be ordered by this property. The Interceptor with the lowest number is called first. If this property is not set, the highest integer number is used by default. Any Hybris Interceptor has no order number set by default. If two or more interceptors have the same order value (or no order value), then the exact order in which these interceptors trigger is not deterministic.
replacedInterceptors : (Optional) A list of Interceptor bean id's (not the corresponding <InterceptorMapping> id) which are replaced by this current Interceptor.
Interceptor Enhancements
Prepare and Remove interceptors can both register models and specify a PersistenceOperation to be performed with these models.
PersistenceOperation is an enumeration with the following values:
From the interceptor point of view, the most important changes took place in the InterceptorContext interface. There are a few new methods:
- void registerElementFor(final Object model, PersistenceOperation operation);
- boolean contains(Object model, PersistenceOperation operation);
- Set<Object> getElementsRegisteredFor(PersistenceOperation operation);
These methods allow an interceptor to:
- register a model (specifying operation to be performed),
- check if the given model is already registered using the given operation,
- get all models registered for the given operation.
To keep backward compatibility, and the behavior of existing methods without the <operation> parameter, InterceptorContext always has a <default operation>, which depends on the scenario in which the interceptor is executed.
If the entry point to modelService was one of save* methods, the default operation for InterceptorContext is <SAVE>.
Accordingly, if the entry point to modelService is one of the remove* methods, the default operation for InterceptorContext is<DELETE>.
Disabling Interceptors
To disable interceptors in a session,
- Disable interceptors programmatically by using the sessionService class
- Disable interceptors declaratively via ImpEx import.
Disable Interceptors Programmatically
To disable interceptors in code, use the sessionService.executeInLocalViewWithParams method.
It is an implementation of a template method pattern and takes a map of attributes that are set in a session before execution of the provided callback method, and reverted afterwards.
There are three attributes that allow you to disable specific interceptors during execution of callback:
- disable.interceptor.beans (InterceptorExecutionPolicy#DISABLED_INTERCEPTOR_BEANS constant) : This attribute takes a set of Spring bean IDs.
- disable.interceptor.types (InterceptorExecutionPolicy#DISABLED_INTERCEPTOR_TYPES constant) : This attribute takes a set of interceptor types (de.hybris.platform.servicelayer.interceptor.impl.InterceptorExecutionPolicy.DisabledType) that you want to disable.
- disable.UniqueAttributesValidator.for.types (InterceptorExecutionPolicy#DISABLED_UNIQUE_ATTRIBUTE_VALIDATOR_FOR_ITEM_TYPES) : This attribute takes a set of item types for which you want to disable UniqueAttributesValidator.
The following example of a currency model has the out-of-the box defined ValidateInterceptor. This interceptor checks if a currency digit is not a negative number. If it is, the following code fails:
final CurrencyModel currency = modelService.create(CurrencyModel.class);
currency.setSymbol("$");
currency.setIsocode("USD");
currency.setDigits(-1);
modelService.save(currency); // throws ModelSavingException caused by InterceptorException
To disable all validate interceptors
final Map<String, Object> params =
ImmutableMap.of(InterceptorExecutionPolicy.DISABLED_INTERCEPTOR_TYPES,
ImmutableSet.of(InterceptorExecutionPolicy.DisabledType.VALIDATE));
sessionService.executeInLocalViewWithParams(params, new SessionExecutionBody() {
@Override
public void executeWithoutResult() {
final CurrencyModel currency = modelService.create(CurrencyModel.class);
currency.setSymbol("$");
currency.setIsocode("Dollar");
currency.setDigits(-1);
modelService.save(currency); // save successful - all validate interceptors are disabled
}
});
To disable specific interceptors on a per-bean basis:
final Map<String, Object> params =
ImmutableMap.of(InterceptorExecutionPolicy.DISABLED_INTERCEPTOR_BEANS,
ImmutableSet.of("validateCurrencyDataInterceptor"));
sessionService.executeInLocalViewWithParams(params, new SessionExecutionBody() {
@Override
public void executeWithoutResult() {
final CurrencyModel currency = modelService.create(CurrencyModel.class);
currency.setSymbol("$");
currency.setIsocode("Dollar");
currency.setDigits(-1);
modelService.save(currency); // save successful - validateCurrencyDataInterceptor interceptor is disabled
}
});
In case we know that there is no Currency with the Dollar IsoCode, we can disable UniqueAttributesValidator for the Currency type:
final Map<String, Object> params =
ImmutableMap.of(InterceptorExecutionPolicy.DISABLED_UNIQUE_ATTRIBUTE_VALIDATOR_FOR_ITEM_TYPES,
ImmutableSet.of("Currency"));
sessionService.executeInLocalViewWithParams(params, new SessionExecutionBody() {
@Override
public void executeWithoutResult() {
final CurrencyModel currency = modelService.create(CurrencyModel.class);
currency.setSymbol("$");
currency.setIsocode("Dollar");
currency.setDigits(-1);
modelService.save(currency); // save successful - UniqueAttributesValidator not called
}
});
Disable Interceptors via ImpEx
To disable all validator type interceptors, use the disable.interceptor.types=validate header attribute:
INSERT_UPDATE Currency[disable.interceptor.types=validate];isocode[unique=true];symbol;digits;
;EUR_Test2;$;-2;
To disable specific interceptors, specify a comma-separated bean-IDs list for the disable.interceptor.beans header attribute.
INSERT_UPDATE Currency[disable.interceptor.beans='validateCurrencyDataInterceptor'];isocode[unique=true];symbol;digits;
;EUR_Test;$;-2;
To disable UniqueAttributesValidator, specify a comma-separated item types for which you want to disable it:
INSERT_UPDATE Currency[disable.UniqueAttributesValidator.for.types='Currency'];isocode[unique=true];symbol;digits;
;EUR_Test;$;-2;