АРХИВ ПО ТЭГУ "GRAILS"
ИЮНЬ 19, 2013

Hi,

peopools, whatsup?

Я тут заимплементил пруф оф консепт (ПОК) нашего группового задания (хотя все пришлось писать одному icon smile POC for University of Liverpool in 2 nights %d1%82%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d0%b8 ) за пару послерабочих вечеро-ночей на Grails + MySQL. Суть системы: University Admission Process, вобщем-то функционал для ПОКа минимальный: 0) разделение юзеров по ролям, 1) посылка сообщений, 2) загрузка документов, 3) менеджмент юзеров, 4) редактирование своего профиля.

Код выложил на гитхаб может кому интересно будет: https://github.com/chupakabr/uol-sp-poc. Баги там есть, я даже знаю о них, но не скажу никому, сами найдутся icon smile POC for University of Liverpool in 2 nights %d1%82%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d0%b8

Выглядит эта ботва примерно так (индекс пейж):

Screen Shot 2013 06 19 at 2.14.02 AM 1024x747 POC for University of Liverpool in 2 nights %d1%82%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d0%b8

Такие дела.

Ушел курить бамбук.

МАРТ 1, 2013
НОЯБРЬ 8, 2012

Hi,

Рябы рябские, как дела?

Меня тут вдруг запарила проблема с оптимистик локинг фейлур (StaleObjectStateException, OptimisticLockingFailureExceptionHibernateOptimisticLockingFailureException) в Grails, когда слишком частые запросы на сервер приводят к синхронному обновлению домена в персистенс, что в свою очередь приводит к падению одного из запросов, т.к. version уже обновлен другим потоком (запросом). Собсно я  решил написать небольшой хелпер для сохранения доменов, в  итоге это оказало не хелпером, а заижекченым методом во все доменные классы проекта. Ща покажу как это юзается, а потом уже саму имплементацию.

Домен

  1. // по дефолту в грейлс каждый домен
  2. // юзает optimistic locking, используя
  3. // version колонку (проперти)
  4. class Something {
  5.   String name
  6.   String value
  7. }

Стандартное сохранение домена

  1. // загружаем и обновляем значение
  2. def e = Something.get(1)
  3.  
  4. // если в этот момент другой запрос (поток) обновит
  5. // эту запись, то грейлс кинет StaleObjectStateException
  6. e.value = "asdasd"
  7. e.save flush: true, failOnError: true

Сохранение с optimistic locking with retry вариант 1

  1. // загружаем и обновляем
  2. def e = Something.get(1)
  3.  
  4. // если в этот момент другой  запрос (поток) обновит
  5. // эту запись, то мы рефрешнем запись в сессии и
  6. // попробуем обновить еще разок
  7. e.saveOptimisticWithRetry flush: true, failOnError: true, retryCount: 2, { obj ->
  8.   obj.value = "asd"
  9. }

Сохранение с optimistic locking with retry вариант 2

  1. // все параметры опциональны, их можно опустить
  2. // по дефолту:
  3. // retryCount = 2
  4. // flush = false
  5. // failOnError = true
  6. def e = Something.get(1)
  7. e.saveOptimisticWithRetry retryCount: 5, { obj ->
  8.   obj.value = "asd"
  9. }

Сохранение с optimistic locking with retry вариант 3

  1. // все параметры опциональны, их можно опустить
  2. def e = Something.get(1)
  3. e.saveOptimisticWithRetry { obj ->
  4.   obj.value = "asd"
  5. }

Имплементация optimistic locking with retry

  1. // в Bootstrap.groovy к примеру можно
  2. // заинжектить метод во все домены
  3. for (dc in grailsApplication.domainClasses) {
  4.   dc.metaClass.saveOptimisticWithRetry = { args = null, setter ->
  5.     final int retryCount = args?.containsKey('retryCount') ? args.retryCount : 2
  6.     final boolean failOnError = args?.containsKey('failOnError') ? args.failOnError : true
  7.     final boolean flush = args?.containsKey('flush') ? args.flush : true
  8.    
  9.     int i = 0
  10.     while (true) {
  11.       try {
  12.         // Save domain
  13.         setter(delegate)
  14.         delegate.save(failOnError: failOnError, flush: flush)
  15.         return
  16.       }
  17.       catch (StaleObjectStateException e) {
  18.         // Throw exception if retry count bounds reached
  19.         if (++i > retryCount) {
  20.           throw e
  21.         }
  22.        
  23.         // Refresh object within session
  24.         delegate.refresh()
  25.       }
  26.     }
  27.   }
  28. }

Хорошего дня!

PS Можно вобщем-то подменить и сам метод save() у доменов, добавив в него имплементацию ретрая при оптимистик логинг фейлуре ексепшене.

ИЮНЬ 15, 2012

Hi,

Just a note: А вы знали, что Grails не будет создавать индексы в базе (например, строковые), если в датасурсе (DataSource.groovy) прописан режим dbCreate = «update», индексы буду создавать только при dbCreate = «create» или dbCreate = «create-drop».

ОКТЯБРЬ 5, 2011

grails logo Grails URL mappings   different controllers by HTTP method %d1%82%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d0%b8

Hey.

Grails замечательная штука, но иногда чтобы понять как оно работает, нужно все таки в код посмотреть icon smile Grails URL mappings   different controllers by HTTP method %d1%82%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d0%b8

Например задача: нужно на один и тот же урл в зависимости от HTTP метода заюзать различные контроллеры. Для различных экшенов и одного контроллера это делается легко:

«/api/stuff/$id» (controller: 'stuff') {
  1.    action = [ GET: 'show', PUT: 'update', DELETE: 'delete' ]
  2.   }

Т.е. на «/api/stuff/123 DELETE» мы вызовем StuffController::deleteAction().

А мы хотим различные контроллеры:

  • «/api/stuff/123 DELETE» мы вызовем StuffCustomController::deleteAction(),
  • для остальных хттп методов: ApiController::stuffAction().

Тогда нужно немножко копнуть в грейлс код. В таком замечательном интерфейсе как org.codehaus.groovy.grails.web.mapping.UrlMapping можно увидеть, что в getActionName() и getControllerName() возвращается Closure или String. Собсно нас интересует closure. Раз это замыкание, то мы легко можем сделать свитч по текущему методу из реквест объекта и таким образом динамически сказать какой контроллер нужно бы сейчас заюзать.

В итоге:

«/api/stuff/$id» {
  1.    controller = {
  2.     switch (request?.method) {
  3.      case 'DELETE':
  4.       return 'stuffCustom'
  5.      default:
  6.       return 'api'
  7.     }
  8.    }
  9.    action = {
  10.     switch (request?.method) {
  11.      case 'DELETE':
  12.       return 'delete'
  13.      default:
  14.       return 'stuff'
  15.     }
  16.    }
  17.   }
АВГУСТ 19, 2011

oracle hate sux Grails/Java   Insert LARGE CLOB into Oracle 9i %d1%82%d0%b5%d1%85%d0%bd%d0%be%d0%bb%d0%be%d0%b3%d0%b8%d0%b8

Hi.

Ох уж этот оракл, юзабильность это не о нем.

На этот раз проблема с Oracle 9i драйвером для Java. Возникла проблема сохранения CLOB / BLOB данных в базу, используя драйвер для Oracle 9i. На 10g все вроде как даже работает..

Так вот когда пытаешься работать с CLOB / BLOB, то возникают различные ексепшены типа

  • java.sql.SQLException: Data size bigger than max size for this type: 50584
  • java.sql.SQLException: operation not allowed: streams type cannot be used in batching
Аха, можете попробовать, ни для какого типа данных у меня не получилось сохранить большой объект простым способом. А я пробовал много чего: CLOB, BLOB, LONG, LONG RAW. Но фига — больше 4-50kb не сохраняет.
В итоге пришел к изврашенному решению описанному где то в потайных документациях оракла. Решение использует Java JDBC для сохранения модели если это оракл драйвер версии 9. Для других баз и других версий оракла будет использоваться стандартное сохранение доменного класса Grails.
Моделька Grails
package my.com
  1.  
  2. class MyDomain {
  3.  
  4.  String bigData
  5.  
  6.  static constraints = {
  7.   bigData(maxSize: 1024 * 1024) // 1 MB
  8.  }
  9.  
  10.  static mapping = {
  11.   bigData type:"text" // this will create CLOB data type column
  12.  }
  13. }
Методы сервиса для сохранения большого CLOB’а
def isOracle9Driver = {
  1.   boolean isOracle9 = false
  2.  
  3.   try {
  4.    Connection conn = sessionFactory.getCurrentSession().connection()
  5.    DatabaseMetaData dbmd = conn.getMetaData()
  6.  
  7.    if (dbmd != null) {
  8.     String dbName = dbmd.getDatabaseProductName()
  9.     String dbDriverVer = dbmd.getDriverVersion()
  10.  
  11.     if ("Oracle".equals(dbName) && (dbDriverVer != null && dbDriverVer.startsWith("9."))) {
  12.      isOracle9 = true
  13.     }
  14.    }
  15.   } catch (Exception e) {
  16.    log.warn "Error getting DB Driver version — " + e.getMessage()
  17.    e.printStackTrace()
  18.   }
  19.  
  20.   return isOracle9
  21.  }
  22.  
  23.  def saveMyDomain(def bigData) {
  24.  
  25.   // RAW SQL USING JDBC FOR Oracle 9i DRIVER
  26.   if (isOracle9Driver()) {
  27.  
  28.    Connection conn = sessionFactory.getCurrentSession().connection()
  29.    PreparedStatement stmt
  30.    CallableStatement callableStmt
  31.  
  32.    try {
  33.  
  34.     //
  35.     // Insert new entry with empty clob
  36.     String sql = "BEGIN INSERT INTO my_domain (id,version,big_data) values (HIBERNATE_SEQUENCE.NEXTVAL,1,empty_clob()) RETURNING id INTO ?; END;"
  37.     callableStmt = conn.prepareCall(sql)
  38.     callableStmt.registerOutParameter(1, Types.INTEGER);
  39.     callableStmt.executeUpdate()
  40.  
  41.     long newId = callableStmt.getLong(1)
  42.     if (newId < 0) {
  43.      throw new IllegalStateException("Cannot get autogenerated ID of my_domain: got id=" + newId)
  44.     }
  45.     callableStmt.close()
  46.  
  47.     //
  48.     // Select for update
  49.     sql = "SELECT big_data FROM my_domain WHERE id=? FOR UPDATE"
  50.     stmt = conn.prepareStatement(sql)
  51.     stmt.setLong(1, newId)
  52.  
  53.     ResultSet rss = stmt.executeQuery()
  54.     if (!rss.next()){
  55.      throw new IllegalStateException("Cannot get result for 'select for update' of my_domain")
  56.     }
  57.  
  58.     //
  59.     // Update create entry with clob data
  60.     CLOB clob = (CLOB) rss.getClob("big_data")
  61.     clob.putString(1, bigData)
  62.  
  63.     sql = "UPDATE my_domain SET big_data=? WHERE id=?"
  64.     stmt = conn.prepareStatement(sql)
  65.     stmt.setClob(1, clob)
  66.     stmt.setLong(2, newId)
  67.     stmt.executeUpdate()
  68.     stmt.close()
  69.  
  70.     conn.commit()
  71.  
  72.     } catch (Exception e) {
  73.      log.error "Cannot save my_domain — " + e.getMessage()
  74.      e.printStackTrace()
  75.      conn.rollback()
  76.     } finally {
  77.      if (callableStmt != null) callableStmt.close()
  78.      if (stmt != null) stmt.close()
  79.     }
  80.   } else {
  81.    // DOMAIN CLASS SAVE
  82.    new MyDomain(
  83.      bigData: bigData
  84.     ).save(flush:true)
  85.   }
  86.  }
Страницы:12