GORM Optimization

Burt Beckwith
SpringSource




                                                  CONFIDENTIAL
                    © 2010 SpringSource, A division of VMware. All rights reserved
Agenda

 Mapping Database Views

 Writing a Custom Configuration Subclass for Fun and Profit

 Read-only Domain Classes

 Monitoring




                               CONFIDENTIAL                    2
Also See



 Advanced GORM - Performance, Customization and
 Monitoring

 • Spring One / class UserInfo {
                2GX 2010
                    String name
                    String orgName
 • http://www.infoq.com/presentations/GORM-Performance
                     static mapping = {
 • Primarily   focused on 'v_user_info'
                       table performance     implications around
                     }
                   }
  using Many-to-One and Many-to-Many in GORM



                              CONFIDENTIAL                         3
Mapping Database Views




         CONFIDENTIAL    4
Database View –> Entity


    class Organization {
      String name
    }


                                                class UserInfo {
                                                  String name
                                                  String orgName
                                                }
   class AuthUser {
     String name
     String password
     Organization organization
   }




                                 CONFIDENTIAL                      5
Database View –> Entity

   CREATE OR REPLACE VIEW v_user_info AS
   SELECT u.name, u.id, u.version, o.name org_name
   FROM auth_user u, organization o
   WHERE u.organization_id = o.id



                class UserInfo {
                   String name
                   String orgName

                    static mapping = {
                      table 'v_user_info'
                    }
                }




                                CONFIDENTIAL         6
Database View –> Entity


 ERROR hbm2ddl.SchemaExport  ­ 
 Unsuccessful: create table v_user_info (id 
 bigint not null auto_increment, version 
 bigint not null, name varchar(255) not 
 null, org_name varchar(255) not null, 
 primary key (id)) type=InnoDB

 ERROR hbm2ddl.SchemaExport  ­ Table 
 'v_user_info' already exists




                          CONFIDENTIAL         7
Database View –> Entity

Register a Custom Configuration in DataSource.groovy


         dataSource {
           pooled = true
           driverClassName = ...
           username = ...
           password = ...
           dialect = ...
           configClass = gr8conf.DdlFilterConfiguration
         }




                                  CONFIDENTIAL            8
Database View –> Entity

   public class DdlFilterConfiguration extends GrailsAnnotationConfiguration {

       private static final String[] IGNORED_NAMES = { "v_user_info" };
       ...
       private boolean isIgnored(String command) {
           command = command.toLowerCase();

           for (String table : IGNORED_NAMES) {
             if (command.startsWith("create table " + table + " ") ||
                 command.startsWith("alter table " + table + " ") ||
                 command.startsWith("drop table " + table) ||
                 command.startsWith("drop table if exists " + table)) {
                return true;
             }
           }

           return false;
       }
   }

                                           CONFIDENTIAL                          9
Database View –> Entity


      grails-app/conf/hibernate/hibernate.cfg.xml


    <hibernate­configuration>

       <session­factory>

          <mapping resource='misc.mysql.innodb.hbm.xml'/>

          <mapping resource='misc.h2.hbm.xml'/>

       </session­factory>

    </hibernate­configuration>




                            CONFIDENTIAL                    10
Database View –> Entity

     grails-app/conf/hibernate/misc.mysql.innodb.hbm.xml

    <hibernate­mapping>
       <database­object>

          <create>
             CREATE OR REPLACE VIEW v_user_info AS
             SELECT u.name, u.id, u.version, o.name org_name
             FROM auth_user u, organization o
             WHERE u.organization_id = o.id
          </create>

          <drop>DROP VIEW IF EXISTS v_user_info</drop>

          <dialect­scope
              name='org.hibernate.dialect.MySQLInnoDBDialect' />

       </database­object>
    </hibernate­mapping>



                              CONFIDENTIAL                         11
Subdomain Entity


      “Subdomain” with a subset of AuthUser data:


               class Person {
                 String name
                 Organization organization

                   static mapping = {
                     table 'auth_user'
                   }
               }




                                 CONFIDENTIAL       12
Subdomain Entity


      Updatable Subdomain:


               class Person {
                 String name
                 Organization organization

                   static mapping = {
                     table 'auth_user'
                     dynamicUpdate true
                   }
               }




                               CONFIDENTIAL   13
Writing a Custom Configuration Subclass for
               Fun and Profit




                   CONFIDENTIAL               14
Writing a Custom Configuration Subclass for Fun and Profit

Custom Configurations: http://burtbeckwith.com/blog/?p=465
 Examples
  • Previous SQL generation example
    • Overrides generateSchemaCreationScript(),
      generateDropSchemaScript(), and generateSchemaUpdateScript()
  • Specifying the 'connection.provider_class' property
    • Overrides buildSettings()
  • Renaming columns of a composite foreign key
    • Overrides secondPassCompile()
  • Overriding the EntityPersister class
    • Overrides secondPassCompile()
  • Using field access
    • Overrides secondPassCompile()



                                       CONFIDENTIAL                  15
Writing a Custom Configuration Subclass for Fun and Profit


 secondPassCompile()
  • Access each PersistentClass/RootClass and call any of
   • addFilter()              • setDynamicUpdate()
   • setBatchSize()           • setExplicitPolymorphism()
   • setCustomSQLDelete()     • setOptimisticLockMode()
   • setCustomSQLInsert()     • setSelectBeforeUpdate()
   • setCustomSQLUpdate()     • setWhere()
   • setDynamicInsert()




                                 CONFIDENTIAL                16
Read-only Domain Classes




          CONFIDENTIAL     17
Read-only Domain Classes

 Not 100% possible, but close enough

 Hibernate

 • Seems possible via setMutable(false) in a custom Configuration, but:


   • Won't block saves or deletes, only disables dirty-check updates

   • Does nothing with collections since they're a PersistentSet or
    PersistentList and managed separately (but you wouldn't want to map
    collections anyway, right?)

   • See http://docs.jboss.org/hibernate/core/3.5/reference/en/html/readonly.html



                                     CONFIDENTIAL                                   18
Read-only Domain Classes

 Grails

 • Only beforeUpdate supports 'vetoing' by returning false


 • A hackish solution would be to throw an exception in beforeDelete and
   beforeInsert


 • Better solution: hibernateEventListeners bean in grails­
   app/conf/spring/resources.groovy


 • Still a good idea to use a custom Configuration and call setMutable(false)
   to reduce memory usage




                                   CONFIDENTIAL                                 19
Read-only Domain Classes


      grails-app/conf/spring/resources.groovy

    import gr8conf.ReadOnlyEventListener

    import o.c.g.g.orm.hibernate.HibernateEventListeners

    beans = {

       readOnlyEventListener(ReadOnlyEventListener)

       hibernateEventListeners(HibernateEventListeners) {
          listenerMap = ['pre­delete': readOnlyEventListener,
                         'pre­insert': readOnlyEventListener,
                         'pre­update': readOnlyEventListener]
       }
    }




                               CONFIDENTIAL                     20
Read-only Domain Classes

   src/groovy/gr8conf/ReadOnlyEventListener.groovy
   class ReadOnlyEventListener implements PreDeleteEventListener,
            PreInsertEventListener, PreUpdateEventListener {

    private static final List<String> READ_ONLY = ['gr8conf.LegacyData']

    boolean onPreDelete(PreDeleteEvent event) {
       isReadOnly event.persister.entityName
    }

    boolean onPreInsert(PreInsertEvent event) {
       isReadOnly event.persister.entityName
    }

    boolean onPreUpdate(PreUpdateEvent event) {
       isReadOnly event.persister.entityName
    }

    private boolean isReadOnly(String name) { READ_ONLY.contains name }
   }



                                CONFIDENTIAL                               21
Read-only Domain Classes


   grails-app/domain/gr8conf/LegacyData.groovy


                class LegacyData {

                   String name

                   static mapping = {
                      id column: 'legacy_data_id'
                      version false
                   }
                }




                            CONFIDENTIAL            22
Read-only Domain Classes

 ReadOnlyEventListener

 • delete()
   • silently fails


 • Update with save()
   • silently fails


 • New instance save()
   • Throws exception    ?




                             CONFIDENTIAL   23
Read-only Domain Classes


   You can also map a second writable domain class
   to the same table, e.g. for admin:

   class User {                           class WritableUser {

      String username                        String username
      String password                        String password
   }
                                             static mapping = {
                                                table 'user'
                                             }
                                          }




                           CONFIDENTIAL                           24
Monitoring




   CONFIDENTIAL   25
Monitoring

 SQL logging
 • logSql=true in DataSource.groovy
 • org.hibernate.SQL → debug, org.hibernate.type → trace
     appenders {
        file name: 'sql', file: 'sql.log'
     }

     debug additivity: false, sql: 'org.hibernate.SQL'
     trace additivity: false, sql: 'org.hibernate.type'

 • P6spy plugin
   • Use SQL Profiler Swing app to view realtime logs; see Mike Hugo's blog post
    Grails, p6spy and Sql Profiler
 • Run explain (Oracle, MySQL, others) on real queries to look for missing indexes




                                         CONFIDENTIAL                                26
Monitoring


 Spring Insight lets you drill down to SQL and view timing
 • http://www.grails.org/screencast/show/13

 Profiler plugin can give you timing data
 JavaMelody Plugin
 App Info plugin




                                 CONFIDENTIAL                 27
Questions?




   CONFIDENTIAL   28

GR8Conf 2011: GORM Optimization

  • 1.
    GORM Optimization Burt Beckwith SpringSource CONFIDENTIAL © 2010 SpringSource, A division of VMware. All rights reserved
  • 2.
    Agenda  Mapping DatabaseViews  Writing a Custom Configuration Subclass for Fun and Profit  Read-only Domain Classes  Monitoring CONFIDENTIAL 2
  • 3.
    Also See  AdvancedGORM - Performance, Customization and Monitoring • Spring One / class UserInfo { 2GX 2010 String name String orgName • http://www.infoq.com/presentations/GORM-Performance static mapping = { • Primarily focused on 'v_user_info' table performance implications around } } using Many-to-One and Many-to-Many in GORM CONFIDENTIAL 3
  • 4.
  • 5.
    Database View –>Entity class Organization { String name } class UserInfo { String name String orgName } class AuthUser { String name String password Organization organization } CONFIDENTIAL 5
  • 6.
    Database View –>Entity CREATE OR REPLACE VIEW v_user_info AS SELECT u.name, u.id, u.version, o.name org_name FROM auth_user u, organization o WHERE u.organization_id = o.id class UserInfo { String name String orgName static mapping = { table 'v_user_info' } } CONFIDENTIAL 6
  • 7.
    Database View –>Entity ERROR hbm2ddl.SchemaExport  ­  Unsuccessful: create table v_user_info (id  bigint not null auto_increment, version  bigint not null, name varchar(255) not  null, org_name varchar(255) not null,  primary key (id)) type=InnoDB ERROR hbm2ddl.SchemaExport  ­ Table  'v_user_info' already exists CONFIDENTIAL 7
  • 8.
    Database View –>Entity Register a Custom Configuration in DataSource.groovy dataSource { pooled = true driverClassName = ... username = ... password = ... dialect = ... configClass = gr8conf.DdlFilterConfiguration } CONFIDENTIAL 8
  • 9.
    Database View –>Entity public class DdlFilterConfiguration extends GrailsAnnotationConfiguration { private static final String[] IGNORED_NAMES = { "v_user_info" }; ... private boolean isIgnored(String command) { command = command.toLowerCase(); for (String table : IGNORED_NAMES) { if (command.startsWith("create table " + table + " ") || command.startsWith("alter table " + table + " ") || command.startsWith("drop table " + table) || command.startsWith("drop table if exists " + table)) { return true; } } return false; } } CONFIDENTIAL 9
  • 10.
    Database View –>Entity grails-app/conf/hibernate/hibernate.cfg.xml <hibernate­configuration>    <session­factory>       <mapping resource='misc.mysql.innodb.hbm.xml'/>       <mapping resource='misc.h2.hbm.xml'/>    </session­factory> </hibernate­configuration> CONFIDENTIAL 10
  • 11.
    Database View –>Entity grails-app/conf/hibernate/misc.mysql.innodb.hbm.xml <hibernate­mapping>    <database­object>       <create>          CREATE OR REPLACE VIEW v_user_info AS          SELECT u.name, u.id, u.version, o.name org_name          FROM auth_user u, organization o          WHERE u.organization_id = o.id       </create>       <drop>DROP VIEW IF EXISTS v_user_info</drop>       <dialect­scope           name='org.hibernate.dialect.MySQLInnoDBDialect' />    </database­object> </hibernate­mapping> CONFIDENTIAL 11
  • 12.
    Subdomain Entity “Subdomain” with a subset of AuthUser data: class Person { String name Organization organization static mapping = { table 'auth_user' } } CONFIDENTIAL 12
  • 13.
    Subdomain Entity Updatable Subdomain: class Person { String name Organization organization static mapping = { table 'auth_user' dynamicUpdate true } } CONFIDENTIAL 13
  • 14.
    Writing a CustomConfiguration Subclass for Fun and Profit CONFIDENTIAL 14
  • 15.
    Writing a CustomConfiguration Subclass for Fun and Profit Custom Configurations: http://burtbeckwith.com/blog/?p=465  Examples • Previous SQL generation example • Overrides generateSchemaCreationScript(), generateDropSchemaScript(), and generateSchemaUpdateScript() • Specifying the 'connection.provider_class' property • Overrides buildSettings() • Renaming columns of a composite foreign key • Overrides secondPassCompile() • Overriding the EntityPersister class • Overrides secondPassCompile() • Using field access • Overrides secondPassCompile() CONFIDENTIAL 15
  • 16.
    Writing a CustomConfiguration Subclass for Fun and Profit  secondPassCompile() • Access each PersistentClass/RootClass and call any of • addFilter() • setDynamicUpdate() • setBatchSize() • setExplicitPolymorphism() • setCustomSQLDelete() • setOptimisticLockMode() • setCustomSQLInsert() • setSelectBeforeUpdate() • setCustomSQLUpdate() • setWhere() • setDynamicInsert() CONFIDENTIAL 16
  • 17.
  • 18.
    Read-only Domain Classes Not 100% possible, but close enough  Hibernate • Seems possible via setMutable(false) in a custom Configuration, but: • Won't block saves or deletes, only disables dirty-check updates • Does nothing with collections since they're a PersistentSet or PersistentList and managed separately (but you wouldn't want to map collections anyway, right?) • See http://docs.jboss.org/hibernate/core/3.5/reference/en/html/readonly.html CONFIDENTIAL 18
  • 19.
    Read-only Domain Classes Grails • Only beforeUpdate supports 'vetoing' by returning false • A hackish solution would be to throw an exception in beforeDelete and beforeInsert • Better solution: hibernateEventListeners bean in grails­ app/conf/spring/resources.groovy • Still a good idea to use a custom Configuration and call setMutable(false) to reduce memory usage CONFIDENTIAL 19
  • 20.
    Read-only Domain Classes grails-app/conf/spring/resources.groovy import gr8conf.ReadOnlyEventListener import o.c.g.g.orm.hibernate.HibernateEventListeners beans = {    readOnlyEventListener(ReadOnlyEventListener)    hibernateEventListeners(HibernateEventListeners) {       listenerMap = ['pre­delete': readOnlyEventListener,                      'pre­insert': readOnlyEventListener,                      'pre­update': readOnlyEventListener]    } } CONFIDENTIAL 20
  • 21.
    Read-only Domain Classes src/groovy/gr8conf/ReadOnlyEventListener.groovy class ReadOnlyEventListener implements PreDeleteEventListener,          PreInsertEventListener, PreUpdateEventListener {  private static final List<String> READ_ONLY = ['gr8conf.LegacyData']  boolean onPreDelete(PreDeleteEvent event) {     isReadOnly event.persister.entityName  }  boolean onPreInsert(PreInsertEvent event) {     isReadOnly event.persister.entityName  }  boolean onPreUpdate(PreUpdateEvent event) {     isReadOnly event.persister.entityName  }  private boolean isReadOnly(String name) { READ_ONLY.contains name } } CONFIDENTIAL 21
  • 22.
    Read-only Domain Classes grails-app/domain/gr8conf/LegacyData.groovy class LegacyData {    String name    static mapping = {       id column: 'legacy_data_id'       version false    } } CONFIDENTIAL 22
  • 23.
    Read-only Domain Classes ReadOnlyEventListener • delete() • silently fails • Update with save() • silently fails • New instance save() • Throws exception ? CONFIDENTIAL 23
  • 24.
    Read-only Domain Classes You can also map a second writable domain class to the same table, e.g. for admin: class User { class WritableUser {    String username    String username    String password    String password }    static mapping = {       table 'user'    } } CONFIDENTIAL 24
  • 25.
    Monitoring CONFIDENTIAL 25
  • 26.
    Monitoring  SQL logging • logSql=true in DataSource.groovy • org.hibernate.SQL → debug, org.hibernate.type → trace appenders {    file name: 'sql', file: 'sql.log' } debug additivity: false, sql: 'org.hibernate.SQL' trace additivity: false, sql: 'org.hibernate.type' • P6spy plugin • Use SQL Profiler Swing app to view realtime logs; see Mike Hugo's blog post Grails, p6spy and Sql Profiler • Run explain (Oracle, MySQL, others) on real queries to look for missing indexes CONFIDENTIAL 26
  • 27.
    Monitoring  Spring Insightlets you drill down to SQL and view timing • http://www.grails.org/screencast/show/13  Profiler plugin can give you timing data  JavaMelody Plugin  App Info plugin CONFIDENTIAL 27
  • 28.
    Questions? CONFIDENTIAL 28