© 2010 SpringSource, A division of VMware. All rights reserved CONFIDENTIAL CONFIDENTIAL GORM Optimization Burt Beckwith SpringSource
May 17, 2015
© 2010 SpringSource, A division of VMware. All rights reserved
CONFIDENTIALCONFIDENTIAL
GORM Optimization
Burt Beckwith
SpringSource
2CONFIDENTIAL 2CONFIDENTIAL 2
Mapping Database Views
Writing a Custom Configuration Subclass for Fun and Profit
Read-only Domain Classes
Monitoring
Agenda
3CONFIDENTIAL 3CONFIDENTIAL
Also See
class UserInfo { String name String orgName
static mapping = { table 'v_user_info' }}
Advanced GORM - Performance, Customization and
Monitoring
•Spring One / 2GX 2010
•http://www.infoq.com/presentations/GORM-Performance
•Primarily focused on performance implications around
using Many-to-One and Many-to-Many in GORM
4CONFIDENTIAL 4CONFIDENTIAL
Mapping Database Views
5CONFIDENTIAL 5CONFIDENTIAL
Database View –> Entity
class AuthUser { String name String password Organization organization}
class UserInfo { String name String orgName}
class Organization { String name}
6CONFIDENTIAL 6CONFIDENTIAL
Database View –> Entity
CREATE OR REPLACE VIEW v_user_info ASSELECT u.name, u.id, u.version, o.name org_nameFROM auth_user u, organization oWHERE u.organization_id = o.id
class UserInfo { String name String orgName
static mapping = { table 'v_user_info' }}
7CONFIDENTIAL 7CONFIDENTIAL
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
8CONFIDENTIAL 8CONFIDENTIAL
Database View –> Entity
Register a Custom Configuration in DataSource.groovy
dataSource { pooled = true driverClassName = ... username = ... password = ... dialect = ... configClass = gr8conf.DdlFilterConfiguration}
9CONFIDENTIAL 9CONFIDENTIAL
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; }}
10CONFIDENTIAL 10CONFIDENTIAL
Database View –> Entity
<hibernateconfiguration>
<sessionfactory>
<mapping resource='misc.mysql.innodb.hbm.xml'/>
<mapping resource='misc.h2.hbm.xml'/>
</sessionfactory>
</hibernateconfiguration>
grails-app/conf/hibernate/hibernate.cfg.xml
11CONFIDENTIAL 11CONFIDENTIAL
Database View –> Entity
<hibernatemapping> <databaseobject>
<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>
<dialectscope name='org.hibernate.dialect.MySQLInnoDBDialect' />
</databaseobject></hibernatemapping>
grails-app/conf/hibernate/misc.mysql.innodb.hbm.xml
12CONFIDENTIAL 12CONFIDENTIAL
Subdomain Entity
class Person { String name Organization organization
static mapping = { table 'auth_user' }}
“Subdomain” with a subset of AuthUser data:
13CONFIDENTIAL 13CONFIDENTIAL
Subdomain Entity
class Person { String name Organization organization
static mapping = { table 'auth_user' dynamicUpdate true }}
Updatable Subdomain:
14CONFIDENTIAL 14CONFIDENTIAL
Writing a Custom Configuration Subclass for Fun and Profit
15CONFIDENTIAL 15CONFIDENTIAL
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()
16CONFIDENTIAL 16CONFIDENTIAL
Writing a Custom Configuration Subclass for Fun and Profit
secondPassCompile()
• Access each PersistentClass/RootClass and call any of
• addFilter()
• setBatchSize()
• setCustomSQLDelete()
• setCustomSQLInsert()
• setCustomSQLUpdate()
• setDynamicInsert()
• setDynamicUpdate()
• setExplicitPolymorphism()
• setOptimisticLockMode()
• setSelectBeforeUpdate()
• setWhere()
17CONFIDENTIAL 17CONFIDENTIAL
Read-only Domain Classes
18CONFIDENTIAL 18CONFIDENTIAL
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
19CONFIDENTIAL 19CONFIDENTIAL
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 grailsapp/conf/spring/resources.groovy
• Still a good idea to use a custom Configuration and call setMutable(false) to reduce memory usage
20CONFIDENTIAL 20CONFIDENTIAL
Read-only Domain Classes
import gr8conf.ReadOnlyEventListener
import o.c.g.g.orm.hibernate.HibernateEventListeners
beans = {
readOnlyEventListener(ReadOnlyEventListener)
hibernateEventListeners(HibernateEventListeners) { listenerMap = ['predelete': readOnlyEventListener, 'preinsert': readOnlyEventListener, 'preupdate': readOnlyEventListener] }}
grails-app/conf/spring/resources.groovy
21CONFIDENTIAL 21CONFIDENTIAL
Read-only Domain Classes
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 }}
src/groovy/gr8conf/ReadOnlyEventListener.groovy
22CONFIDENTIAL 22CONFIDENTIAL
Read-only Domain Classes
class LegacyData {
String name
static mapping = { id column: 'legacy_data_id' version false }}
grails-app/domain/gr8conf/LegacyData.groovy
23CONFIDENTIAL 23CONFIDENTIAL
Read-only Domain Classes
ReadOnlyEventListener
• delete()
• silently fails
• Update with save()
• silently fails
• New instance save()
• Throws exception ?
24CONFIDENTIAL 24CONFIDENTIAL
Read-only Domain Classes
class User {
String username String password}
You can also map a second writable domain class to the same table, e.g. for admin:
class WritableUser {
String username String password
static mapping = { table 'user' }}
25CONFIDENTIAL 25CONFIDENTIAL
Monitoring
26CONFIDENTIAL 26CONFIDENTIAL
Monitoring
SQL logging
• logSql=true in DataSource.groovy
• org.hibernate.SQL → debug, org.hibernate.type → trace
• 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
appenders { file name: 'sql', file: 'sql.log'}
debug additivity: false, sql: 'org.hibernate.SQL'trace additivity: false, sql: 'org.hibernate.type'
27CONFIDENTIAL 27CONFIDENTIAL
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
28CONFIDENTIAL 28CONFIDENTIAL
Questions?