В этом посте я расскажу о своем первом опыте по работе с базами данных в 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).
Короче говоря, вопрос управления транзакциями – это тема для некоторого изучения и экспериментов. Чуть попозже я думаю провести их и разместить здесь результаты.
До новых встреч в эфире;-)
Коллега, это здорово, но не пробовали ли вы что-то другое? Все-таки, zeos требует libmysql.dll, а стало быть, лицензиционно не совсем чисто это. Я, к сожалению, начинающий, нашел только альтернативу DirectMysqlObjects, но его и к лазарусу как-то не очень подключить, и главное - к mysql 5.6. Грит, сервер требует более другую авторизацию... Буду очень рад, если найдете время обьяснить чайнику. llf@hotbox.ru
ОтветитьУдалитьУточнение, проблема с обрезкой строк сохранилась и при использовании TMySQL56Connection, поэтому совет с размером полей актуален и для этого случая.
ОтветитьУдалить