Статьи

Добавление поддержки Oracle в BugLogHQ: как дизайн приложения стал проще

Как я упоминал в своем предыдущем посте в блоге , чтобы опробовать BugLogHQ в своей рабочей среде, я знал, что мне придется обновить код, чтобы он мог работать с таблицами базы данных Oracle. Поэтому я начал просматривать код, чтобы увидеть, насколько трудной будет эта задача, чтобы понять, стоит ли она того. Изменение кода для поддержки другой базы данных означало бы, по крайней мере, просмотр (если не обновление) каждого блока кода, выполняющего операцию SQL: если таких блоков было много, это могло бы занять много времени.

К счастью для меня, BugLogHQ был разработан для отвлечения большинства SQL-операций за уровнем DAO. Каждая таблица базы данных, используемая BugLogHQ, сопоставляется с CFC DAO в папке компонентов / db приложения BugLogHQ, и эти CFC описывают тип данных name и cfqueryparam для каждого поля таблицы и наследуют функции типа getter и setter из базового DAO учебный класс. Когда приходит время сохранять данные из экземпляров объектов DAO в базе данных, DAO обрабатываются dbDataProvider.cfc в папке компонентов / lib / dao / , и этот CFC содержит код SQL для выполнения всех операций CRUD.

(Следует отметить, что это несколько упрощенное описание всех «движущихся частей», связанных с сохранением данных в BugLogHQ, но после выполнения кода в процессе добавления и удаления записей я решил, что мне не нужно делать изменения в других объектах, таких как фабрика DAO).

Основной проблемой, которую мне нужно было решить, было то, что текущий код для вставки новой записи использовал функцию автоинкремента в других базах данных, поддерживаемых BugLogHQ (MySQL, MS SQL и т. Д.). Oracle не поставляется с опцией автоинкрементного поля. Вместо этого в Oracle есть так называемые последовательности , которые, по сути, представляют собой числа с автоинкрементом, которые существуют отдельно от таблиц базы данных (то есть вы можете, если хотите, вывести следующее целое число из определенной последовательности в любую таблицу). Итак, в сценарии установки SQL, который я написал, для создания необходимых таблиц в Oracle (зеркалирование сценариев установки для других механизмов баз данных, присутствующих в установке BugLogHQпапку), я добавил строки для создания последовательностей для каждой таблицы, причем каждое имя последовательности представляет собой имя таблицы с добавлением «_seq» в конце. Затем я добавил код в функцию _insert () в dbDataProvider.cfc, чтобы использовать последовательности для значения первичного ключа при вставке записи:

<cffunction name="_insert" access="private" returntype="any">
  <cfargument name="columns" required="true" type="struct">
  <cfargument name="_mapTableInfo" type="struct" required="true">
  ...  
  <cfset var dbtype = variables.oConfigBean.getDBType()>
  <cfset var lstFields = structKeyList(arguments.columns)>
  <cfset var tableName = arguments._mapTableInfo.tableName>
  <cfset var seqName= tableName & "_seq">
  <cfset var pkeyName= arguments._mapTableInfo.PKName>
  ...
  <cfif dbtype eq "oracle">
    <cftransaction>	
      <cfquery name="qry" datasource="#DSN#" username="#username#" password="#password#">
        INSERT INTO #getSafeTableName(tableName)# (#pkeyName#,#lstFields#)
        VALUES (
        #seqName#.nextVal,
        <cfloop list="#lstFields#" index="col">
          <cfqueryparam cfsqltype="#arguments.columns[col].cfsqltype#" value="#arguments.columns[col].value#" null="#arguments.columns[col].isNull#">
          <cfif i neq listLen(lstFields)>,</cfif>
            <cfset i = i + 1>
          </cfloop>
        )
      </cfquery>
				
      <cfquery name="qry" datasource="#DSN#" username="#username#" password="#password#" result="qryInfo">
        select #seqName#.currVal as lastId from dual
      </cfquery>
    </cftransaction>
    <cfset newID = qry.lastID>
  <cfelse>
  ...

Таким образом, если значение dbtype (полученное из файла config / buglog-config.xml.cfm ) равно «oracle», блок кода вставки Oracle будет выполнен. Первый блок cfquery вставит запись, используя функцию nextVal () последовательности, связанной с таблицей, чтобы получить следующее значение последовательности, в то время как второй блок cfquery извлекает текущее / последнее значение последовательности через таблицу Oracle « DUAL » msgstr «(конструкция, предназначенная для выполнения практически любой служебной операции, которая возвращает одну запись или значение).

Другой проблемой, которую мне нужно было решить, была настройка некоторых типов данных поля. Тип данных Oracle «varchar2» может содержать только 4000 символов текста; для хранения больших объемов текста необходимо использовать поля с типом данных CLOB. И чтобы использовать cfqueryparam с такими полями, вам нужно установить для параметра cfsqltype в cfqueryparam значение «cf_sql_clob». Поэтому я обновил код в CFC entryDAO и extensionDAO, чтобы справиться с этим (с кодом entryDAO.cfc, показанным ниже):

<cfif variables.oDataProvider.getConfig().getDBType() EQ "oracle">
  <cfset addColumn("exceptionDetails", "cf_sql_clob")>
  <cfset addColumn("HTMLReport", "cf_sql_clob")>
<cfelse>
  <cfset addColumn("exceptionDetails", "cf_sql_varchar")>
  <cfset addColumn("HTMLReport", "cf_sql_varchar")>
</cfif>

После этого мне осталось только добавить условные блоки, специфичные для Oracle, к двум другим запросам (связанным с использованием функций базы данных, специфичных для каждой платформы базы данных) в двух других файлах ( components / hq / appService.cfc и components /). lib / entryFinder.cfc ), и я закончил.

Если бы я сделал все изменения, которые я только что описал, идеально с первой попытки — что по общему признанию, я не сделал 🙂 — мне потребовалось бы меньше времени, чем это, чтобы написать этот пост в блоге. И это признак жесткой, хорошо разработанной архитектуры приложений, в которой определенные ключевые аспекты поведения приложений централизованы таким образом, что позволяет добавлять функциональность (в данном случае поддержку ядра СУБД) путем обновления всего нескольких файлов, отвечающих за обработку этой конкретной задачи. ,