Maximo business objects (MBOs)
- The initValue() method is the first method executed after the constructor.
- Typically used to initialize attributes on new records and to set default values.
- The init() method is called after initValue(). Currently used to set current attribute as read-only based on some condition. Set related attributes as read-only.
- Setting of field flag should not be done in the init() of mbo. It should be done in the initFieldFlagsonMbo():
- Consider performing logging, in the initFieldFlagsOnMbo() method.
- The logic of setting the readonly and required field flags to the MBO.
- Any setting of the field flags that require expensive operation, such as database query should be moved from init() to this method. However, those which don't require steps such as fetching Mbos can be left in init().
- You can group the logic for a few attributes together if logic of deciding their flag is the same, or in other words, can be handled at once.
- Don't fetch MBOs in the init() method.
- If MBO needs to be fetched, do it in initFieldFlagsOnMbo().
- Doing this could avoid fetching additional MBOs when this current MBO being called. This could also improve the performance.
- Consider performing logging, in the initFieldFlagsOnMbo() method.
- Constructing a MBO is costly
- If you have a MBO available to use, do not refetch the MBO.
- Don't implement a cache that holds on to MBOs, use smaller data structure.
- Use the information that already resides in memory whenever possible, avoid getting new MBO from the database.
- Using the same relationship from the same MBO will get you the same MBO set already created.
- Pass the Mbo/MboRemote as parameters to the methods.
- Use BulletinBoardService to communicate information among objects within the execution of the same thread if passing parameter is not possible.
- The constructor should only be used to call super and set some instance variables.
- Should never execute methods that could call exceptions.
- Don't ever use a MBO or mbo set class as a event Listener, it will never get garbage collected.
- Don't ever put a mbo/set as static anywhere.
MboSets
- If you only need to process records that are already instantiated in the MboSet use MboSet.getSize to loop through the records instead of count.
- Avoid using the MboSet method count. It hits the database and can take a long time.
- To see if a MboSet is empty use the MboSet method isEmpty(), do not use count().
- To see if there's only one record in the set, use:
- ((set.getMbo(0) != null) && (set.getMbo(1) == null)).
- If you want to visit every member of a set, use code like:
*EXAMPLE
- If you only want to check to see if a set has been modified, use Mbo method getInstanciatedMboSet.
- If the set hasn't been read in yet, this method returns null, so you know no possible changes could have occurred:
EXAMPLE
- If the set hasn't been read in yet, this method returns null, so you know no possible changes could have occurred:
- Close the MboSet if you are certain you will not use it anymore.
- Use set NOSAVE flag to true, if MboSet obtaining is for readonly purpose. Then it won't be added to transaction.
- If a MboSet is used for traversing forward only and not to be saved use DISCARDABLE MboSet.
- The children of a discardable MBO is NOSAVE, change NOSAVE flag back to true.
- Or don't use relationship, directly create set using a composed where clause.
- SAVE - Don't call save in business logic code.
- Exceptions are transactions that don't need to be rolled back.
- Think Rollback when calling save.
- The modification of a MboSet that hasn't been saved can be obtained from the same relationship from the same parent, if you use a different relationship you won't see the change.
- If two MboSets contain MBOs pointing to the same record in the database and you modify both you will get "Record has been updated by another user". That is why you should use the same relationship to obtain the record.
- Avoid repeating logic, reuse MboSets and MBOs
- Pay extra attention to the logic inside a loop.
- When implement lower level method, understand how it is to be used. Avoid expensive operations that would become repeated when the method is used.
Field validations and actions
NOTE: Even though the validation and action logic can be put in either method, by convention the validation method should only do validation and the action method should only do business logic. Do not perform validation in the action method.
- Field validation gets invoked when tabbing out of a field, or when a field is being set from the backend.
- When the setValue method is invoked for an attribute the framework accesses the data dictionary table MAXATTRIBUTE to determine if the attribute has a validation class associated with it.
- Typical order of execution: initValue(), init(), validate(), action(), hasList(), getList().
- initValue() - initializes fields for new records
- init() - sets read-only fields
- Once a validation class has been determined, the validate() will be executed. The validation method can be customized to verify whether the new value for the attribute is valid.
- An action() method may then be executed. Action methods contain business logic and updates to other objects.
- hasList() - returns true if there is a list.
- getList() - creates MboSet for list.
- MEA transactions also invoke field validation for all fields it sets at the very end.
- MEA - Use getValidateOrder() to specify the attribute validate order
- In case MEA performs delay validation, use this method to specify the validate order.
- Register validation classes, by defining the class name in the class field.
- If a class is already defined, then you can extend the existing class
- Using Access Flags
- The access flags are defined in the MboConstants interface. Implement psdi.mbo.MboConstants to use the constants without using a prefix with them.
- Some of the more commonly used flags: NOACCESSCHECK, NOVALIDATION, DELAYVALIDATION, NOACTION, NOVALIDATION_AND_NOACTION.
NOTE: It is important to set the appropriate flags to help increase Maximo performance.
- DOMAINS - MAXTableDomain, CrossOverDomain, ALNDomain, SynonymDomain, NumericDomain
- Use Domains and Crossover Domains as much as possible
- If unachievable then use FldClass
- Understand all flags when developing field class.
- Avoid doing validation if it's not needed.
- Conditional Domain Value
- ALNDomain/SynonymDomain/NumericDomain
- Conditional Cross Over Domain (various flags - copy don't copy, etc)
- Domain and FldClass could co-exist.
- Consolidate Domain and FldClass if possible
- Validation occurs for both.
- Everything in the Field Class will be executed except for the getList(). The list will come from the domain that is defined. If a domain is not defined the list will be fetched from the field class.
- Consolidate Domain and FldClass if possible
- MaxLookupmap - key mapping for lookup and goto app.
- Better to be implemented in table and not in fldclass.
- Avoid Yes-No-Cancel Dialog, it's memory intensive.
- Understand User-Interactive.
- Conditional Domain Value
- Field Flags (ex. Readonly)
- Do not implement costly field flags in initvalue or init of MBO.
- Implement in mbo.initFieldFlagsonMbo
- Non-persisistent attributes
- Population of value should be defined in that fields initvalue, not in MBO's init.
- When using MAXTABLEDOMAINS - getList() gets called every time.
- Do criterias in the constructor if it's fixed and not dynamic.
- Two classes can be associated to an attribute. MboValue and Validation Class.
- The MboValue class contains the attribute value and all the attribute properties defined in the data dictionary.
- Some of the more commonly used methods are listed:
- getString(): Gets attribute value as String
- getInt(), getFloat, getDouble(): Get value as int, float, ...
- getColumnTitle: Gets the title of the column
- getInitialValue(): Gets initial value
- getMbo(): Gets the MBO associated with the attribute
- setValue(): Sets the value of the attribute
- setValueNull(): Sets the value null
- isNull(): True if attribute value is null
- isReadOnly(): True if attribute is read-only
Relationships
- A whereclause generated by a relationship is only a portion of the whereclause generated, there are other parts to the clause such as security.
- Make sure a relationship is not defined in the database before adding a new one.
- Don't modify an existing relationship. Create a new one.
- Everything obtained via a relationship is included in the same transaction. When any MboSet in the transaction is saved, all MboSets in that same transaction will be saved.
- Don't use a relationship if you want a brand new transaction, instead get the set from MXServer.
Example: The following gets the complete Work Order table which you can further narrow the set by applying a where clause.
\\ WOSetRemote mboSet = (WOSetRemote) MXServer.getMXServer().getMboSet("WORKORDER", getUserInfo());
General
- When using a private method, consider writing another public method to call this private method. So outside objects can extend from this private method if needed.
- If a set is filtered from the UI using setQbe, the user should be aware that if they attempt an action using the getThisMboSet(), they will only be getting the filtered set, not the complete set.
- When writing code, do not forget that there are possibilities that the code could be called from MEA or other objects that don't involve the UI.
- When executing scripts, the order of execution is first DBC script and then JAVA.
- If you want to implement an interface, you need to implement each and every of its abstract classes. Otherwise your class will remain abstract as well.
- Do not use System.out.println
- Avoid implementing static variable and class
- Don't use synchronized class if not needed
- Don't use toUpperCase() or toLowerCase() liberally, pass parameters in the right case.
- Use StringBuilder(StringBuffer if need synchronization) instead of String += in a loop.
- Formatter classes are expensive, directly use the object instead of converting to string and converting back.
- Consider fetching a MBO independent of the MBO used in a while loop, which may have been fetched via a relationship. This helps release MBOs and free up memory.
- Consider using the DISCARDABLE flag to enable the release of MBOs quicker. Garabage collection usually cleans up the left over MBOs and mboSets, but it could be slower than what you need. Use discardable when a MboSet is used for traversing forward only and is not to be saved.
- Consider using getMXTransation().save(): If processing a large set of records, consider saving each transaction instead of waiting for the entire set in the while loop to perform transactions. This will get the currently active transaction for the object. If there is no current transaction, a new one is created for you.
- What may also help release memory is fetching a new database connection. This helps avoid extensive instantiation of MBOs in the memory:
Keeping costs low
- Improve the database query for the MboSet
- A not well tuned Sql query is generally the biggest contributor to elapsed time taken by business logic
- Don't make the table too wide
- Using normalized tables, especially if the tables can be separated out by different functions, such as can be displayed on a different tab or dialogs on UI, can be beneficial
- Keep MBO's init() light
- Don't query DB in init() at all and move expensive logic of setting MBO or field flags to initFieldFlagsOnMbo()
- In init of field A, don't initialize non-persistent field B.
- Field B should initialize itself in its own initValue()
Common performance problems
- Repeated queries, too many queries, and redundant queries
- Use the existing relationship, use getSharedMboSet() if applicable
- Consider passing parameter(s) or caching
- Don't issue queries in Mbo.init()
- Don't call reset/save unnecessarily
- If the method is a method that can be called repeatedly, don't simply issue the query, think alternative solutions
- Lacking Indexes
- Don't assume the record set will be small, have strategies for unexpected
- Test against large database
- Use explain plan or other tool to help with the query and indexes
- Analyze the possible queries to the table, create appropriate indexes
- Not able to use indexes.
- If the field can be UPPER, don't define it as ALN
- Avoid using QBE in the code, use setWhere() to prevent leading %
- Define TEXT search type on description like columns
- Avoid using "not in" (not to be confused with not in ('abc', 'efg')) and !=
- Avoid selects and sub-selects that only using columns with few distinct values
- Keep in mind that result can be different on different DBs
- Statements too complex
- Use tools such as Optim query tuner to help with writing the query and create index
- Consider using multiple queries
- Query returns too many records, not enough qualifiers
- Re-examine functionality, rewrite the query
- Provide a default filter for the application list page and lookup
- Find alternative app flow to use more selective columns in the query
- Reduce the records returned in subqueries
- Example of SQL which performs differently based on the database platform
- Query result may be different on different DB platform, but in majority of cases, they are similar.
- Costs shown are relative to the DB, don't literally compare between platforms.
- The "not in" is much worse than "in".
Considerations when defining indexes
- Do not index columns that are modified frequently (like rowstamp)
- Consider indexing columns that are used frequently to join tables in SQL statements
- Don't index the column that have very few distinct values, consider composite index to increase the selectivity, especially if their combined selectivity is better than the selectivity of either column individually
- Create the index so the keys used in WHERE clauses make up a leading portion.
- If index is on columns A, B, C in this order, query that only uses A and C will not use the index effectively; query that uses A and B will use the index effectively.
- If all keys are used in the SQLs equally often choose the most selective columns first in the index
- If a column in a composite index allows null, make sure a not null column precedes it in the index
- Beware of the cost of having indexes, don't define too many of them, drop unused ones
- This is to reduce overhead on inserts and updates
- Start with the frequently executed queries and most expensive when creating indexes
- Seek help from DB tools and subject experts
Database performance
- Keep the transaction short
- Avoid overwrite MboSet.saveTransaction()
- Avoid using event listener after save
- If you have to do the above, please keep the logic minimum
- Use NOSAVE MboSet to keep the sets in MXTransaction small, thus shortens the looping time for the transaction
- Close a MboSet immediately if you know you can close it
- Keep the cursor open in some DB can cause locking or even dead locking
- If isEmpty() is called, and you know it is possible that there are more than 2 records in the set, and you know you are not going to reuse the set, close it. (Maximo fetches 1 record further, and will close the set automatically if all the records are fetched.)
- Don't design very wide tables
- Select * is costly when the table is wide
- Use views or sub-select with proper index defined is not slower
- Providing default Order by only if the result set is small enough
- Order by is very costly, especially on large result set. Even though maximo may not fetch all, the cost on DB side is high
Other practices affecting performance
- Care should be taken when using Yes/No/Cancel dialog
- Yes/No/Cancel is expensive because the logic repeats
- Call the logic of deciding whether to throw yes/no/cancel as early as possible in the logic
- Know when to use MboSet.count() and MboSet.getSize()
- MboSet.count() will most likely hit the Database, MboSet.getSize() only give you how many has been loaded in memory
- Use MboSet.getSize() if what you are going to process are already instantiated as Mbos in memory.
- Don't use save() in the business logic code unless for separate transactions
- Transaction atomicity
- Save invokes commits, which resets the mboset. If you get child mboset from this MBO again, all its related sets will be re-instantiated by re-querying the DB.
- When logging, always check the logging level (don't check when logging error)
if (logger.isWarnEnabled())
logger.warn();
logger.warn();
- There is added cost when using conditional UI and conditional sigoption, use them wisely, do not over-do it
- When defining maximo condition, evaluate the cost of complex expression that will rely on DB query to evaluate
- Consider using a java class if complicated logic is involved
- Share maximo condition if possible to reduce the evaluation time
- Condition result is cached for each MBO
- Don't choose always evaluate if the business logic doesn't require so
- String operations such as toUpperCase() is very expensive
- Don't use toUpperCase() or toLowerCase() liberally, passing parameters in the right case
- StringBuilder(StringBuffer if need synchronization) should be used instead of String for string concatenation in a looping logic.
- Formatter classes are expensive, directly use the object instead of converting to string and converting back.
Considerations when designing
- Consider redesigning the look and feel and the functionality to improve usability and performance, for example, show information from different tables on different tabs or dialogs
- Consider whenever possible to introduce default filters to the list page. It is hard to add the filter after the client is accustomed to and facing performance problem when their data grows.
- Consider providing warning when performance threshold is met. (e.g. don't wait until the user is reporting 5,000 labor reported to the same workorder.)
- Consider providing support for using background async job when the operation is anticipated to be slow (AsyncJobHandler and AsyncJobSubmitter)
- Offer alternative solutions such as crontasks to handle slow and resource intensive operations.
- Weigh among non-persistent field, related field, and crossed over persistent field and using views. Each has its pros and cons, views generally offer the most benefit if can be used
- Provide purge functionality for transactional history tables to control the size
Example
Redundant Logic
Correction of Redundant Logic which makes a big difference
No comments:
Post a Comment