Grails 3: использование findAll с таблицами соединений

В моем веб-приложении Grails есть следующие классы предметной области:

class Customer {
    static hasMany = [
        addresses: Address
    ]

    static mapping = {
        addresses (cascade: "all-delete-orphan", joinTable: [name: "bs_core_customer_addresses"])
    }
}

class Address {
    ...
}

Теперь я хочу реализовать возможность фильтровать отношение 1: n, например адреса, для таких вещей, как страна (должно быть динамическим, это означает, что пользователь может сам добавлять различные фильтры).

Каков наилучший способ добиться этого?

  1. Фильтровать коллекцию из объекта Customer? например customer.addresses.findAll{...}
  2. Прямой запрос из базы данных? Как я могу добавить ограничение для отношения Customer<->Address. belongsTo в доменном классе Address не вариант, потому что объект Address используется в нескольких отношениях 1:n. например Customer.findAll(...)
  3. Любой другой вариант?

person Andreas    schedule 13.06.2016    source источник


Ответы (1)


вы должны быть в состоянии уйти с

 static constraints = {
 addresses(validator: checkAddress)
 }
  // This is a static method which is used for validation 
  // and can be used for when inserting a record to check how many 
  // existing addresses exist for the end user that has countryCode of US
  // This is directly bound to all the object the user and will
  // will not be searching the entire DB (A local find limited to user records)
 static def checkAddress={val,obj,errors->
        if (!obj?.addresses.findAll{it.countryCode=='US'}?.size() >= 2) {
            return errors.rejectValue('adress','exceeds limit')
        }
    }  

Вышеизложенное должно быть самоочевидным, но, прочитав ваш пост несколько раз, я думаю, что лучше понимаю, чего вы пытаетесь достичь, и, вероятно, есть несколько разных способов сделать это. Итак, давайте рассмотрим некоторые из них:

Используя запрос HQL, вы можете изменить его на другой метод, я предпочитаю HQL.

class Customer {
    def userService
  //UserAddress does not have setter transients not essential
   static transients = ['userAddress','userService']

  //This is a protected function that will return an address 
  // object given a countryCode 
  // Execute like this:
  //  Customer cm = Customer.get(customer.id as Long)
  //Address usa = cm.getCountry('US')

   protected Address getUserAddress(String countryCode) {
     return userService.findAddress(countryCode, this.id)
   }
}

Теперь служба, но на самом деле вам не нужно выполнять в классе домена, если нет какой-либо другой необходимости, для отображения и т. д. вы всегда можете вызвать этот вид службы из вызова контроллера для отображения для целей отображения

class UserSerice {
 // This should return the entire address object back to the domain class 
 // that called it and will be quicker more efficient than findAll 
 Address findAddress(String countryCode, Long customerId) {
    String  query="""
      select address from Address a 
      where a.id :id and countryCode = :code 
    """
   def inputParams=[code:countryCode, id:customerId]
        return Address.executeQuery(query,inputParams,[readOnly:true,timeout:15])
 }

Другим подходом может быть третья таблица, которая обновляется при каждом добавлении адреса, что даст быстрый поиск:

class Customer {
    static hasMany = [
        addresses: Address
         //probably don't even need this
         //, userCountries:UserCountries
    ]
 }

 Class UserCountries {
  // register customer 
  Customer customer
  String CountryCode
  //maybe address object or Long addressId - depending on if how plain you wanted this to be
  Address address
 }

Затем регистрируйте идентификатор адреса и код страны в этом доменном классе каждый раз, когда вы добавляете новый адрес, и я думаю, вам нужно будет написать некоторый обратно совместимый код, чтобы добавить существующие записи в эту таблицу, чтобы она работала правильно.

Я оставил комментарий, а затем удалил его, чтобы вы могли подробнее рассказать о том, что и как происходило фильтрование. поскольку, хотя вы говорите о коде страны, нет фактического кода, показывающего, как все это вписывается.

Я все еще думаю, что что-то настолько простое, как это, сработает // Это будет только поиск со всеми связанными объектами адресов, привязанными к этому клиенту. поэтому найдите в элементах отношения hasMany этого конкретного клиента

protected def getCustomAddress(String countryCode) {
   return   addresses.findAll{it.code==countryCode}
}

Другие далекие идеи могут быть чем-то вроде этого

class Customer {
  String _bindAddress 
  List bindAddress=[]

  static transients = [ 'bindAddress' ]

  static constraints = {  
   _bindAddress(nullable:true)
  }

  //you store a flat CSV as _bindAddress
  //you need to work out your own logic to ammend to existing CSV each time address is added 
  // you will also update _bindAddress of this domainClass each time customer gets a hasMany address added
  // so no need for setBindAddress 
 //  void setBindAddress(String b) {
 //    bindAddress=b.split(',')
 // }
   //Inorder to set a list back to flat file 
  //pass in list object
   void setBindAddress(List bindAddress) {
    _bindAddress=bindAddress.join(',')
     /for 1 element this captures better 
    //_bindAddress=bindAddress.tokenize(',').collect{it}
   }
  //This is now your object as a list that you can query for what you are querying.
  List getBindAdress() {
    return _bindAddress.split(',')
  }

}

Если ваш фактический список csv содержал список «COUNTRY_CODE-ADDRESS_ID», вы могли бы запросить, как это

def found = customer.bindAddress.find{it.startsWith('US-')}
Address usaAddress= Address.get(found.split('-')[1] as Long)
 //Slightly longer explaining above:
def found = customer.bindAddress.find{it.startsWith('US-')}
def record = found.split('-')
String countryCode=record[0]
Long addressId=record[1] as Long
Address usaAddress= Address.get(addressId)
person V H    schedule 14.06.2016
comment
как ограничение может помочь мне реализовать функцию фильтра (например, получить все адреса клиента в США)? checkAddress только подтверждает, что в addresses есть не более 2 объектов, а в checkAddress2 findAllByCustomer невозможно, потому что Address не имеет поля customer - person Andreas; 14.06.2016
comment
obj?.addresses.findAll{it.country=='US'} ? возможно . @Андреас - person V H; 14.06.2016
comment
это на самом деле вопрос, недостатком этого является то, что все объекты коллекции (в данном случае адреса) извлекаются из базы данных - person Andreas; 17.06.2016
comment
хорошо зарегистрируйте его как список кодов стран csv каждый раз, когда добавляется адрес, который затем связывается обратно в виде списка, поэтому временный список кодов стран, который является добавленными адресами, вы можете затем посмотреть, сколько экземпляров. из «US» находится в этом списке каждый раз, когда вам нужно знать, и это сохраняет другой запрос. «все объекты коллекции» да для этого клиента, но затем возвращаются в виде списка, содержащего конкретную информацию. В качестве альтернативы привяжите службу, которая отключается и выполняет очень специфический запрос, чтобы вернуть коды стран в виде списка/карты. stackoverflow.com/questions/35245378 - person V H; 17.06.2016
comment
Я не знаю, что вы хотите мне сказать, и я не знаю, как ваш вопрос связан с моим вопросом... хранение любых переходных данных не вариант для масштабируемого приложения... - person Andreas; 19.06.2016