Tuesday, October 27, 2015

Corrective Action for Hibernate


An undo functionality is necessary for all systems. When customer calls for a bad data
that he created he is not willing to cancel it. He wants to delete all evidences
from system.

Think we are creating a purchase order. We can cancel order. But user wants to delete
created products ,shipment and payment terms...

So what can we do is refer to article for "Event Loggging".
We need to find the session id of main object.(Purchase Order)
And find all the objects related with it.
Then delete.

Right?
What about updates done to Purchase Order?

So we need a field on our beans that creates and updates our beans.
Open a field concatenate every session id that affected your bean.
1st item will be creator sessionid,others will be updaters.

Then a query delete all related items will be like :

SessionId Concatanted : 12,34,46
12 : creator session 34,46 updaters

delete from tableX where sessionid in (12,34,46);
delete from tableY where sessionid in (12,34,46);
delete from tableZ where sessionid in (12,34,46);

Code for this part is easy if you read other thread.
It is important to get the idea.

Of course one can do this with JDBC. We only need to have unique number(or string)
for every transaction we are doing.

Here I say session usually. Because our sessions are single transaction and atomic.
So while reading you can realize to call this transaction.It depends on your model.

Using GWT BaseModelData object for infinitive flexibility



We have implemented a project with GXT( a GWT framework) and Hibernate.

It has a class like this.
com.extjs.gxt.ui.client.data.BaseModelData

Idea is put fields into Hashmap and read them by key. This object goes between
server and browser.

public User (

 protected Organization organization;
 protected String name;
 protected String surname;
 
 }
 
 //will be 

public UserDao extends  BaseModelData( 
 
    //Utility methods for explicit access
    public Organization getOrganization( ) 
    {
        return this.< Organization >get( "organization" ) ;
    }

    public String getName( ) 
    {
        return this.< String >get( "name" ) ;
    }
    
    public String getSurname( ) 
    {
        return this.< String >get( "surname" ) ;
    }
    
    


So what is so flexible about this?

We found this very useful when creating a sample bean.
We have everything in map. So for organization field we can even put
an organization or List.

If you check Database querying by Object notation article you will understand this is for.

User sample = new User();
user.set( "organization",new Organization[]{org1,org2,org3});
user.set( "name",new Organization[]{"John","Jack"});

I can send this sample Object and interpret this into a query where
Bring users in any of organizations (org1,org2,org3) having name either John or Jack.

Object obj = sample.get("organization");

if( obj instanceof Organization )
{

}else if( obj instanceof List)
{

}

Think, how will you make this without this model?

Hibernate Audit By keeping order of change


There are lots of samples in internet creating audit data for hibernate with Interceptors.

With these samples (maybe I missed some of them) you do not know the order of changes.
We needed to know the order for some reasons.

Luckily our hibernate code was generated with a tool we wrote. We put a method as 1st
line in our setter methods. We were calling a method to understand if this field is changed.
(By this way u can also get full path (stacktrace) of change )

public class User {
 public void setPersonel( YPerson personel )
    {
        this.setEditedFieldValue( "personel", this.personel, personel, true ) ; 
        this.personel = personel ;
    }
 
 public void setCode( String code )
    {
        this.setEditedFieldValue( "code", this.code, code, true ) ; 
        this.code = code ;
    }
 
 }

 



Below is the generic code to check if field is changed.
There are cases like
1)Object created with new,we are assigning necessary fields (You do not need to keep ,
because objects former values are null before creation so if an object does not have any audit data,it is same from beginning)
2)Object is being read from database ( We are attaching object so all values will seem null,be careful)
Hibernate creates object with null constructor and calls setters.
3)Object attached , in the flow we changed a field.


protected void setEditedFieldValue( String fieldName, Object oldValue, Object newValue, boolean isNullable )
    {
        //Beans can override isIntercepted method to skip being audited
        if( !this.isIntercepted( ) )
        {
            return ;
        }
        
        //Do not need to audit initially null ones, or you can skip at time of attaching
        if( oldValue == null )
        {
            return ;
        }

        //for not null int values our code has 0. So if something is not nullable (we know at time of code generation )
        // but it is 0 it means null for us return without auditing
        if( !isNullable )
        {
            if( ( oldValue instanceof Integer ) && ( ( Integer )oldValue ).intValue( ) == 0 )
            {
                return ;
            }

            if( ( oldValue instanceof BigDecimal ) && ( ( BigDecimal )oldValue ).compareTo( BigDecimal.ZERO ) == 0 )
            {
                return ;
            }
        }

        //Do not audit if value did not change,
        //Example : You opened edit page, change some values and some not.So audit only changed ones.
        if( oldValue == newValue )
        {
            return ;
        }

        

        if( oldValue instanceof YBean )
        {
            String oldDisp = "[N/A]" ;
            String newDisp = "[N/A]" ;

            if( YServerConstants.IS_AUDIT_DEBUG_MODE )
            {
                //want to know when if beans are proxy or no.
                    if( HibernateUtils.isproxy( ( YBean )oldValue ) )
                    {
                        oldDisp = "##BeanProxy##;" + oldValue.getClass( ).getSimpleName( ) + "; id:" + ( ( YBean )oldValue ).getId( ) ;
                    }
                    else
                    {
                        oldDisp = "##YBean##;" + ( ( YBean )oldValue ).getInstanceBeanName( ) + "; id:" + ( ( YBean )oldValue ).getId( ) + "; disp:" + ( ( YBean )oldValue ).getShortDisplay( ) ;
                    }

                    if( newValue != null )
                    {
                        if( HibernateUtils.isproxy( ( YBean )newValue ) )
                        {
                            newDisp = "##BeanProxy##;" + newValue.getClass( ).getSimpleName( ) + "; id:" + ( ( YBean )newValue ).getId( ) ;
                        }
                        else
                        {
                            newDisp = "##YBean##;" + ( ( YBean )newValue ).getInstanceBeanName( ) + "; id:" + ( ( YBean )newValue ).getId( ) + "; disp:" + ( ( YBean )newValue ).getShortDisplay( ) ;
                        }
                    }
                

                if( !oldDisp.equals( newDisp ) )
                {
                    if( this._editedFieldValuesPool == null )
                    {
                        this._editedFieldValuesPool = new YAuditFieldNameValuePairPool( ) ;
                    }

                    this._editedFieldValuesPool.setFieldValue( fieldName, oldDisp ) ;
                }
            }
        }
        else
        {
            //We do not know if any of objects are null so can not call , o1.equals(02) 
            if( isObjectEquals( oldValue, newValue ) )
            {
                return ;
            }

            if( this._editedFieldValuesPool == null )
            {
                this._editedFieldValuesPool = new YAuditFieldNameValuePairPool( ) ;
            }

            this._editedFieldValuesPool.setFieldValue( fieldName, oldValue ) ;
        }
    }
    
    
    public static boolean isObjectEquals( Object o1, Object o2 )
    {
        if( o1 == o2 )
        {
            return true ;
        }

        if( o1 == null || o2 == null )
        {
            return false ;
        }

        return o1.equals( o2 ) ;
    }

    
    
    //This class is holding audit data. Since audits are hold  at Arraylist you do not loose order.
    //Also you can even add stack traces with conditions to monitor your system.
    
    
    
    
public class YAuditFieldNameValuePairPool implements Serializable
{
    protected ArrayList<String> nameList = new ArrayList<String>( 0 ) ;

    protected ArrayList<Object> valueList = new ArrayList<Object>( 0 ) ;
    
    
    public int setFieldValue( String fieldName, Object value )
    {
        boolean isFound = false ;

        //check If you have same record do not save again
        //This is a decision you can think this is usefull to see if
        //a change is being applied mutiple time.
        for( int i = this.nameList.size( ) - 1; i >= 0; --i )
        {
            String name = this.nameList.get( i ) ;
            Object val = this.valueList.get( i ) ;

            if( name.equals( fieldName ) )
            {
                if( YClientUtils.isObjectEquals( value, val ) )
                {
                    isFound = true ;
                    break ;
                }
            }
        }

        if( !isFound )
        {
            this.nameList.add( fieldName ) ;
            this.valueList.add( value ) ;

            return this.nameList.size( ) - 1 ;
        }

        return -1 ;
    }

An alternative to Hibernate Criteria and HQL



An alternative to Hibernate Criteria, Stay in object in notation for querying database,Express your query with object relations.

*** Below logic is like ,
"org.hibernate.criterion.Example"
But more advance and also good for omni searches in all fields of beans.(String fields)

When you use hibernate Example you still define projections,criteria...
Below notation is pure object notation.



We can use HQL or criteria api for querying database. This is hibernate and we are dealing with objects.
We taught why not query database by objects.(Probably lots of people did this by time
when they advance in hibernate)
For example :
I want users with name John having organization 'ABC Company'

hql = ' Select dao FROM User dao where dao.name like '%John%' and dao.organization.name like '%ABC Company%' "

Criteria


Full Objecte Notation
Create detached object representing what you look for
User sample = new User();
sample.setName("John");

Organization org = new Organization();
org.setName("ABC Company");
sample.setOrg(org);

If you have builder pattern

(New User()).setName("John").getOrganization().setName("ABC Company");

This is abusing Hibernate or Just going up one level in Object notation.
Express your query with object relations.

Of course this is translated into HQL or Criteria for querying. We wrote both versions. They both seemed very nice to us.
We love our this piece of code so much :) We wrote this at 2009. At that time there was nothing like this(as we search).
Maybe now there is.

If ultimate goal of Hibernate is to stay in Object notation and ease of use, I think a notation like this
must be alternative like HQL or Criteria.


Below code enables deep search on multiple beans and fields also.

--- Find relations for this user
--- Hql equivalent = " Select dao FROM YRelUserHorg dao where dao.user.id = this.id "
YRelUserHorg beanExample = new YRelUserHorg( ) ;
beanExample.setUser( this ) ;
List beanList = HibernateUtils2.execOmni( null, beanExample, "%", 0, null ) ;

--Discussion is a self referencing table
--Find replies to this thread
--- Hql equivalent = " Select dao FROM YDiscussion dao where dao.reply.id = " + this.mainDiscussion.getId( ) ;
YDiscussion sample = new YDiscussion( ) ;
YDiscussion reply = new YDiscussion( ) ;
reply.setId( this.mainDiscussion.getId( ) ) ;
sample.setReply( reply ) ;

List beanList = HibernateUtils2.execOmni( null, sample, null, null, null ) ;

--- This is a nice one,u have a bean with a sub bean say u want to search "xyz" on any files
--- In our database everyone working in company is Person and system users are User so, there is a person field in User
--- Hql equivalent = " Select dao FROM YUser dao
-- where dao.title like '%xyz%' or dao.address1 like '%xyz%' or dao.person.name like '%xyz%' or dao.person.surname like '%xyz%' .... think this is
much more(and it really is)
YUser beanExample = new YUser( ) ;
beanExample.setPersonel( new YPerson( ) ) ;

List beanList = HibernateUtils2.execOmni( null, beanExample, "%" + query + "%", 0, this.loadUserItemCount ) ;

--- users having role admin(an object refernce attached object) and having John in any of fields
YRelUserHorg beanExample = new YRelUserHorg( ) ;
beanExample.setRole( adminRole ) ;
bean.setUser(new YUser());


List beanList = HibernateUtils2.execOmni( null, beanExample, "%John%", 0, null ) ;


Of course this code is not full and needs more features but it's talents are really impressive.
Deep searching,and staying at Object notation makes us feel doing something elegant. Because we express our need
with object notation. we can change implementation any time(even to JDBC).

@Transient
    @Override
    public YRelOrganizationOrganization getCriteriaTemplate( String key )
    {
        if( HibernateUtils2.TKEY_MYSUPPLIERS.equals( key ) )
        {
            YRelOrganizationOrganization roo = new YRelOrganizationOrganization( ) ;
            roo.setRelationType( DRelOrganizationOrganization.TYPE_BUYER_SUPPLIER ) ;
            roo.setOtherOrganization( new YOrganization( ) ) ;

            return roo ;
        }
                YRelOrganizationOrganization beanExample = new YRelOrganizationOrganization( ) ;
        beanExample.setStatus( 1 );
        //beanExample.setOtherOrganization( new YOrganization( ) );

        //List<Integer> beanList = HibernateUtils2.execOmni( HibernateUtils2.TKEY_MYSUPPLIERS, beanExample, query + "%", 0, 30, "otherOrganization.interchangeUser.personel.name", SortOrder.ASCENDING ) ;
        List<Integer> beanList = HibernateUtils2.execOmni( HibernateUtils2.TKEY_MYSUPPLIERS, beanExample, query + "%", 0, 30, "otherOrganization.name", SortOrder.ASCENDING ) ;

        
            
            
            
                        if( "String".equals( simpleTypeName ) )
            {
                Object val = subBean.getFieldValue( fieldName ) ;

                if( subBean.query_isForceIncludedField( fieldName ) || ( ( val == null && !IGNORE_IN_SEARCH.equals( val ) ) && !subBean.query_isForceExcludedField( fieldName ) ) )
                {
                    disjunction.add( Restrictions.like( ( alias != null ? alias + "." : "" ) + fieldName, omni ) ) ;
                }
            }
            
                        if( "String".equals( simpleTypeName ) )
            {
                if( subBean.getFieldValue( fieldName ) != null )
                {
                    String val = ( String )subBean.getFieldValue( fieldName ) ;

                    if( subBean.query_isForceIncludedField( fieldName ) || ( ( !YClientUtils.isBlankTrim( val ) && !IGNORE_IN_SEARCH.equals( val ) ) && !subBean.query_isForceExcludedField( fieldName ) ) )
                    {
                        criteria.add( Restrictions.like( ( alias != null ? alias + "." : "" ) + fieldName, val ) ) ;
                    }
                }
            }
            


Logic : we can have template beans (like you are using a type of user select query so much ,so create a template) and also can send an override beanExample
Think like this,
You have a template of users having type Manager this is templateBean
Then you want ones with name Max, you send a sample with name Max.

You can also use this with null template and a sample with both name Max and type Manager












public static List<Integer> execOmni( String key, YBean beanSend, String somnis, Integer firstResult, Integer maxResult, String sort, SortOrder order, boolean isForceOrgId )
    {

        String omnis = YClientUtils.notNullTrim( somnis, "" ) ;

        if( "%%".equals( omnis ) )
        {
            omnis = "" ;
        }

        YBean templateBean = key == null ? null : beanSend.getCriteriaTemplate( key ) ;

        if( templateBean == null )
        {
            templateBean = beanSend ;
        }

        HashMap<String, Criteria> aliasSet = new HashMap<String, Criteria>( ) ;

        Criteria criteria = HibernateManager.getSession( ).createCriteria( beanSend.getClass( ) ) ;

        // Some beans are only readable by their related organizations so a default organization is added every query
        // Some beans are shared like product. A buyer can see supplier product so isForceOrgId is for omitting this.
        if( isForceOrgId && !YServerSession.isTopAdmin( ) )
        {
            if( !( beanSend instanceof YOrganization ) && !( beanSend instanceof YCatalogDetail ) )
            {
                String organizationField = getOrganizationField( beanSend ) ;
                if( organizationField != null )
                {
                    criteria.add( Restrictions.eq( organizationField + ".id", YServerSession.getSession( ).getOrganizationId( ) ) ) ;
                }
            }
        }

        String[ ] beanName = beanSend.getBeanNames( ) ;

        //ADD EXACT VALUES OF BOTH IF THEY ARE NOT EQUAL
        addExactValues( criteria, templateBean, null ) ;
        if( !beanSend.equals( templateBean ) )
        {
            addExactValues( criteria, beanSend, null ) ;
        }

        //Check sub beans ,and if they have value add exact values there
        // (new User()).getOrganization().setName("ABC");
        for( String s : beanName )
        {
            YBean subBean = ( YBean )beanSend.getFieldValue( s ) ;
            //-------------- ADD  EXACT VALUES FROM SEND BEAN
            if( subBean != null )
            {
                String subAlias = "dao_" + s + "_" ;
                if( aliasSet.get( subAlias ) == null )
                {
                    Criteria sub = criteria.createCriteria( s, subAlias, CriteriaSpecification.LEFT_JOIN ) ;
                    aliasSet.put( subAlias, sub ) ;
                }

                addExactValues( criteria, subBean, subAlias ) ;
            }

            //-------------- ADD  EXACT VALUES FROM TEMPLATE BEAN
            subBean = ( YBean )templateBean.getFieldValue( s ) ;
            if( subBean != null )
            {
                String subAlias = "dao_" + s + "_" ;
                if( aliasSet.get( subAlias ) == null )
                {
                    Criteria sub = criteria.createCriteria( s, subAlias, CriteriaSpecification.LEFT_JOIN ) ;
                    aliasSet.put( subAlias, sub ) ;
                }

                addExactValues( criteria, subBean, subAlias ) ;
            }
        }

        // Send your omni query it will be AND or ORs
        //like u write "Europe Max "
        // ( a like '%Europe%' OR b like '%Europe%' ) AND ( a like '%Max%' OR b like '%Max%' ) 
        if( omnis != null && !YClientUtils.isBlankTrim( omnis ) )
        {
            String[ ] splits = omnis.split( " " ) ;

            for( int i = 0; i < splits.length; i++ )
            {
                String split = splits[ i ] ;

                if( YClientUtils.isBlankTrim( split ) )
                    continue ;

                split = split.trim( ) ;

                if( !split.startsWith( "%" ) )
                    split = "%" + split ;

                if( !split.endsWith( "%" ) )
                    split = split + "%" ;

                Disjunction disjunction = Restrictions.disjunction( ) ;

                criteria.add( disjunction ) ;

                setOmniValues( disjunction, templateBean, split, null ) ;
                setOmniValues( disjunction, beanSend, split, null ) ;

                //Do this for also not null sub beans ( going deeper !!! )
                for( String s : beanName )
                {
                    YBean subBean = ( YBean )beanSend.getFieldValue( s ) ;
                    if( subBean != null )
                    {
                        String subAlias = "dao_" + s + "_" ;
                        setOmniValues( disjunction, subBean, split, subAlias ) ;
                    }
                    //----------------------
                    subBean = ( YBean )templateBean.getFieldValue( s ) ;
                    if( subBean != null )
                    {
                        String subAlias = "dao_" + s + "_" ;
                        setOmniValues( disjunction, subBean, split, subAlias ) ;
                    }
                }
            }
        }

        
        String criteriaProjection = beanSend.getCriteriaProjection( key ) ;

        //You can specify what to return, id is default, object is not returned for performance,u can still
        //load all of these objects batch ,never load objects from a query, 100000 results not rare!
        if( !YClientUtils.isBlankTrim( criteriaProjection ) )
        {
            criteria.setProjection( Projections.projectionList( ).add( Projections.property( criteriaProjection ), "id" ) ) ;
        }
        else
        {
            criteria.setProjection( Projections.projectionList( ).add( Projections.property( "id" ), "id" ) ) ;
        }

        //criteria.addOrder( Order.asc( "id" ) ) ;

        if( YClientUtils.isBlankTrim( sort ) )
        {
            criteria.addOrder( Order.desc( "id" ) ) ;
        }
        else
        {
            String realSortPart = sort ;

            if( sort.indexOf( "." ) > -1 )
            {
                String[ ] chainBeans = realSortPart.split( "\\." ) ;

                //If sort path not present in query create it,
                //Example you wanted only users with name John ,
                // (new User()).setName("John")
                //But want to sort by organization name
                // sort = user.deparment.organization
                // so this path must be created if not already exists
                
                for( int z = 0; z < chainBeans.length - 1; z++ )
                {
                    String subAlias = "dao_" + chainBeans[ z ] + "_" ;

                    if( aliasSet.get( subAlias ) != null )
                    {
                        continue ;
                    }

                    Criteria parent = z == 0 ? criteria : aliasSet.get( "dao_" + chainBeans[ z - 1 ] + "_" ) ;

                    Criteria lastCriteria = parent.createCriteria( chainBeans[ z ], subAlias, CriteriaSpecification.LEFT_JOIN ) ;

                    aliasSet.put( subAlias, lastCriteria ) ;

                }

                String subAlias = "dao_" + chainBeans[ chainBeans.length - 2 ] + "_" ;

                if( SortOrder.ASCENDING.equals( order ) )
                {
                    criteria.addOrder( Order.asc( subAlias + "." + chainBeans[ chainBeans.length - 1 ] ) ) ;
                }
                else
                {
                    criteria.addOrder( Order.desc( subAlias + "." + chainBeans[ chainBeans.length - 1 ] ) ) ;
                }
            }
            else
            {
                if( SortOrder.ASCENDING.equals( order ) )
                {
                    criteria.addOrder( Order.asc( sort ) ) ;
                }
                else
                {
                    criteria.addOrder( Order.desc( sort ) ) ;
                }

            }

        }

        if( firstResult != null )
        {
            criteria.setFirstResult( firstResult ) ;
        }

        if( maxResult != null )
        {
            criteria.setMaxResults( maxResult ) ;
        }

        criteria.setCacheable( true ) ;

        List<Integer> results = criteria.list( ) ;

        return results ;

    }
    
    protected static void addExactValues( Criteria criteria, YBean subBean, String alias )
    {
        if( subBean.getId( ) != null && subBean.getId( ).intValue( ) > 0 )
        {
            criteria.add( Restrictions.eq( ( alias != null ? alias + "." : "" ) + "id", subBean.getId( ) ) ) ;
            return ;
        }

        String[ ] fieldNames = subBean.getFieldNames( ) ;

        for( String fieldName : fieldNames )
        {
            String simpleTypeName = ReflectionUtils.getField( subBean.getClass( ), fieldName ).getType( ).getSimpleName( ) ;

            if( "String".equals( simpleTypeName ) )
            {
                if( subBean.getFieldValue( fieldName ) != null )
                {
                    String val = ( String )subBean.getFieldValue( fieldName ) ;

                    if( subBean.query_isForceIncludedField( fieldName ) || ( ( !YClientUtils.isBlankTrim( val ) && !IGNORE_IN_SEARCH.equals( val ) ) && !subBean.query_isForceExcludedField( fieldName ) ) )
                    {
                        criteria.add( Restrictions.like( ( alias != null ? alias + "." : "" ) + fieldName, val ) ) ;
                    }
                }
            }

            if( "int".equals( simpleTypeName ) || "Integer".equals( simpleTypeName ) )
            {
                if( subBean.getFieldValue( fieldName ) != null )
                {
                    Integer ival = ( ( Integer )subBean.getFieldValue( fieldName ) ) ;
                    if( subBean.query_isForceIncludedField( fieldName ) || ( ival != null && ival.intValue( ) > 0 && !subBean.query_isForceExcludedField( fieldName ) ) )
                    {
                        criteria.add( Restrictions.eq( ( alias != null ? alias + "." : "" ) + fieldName, subBean.getFieldValue( fieldName ) ) ) ;
                    }
                }
            }
        }

    } ..............................
    //query_isForceIncludedField  means , we are checking if a value
    protected static void setOmniValues( Disjunction disjunction, YBean subBean, String omni, String alias )
    {
        //This is for sample (unpersistent) beans 
        // Say u created a sample like
        // (new User()).setOrganization( Yorganization.getById(1))
        // you want all users with organization 1 but since organization is attached to session
        //All of its non-lazy values are readen. we are dealing with those ones so omit them
        
        if( subBean.getId( ) != null && subBean.getId( ).intValue( ) > 0 )
        {
            return ;
        }

        String[ ] fieldNames = subBean.getFieldNames( ) ;

        for( String fieldName : fieldNames )
        {
            String simpleTypeName = ReflectionUtils.getField( subBean.getClass( ), fieldName ).getType( ).getSimpleName( ) ;

            if( "String".equals( simpleTypeName ) )
            {
                Object val = subBean.getFieldValue( fieldName ) ;

                if( subBean.query_isForceIncludedField( fieldName ) || ( ( val == null && !IGNORE_IN_SEARCH.equals( val ) ) && !subBean.query_isForceExcludedField( fieldName ) ) )
                {
                    disjunction.add( Restrictions.like( ( alias != null ? alias + "." : "" ) + fieldName, omni ) ) ;
                }
            }
        }

    } 

Log Hibernate event in Database

Sometimes we need to know how many objects created in one session.
(We have very big system. When an order is created there are hundred of objects
created).We need to know all the objects created in one session.


If you are using interceptor you can put a call to logging on those methods.

Below code generates identifiers like this.

H:1967664672,T:43TH:http-bio-80-exec-7

H:1967664672
From session hashcode we can understand which objects created at same hibernate session.

T:43TH:http-bio-80-exec-7
T : Thread Number
H : Thread Name

with thread data, we can understand utilization of threads by server.

How many server threads are running at an interval?
Which users took which thread?

protected static void doEventLog( boolean flag, String event, Session session )
    {
        if( flag )
        {
            YEventLog elog = YEventLog.newInstanceWithStackTrace( event ) ;

            String sessIdent = YClientUtils.truncateStringNoElipsis( "H:" + session.hashCode( ) + ",T:" + Thread.currentThread( ).getId( ) + ";TH:" + Thread.currentThread( ).getName( ), 64 ) ;

            elog.setSessionIdentifier( sessIdent ) ;

            YDelayedPersisterService.delayedSave( elog ) ;
        }
    }
    
    //Put stacktrace of current Action
    public static YEventLog newInstanceWithStackTrace( String eventName )
    {
        YEventLog elog = newInstance( eventName ) ;

        String trace = ExceptionUtils.stackTraceToString( null ) ;

        if( trace.length( ) > 25000 )
        {
            trace = trace.substring( 0, 25000 ) ;
        }

        elog.setStringData( trace ) ;

        return elog ;
    }

public class EventLog
{
    private Integer id ;

    private Integer organization ;

    private Integer user ;

    private String sessionIdentifier ;

    private String eventName ;

    private Date time ;

    private BigDecimal decimalData ;

    private String stringData ;

    private int YVersion ;

    private Date YCreateDate ;

    private Date YUpdateDate ;

}



I suggest you, to have 2 fields in database for holding The session id that object was created
and concatenated ids that object was updated or one concatenate all of these. (1st creator)
A better way will be having a separate table.(Our data is separated over tables)
Session Id, Bean Name, Date, Operation

Monday, October 12, 2015

Korean Food


Korean Food is amazing with its diversity. But that diversity
probably stems from historical famines and Korea landscape.
Korean is like small lowlands between lots of mountains.
So there is not enough place for agriculture for a big population.

Historical droughts lead to scarcity in food. Once a Korean told me
it was not even possible to feed cow or pig ,because they do not have enough food
for them also.
So probably(I wish I could read and investigate much on this topic) they had to try
everything they can. (Worm,dog, whatever Western people laugh)

They made pickles of everything and they are perfect. Especially I love ones
with sesame leaf(perilla). I ate different versions of it. I do not know names,
because it is very hard to memorize anything in Korean. But taste is unforgettable.

Every year I go to Busan for a reason coincidentally(Long story).
Once I discovered a restaurant in Busan. I just want to write it in internet
so that google crawler finds it in searches like below.

Best restaurant in Korea

I do not go to 3 star Michelin ones or real luxury ones, but this place seems so elegant
to me. Price is about 10$-30$. Side dishes are unbelievably perfect. It is in Lotte at near Nampo station.

Korea

100,032 square kilometres

Land use:
arable land: 15.3%
permanent crops: 2.2%
permanent pasture: 0.6%
forest: 63.9%
other: 18.0% (2011)
Irrigated land: 8,804 km² (2003)