суббота, 28 апреля 2012 г.

Примеры простых программ на Lazarus под Win32 (Часть 2)

В этом посте я расскажу о своем первом опыте по работе с базами данных в Lazarus.

Как я уже писал в первой части, задача была подключиться к удаленной базе данных MySQL, извлечь оттуда данные и записать их в CSV.

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


Для начала я убедился, что я в принципе могу подключиться к целевой базе данных с имеющимися реквизитами доступа. Для этого воспользовался клиентом dbForge Studio for MySQL. Подключение прошло вполне себе успешно.

Далее я создал проект Lazarus, на пустую форму бросил стандартный компонент TMySQL51Connection, задал его свойства и попробовал подключиться к базе данных. И получил ошибку

"TMySQL51Connection can not work with the installed MySQL client version: Expected (5.1), got (6.0.0)".

Я испробовал целую кучу клиентских библиотек самых разных версий, копировал их в самые неожиданные места (ну, коль скоро их присутствие в папке проекта никакой роли не сыграло), но подключения так и не случилось. Оговорюсь на всякий случай, что MySQL на моей машине не установлен. Промучившись со стандартными компонентами и перерыв форумы на предмет решения, я подумал, что с меня довольно, и надо искать альтернативу.

Надо сказать, что альтернатива нашлась довольно быстро в виде библиотеки ZeosLib, которая установилась сразу и без проблем заработала. После замены TMySQL51Connection на TZConnection, дело пошло вполне себе споро и уложилось в 140 строк размашистым почерком:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, mysql50conn, mysql51conn, FileUtil, Forms, Controls, ExtCtrls,
  Graphics, Dialogs, StdCtrls, ZConnection, ZDataset;

type

  { TForm1 }

  TForm1 = class(TForm)
    procedure bStartClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

var
  mmInfo: TMemo;
  pnTop: TPanel;
  bStart: TButton;
  ZConn: TZConnection;
  ZROQuery: TZReadOnlyQuery;

{$R *.lfm}

{ TForm1 }

procedure ShowInfo(const S: string; const Arr: array of const);
begin
  mmInfo.Lines.Add(
    TimeToStr(Now) + ' ' + Format(S, Arr));
end;

procedure TForm1.bStartClick(Sender: TObject);
var
  SL: TStringList;
  Cnt: integer;
  FN: string;
begin
  SL := TStringList.Create;
  Cnt := 0;
  try
    ShowInfo('Подключение к удаленной базе данных...', []);
    if ZConn.Connected then
      ZConn.Disconnect;
    ZConn.Connect;
    ShowInfo('Выполнено.', []);
    ShowInfo('Выполняется запрос...', []);
    ZROQuery.Active := True;
    SL.Add(
      'id_company;company;category_fi;province;city;address;' + 'phone;url;email;');
    while not ZROQuery.EOF do
    begin
      SL.Add(
        ZROQuery.FieldByName('id_company').AsString + ';' +
        ZROQuery.FieldByName('company').AsString + ';' +
        ZROQuery.FieldByName('category_fi').AsString + ';' +
        ZROQuery.FieldByName('province').AsString + ';' +
        ZROQuery.FieldByName('city').AsString + ';' +
        ZROQuery.FieldByName('address').AsString + ';' +
        ZROQuery.FieldByName('phone').AsString + ';' +
        ZROQuery.FieldByName('url').AsString + ';' +
        ZROQuery.FieldByName('email').AsString + ';'
        );
      Inc(Cnt);
      if Cnt mod 100 = 0 then
        ShowInfo('получено %d  строк', [Cnt]);
      ZROQuery.Next;
    end;
    ZROQuery.Close;
    ShowInfo('Завершено. Всего получено строк: %d', [Cnt]);
    FN := ExtractFilePath(Application.ExeName) + 'data.csv';
    SL.SaveToFile(FN);
    ShowInfo('Результаты сохранены в файл %s', [FN]);
  except
    on E: Exception do
      mmInfo.Lines.Add(E.Message);
  end;
  FreeAndNil(SL);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  pnTop := TPanel.Create(Self);
  pnTop.Parent := Self;
  pnTop.Align := alTop;
  mmInfo := TMemo.Create(Self);
  mmInfo.Parent := Self;
  mmInfo.Align := alClient;
  bStart := TButton.Create(pnTop);
  bStart.Parent := pnTop;
  bStart.Caption := 'Start';
  bStart.OnClick := @bStartClick;
  ZConn := TZConnection.Create(Self);
  ZConn.HostName := 'some_host.info';
  ZConn.User := 'some_user';
  ZConn.Password := 'SoMePwd1';
  ZConn.Database := 'some_db';
  ZConn.Protocol := 'mysql-5';
  ZConn.Port:=3306;
  ZConn.AutoCommit := True;
  ZROQuery := TZReadOnlyQuery.Create(Self);
  ZROQuery.Connection := ZConn;
  ZROQuery.SQL.Text :=
    'select c.ID_COMPANY, c.COMPANY, ca.CATEGORY_RU,ca.CATEGORY_FI, ' +
    '  p.PROVINCE, ci.CITY, ' + '  c.ADDRESS, a.PHONE,a.URL,c.EMAIL,' +
    '  a.ID_ADVERT_TYPE,a.IS_COLORED,a.SEND2TOP,a.SERVE_LANGS ' +
    '  from adverts a ' + '  left JOIN companies c on c.ID_COMPANY=a.ID_COMPANY' +
    '  left join cities ci on ci.ID_CITY=c.ID_CITY' +
    '  left join provinces p on p.ID_PROVINCE=ci.ID_PROVINCE' +
    '  left join categories ca on ca.ID_CATEGORY=a.ID_CATEGORY';
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if ZConn.Connected then
    ZConn.Disconnect;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  bStart.Top := 10;
  bStart.Left := Form1.Width - bStart.Width - 10;
end;

end.

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

На мой взгляд, важный момент здесь – это неявное управление транзакциями, которое обусловлено установкой флага

  ZConn.AutoCommit := True;

Неявное управление транзакциями подразумевает, что каждый выполняемый приложением запрос инициирует транзакцию, которая завершается автоматически после выполнения запроса. Однако дело в том, что TZConnection, имеет довольно оригинальную логику функционирования, о которой имхо необходимо упомянуть:

Далее привожу перевод поста на эту тему с lazarus.freepascal.org:

На самом деле свойство AutoCommit работает следующим образом:

Когда AutoCommit имеет значение true, после выполнения каждого запроса транзакции подтверждаются в автоматическом режиме. Чтобы избежать автоматического подтверждения транзакции, вы можете явно вызвать инструкцию StartTransaction. В этом случае транзакция будет завершена после явного вызова Commit.

Когла AutoCommit имеет значение false вы не должны вызывать StartTransaction. То есть транзакции стартуют автоматически, но не завершаются после выполнения каждой инструкции.

Перевод документации по StartTransaction c zeos.firmos.at:

procedure StartTransaction: В подключенной базе данных процедура StartTransaction стартует новую транзакцию. Эта инструкция может быть использована, только если свойство AutoCommit имеет значение TRUE. В противном случае при вызове StartTransaction будет создаваться исключительная ситуация SInvalidOpInNonAutoCommit. Такая логика работы обуславливается тем, что StartTransaction предназначена для корректировки поведения приложения в режиме AutoCommit. При вызове StartTransaction, режим AutoCommit выключается, затем после вызова Commit или Rollback, режим AutoCommit включается снова.

Если значение свойства AutoCommit установлено в false, новые транзакции создаются автоматически, а вы можете принимать решение, как они должны быть завершены (Commit и Rollback).

Короче говоря, вопрос управления транзакциями – это тема для некоторого изучения и экспериментов. Чуть попозже я думаю провести их и разместить здесь результаты.

До новых встреч в эфире;-)

2 комментария:

  1. Коллега, это здорово, но не пробовали ли вы что-то другое? Все-таки, zeos требует libmysql.dll, а стало быть, лицензиционно не совсем чисто это. Я, к сожалению, начинающий, нашел только альтернативу DirectMysqlObjects, но его и к лазарусу как-то не очень подключить, и главное - к mysql 5.6. Грит, сервер требует более другую авторизацию... Буду очень рад, если найдете время обьяснить чайнику. llf@hotbox.ru

    ОтветитьУдалить
  2. Уточнение, проблема с обрезкой строк сохранилась и при использовании TMySQL56Connection, поэтому совет с размером полей актуален и для этого случая.

    ОтветитьУдалить