Как известно, Marshmallow это ORM библиотека для Delphi. Она входит в состав Spring4D – потрясающей библиотеки коллекций, Dependency Injection и др.
Решил поделиться первым опытом работы с библиотекой Marshmallow в своем проекте, который я делаю на основе своего RobustServer – моего open source шаблона высоко нагруженного сервера. Итак, решил я сделать CRUD таблицы users из БД при помощи Marshmallow. И вот, что у меня получилось.
Модель TUser была создана при помощи генератора классов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
unit uUsers; interface uses Spring.Persistence.Mapping.Attributes, Spring.Persistence.Core.Graphics, Spring; type [Entity] [Table('users', '')] TUser = class private FId: Nullable<Int64>; FEmail: Nullable<string>; FPhone: Nullable<string>; FPassword: Nullable<string>; FRoles_id: Integer; public [AutoGenerated] [Column('id',[cpPrimaryKey,cpDontUpdate],10,0)] property Id: Nullable<Int64> read FId write FId; [Column('email',[],45)] property Email: Nullable<string> read FEmail write FEmail; [Column('phone',[],45)] property Phone: Nullable<string> read FPhone write FPhone; [Column('password',[],45)] property Password: Nullable<string> read FPassword write FPassword; [Column('roles_id',[cpRequired,cpNotNull],10,0)] property Roles_id: Integer read FRoles_id write FRoles_id; end; implementation end. |
Сам серверный класс выглядит вот так
1 2 3 4 5 6 7 8 9 10 11 12 13 |
TRPUsers = class(TRP) private procedure UserDataToJson(aUser: TUser; aJson: ISuperObject); public constructor Create(aContext: TIdContext; aRequestInfo: TIdHTTPRequestInfo; aResponseInfo: TIdHTTPResponseInfo); overload; override; procedure Create(); overload; override; procedure Delete(aId: string); override; procedure Update(); override; procedure GetUserById(aId: string); procedure GetUserByEmail(aEmail: string); procedure GetUserByPhone(aPhone: string); end; |
Методы выглядят довольно элегантно.
Create
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure TRPUsers.Create(); var u: TUser; json: ISuperObject; begin u := TUser.Create(); try DB.Connect(); u.Roles_id := 2; DB.Session.Save(u); json := SO(); json.I['id'] := DB.Session.ExecuteScalar<Integer>('SELECT last_insert_id() as lastID', []); FResponses.OkWithJson(json.AsJSon(false, false)); finally u.Free(); end; end; |
Update
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
procedure TRPUsers.Update(); var id, email, phone, password, roles_id: string; user: TUser; begin id := Common.GetValueIfNotEmpty(FParams.Values['id'], 'id param is empty'); DB.Connect(); user := DB.Session.FindOne<TUser>(id.ToInteger()); // trying to find it in db if user = nil then user := TUser.Create(); try user.Id := id.ToInteger(); if FParams.Values['email'] <> '' then user.Email := FParams.Values['email']; if FParams.Values['phone'] <> '' then user.Phone := FParams.Values['phone']; if FParams.Values['password'] <> '' then user.Password := FParams.Values['password']; if FParams.Values['roles_id'] <> '' then user.Roles_id := FParams.Values['roles_id'].ToInteger(); Db.Session.Update(user); finally user.Free(); end; Responses.OK(); end; |
Delete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
procedure TRPUsers.Delete(aId: string); var u: TUser; begin DB.Connect(); u := DB.Session.FindOne<TUser>(aId.ToInteger()); if u <> nil then try DB.Session.Delete(u); Responses.OK(); finally u.Free(); end else FResponses.Error('no such user'); end; |
Ну и методы для получения информации о сущности по уникальным идентификаторам
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
procedure TRPUsers.GetUserById(aId: string); var id: string; user: TUser; json: ISuperobject; begin DB.Connect(); json := SO(); user := DB.Session.FindOne<TUser>(aId.ToInteger()); try UserDataToJson(user, json); finally user.Free(); end; Responses.OkWithJson(json.AsJSon(false, false)); end; procedure TRPUsers.GetUserByEmail(aEmail: string); var json: ISuperObject; users: IList<TUser>; user: TUser; begin DB.Connect(); user := nil; users := Db.Session.GetList<TUser>('select*from users where email = :0', [aEmail]); user := users.FirstOrDefault(); json := SO(); // answer UserDataToJson(user, json); FResponses.OkWithJson(json.AsJSon(false, false)); end; procedure TRPUsers.GetUserByPhone(aPhone: string); var json: ISuperObject; users: IList<TUser>; user: TUser; begin DB.Connect(); user := nil; users := Db.Session.GetList<TUser>('select*from users where phone = :0', [aPhone]); user := users.FirstOrDefault(); json := SO(); // answer UserDataToJson(user, json); FResponses.OkWithJson(json.AsJSon(false, false)); end; |
Впечатления положительные. Абстракция кода стала более высокой. Были некоторые мелкие проблемы, например я делал соединение вот так..
1 2 3 4 5 |
// Marshmallow connection, add Spring.Persistence.Adapters.FireDAC to units to register fireDAC Conn FConnection := TConnectionFactory.GetInstance(dtFireDAC, FDConnection); FConnection.AutoFreeConnection := True; FConnection.QueryLanguage := qlMySql; FSession := TSession.Create(FConnection); |
Но не добавил в uses модуль
1 |
Spring.Persistence.Adapters.FireDAC |
Из за этого в словарь не добавлялось нужное значение и код вылетал на первой строчке, то есть все компилировалось, но при попытке исполнить код, вылетала ошибка. Методом дебага удалось раскопать в чем было дело. Код соединения с БД стал выглядеть так…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
function TDB.Connect: boolean; var oParams: TStrings; begin oParams := TStringList.Create; try oParams.Add('DataBase=coffeetest_db'); oParams.Add('Password=masterkey'); oParams.Add('User_Name=root'); oParams.Add('Port=3306'); oParams.Add('Server=localhost'); oParams.Add('CharacterSet=utf8'); // oParams.Add('Pooled=true'); FDConnection.Params.Assign(oParams); FDConnection.DriverName := 'MySQL'; //Пробуем подключиться try FDConnection.Connected := true; Q.Connection := FDConnection; Result := FDConnection.Connected; // Marshmallow connection, add Spring.Persistence.Adapters.FireDAC to units to register fireDAC Conn FConnection := TConnectionFactory.GetInstance(dtFireDAC, FDConnection); FConnection.AutoFreeConnection := True; FConnection.QueryLanguage := qlMySql; FSession := TSession.Create(FConnection); except on E: EFDDBEngineException do case E.Kind of ekUserPwdInvalid: // user name or password are incorrect raise Exception.Create('DBConnection Error. User name or password are incorrect' + #13#10 + #13#10 + E.ClassName + ' поднята ошибка, с сообщением : ' + E.Message); ekUserPwdExpired: raise Exception.Create('DBConnection Error. User password is expired' + #13#10 + #13#10 + E.ClassName + ' поднята ошибка, с сообщением : ' + E.Message); ekServerGone: raise Exception.Create('DBConnection Error. DBMS is not accessible due to some reason' + #13#10 + #13#10 + E.ClassName + ' поднята ошибка, с сообщением : ' + E.Message); else // other issues raise Exception.Create('DBConnection Error. UnknownMistake' + #13#10 + #13#10 + E.ClassName + ' поднята ошибка, с сообщением : ' + E.Message); end; on E: Exception do raise Exception.Create(E.ClassName + ' поднята ошибка, с сообщением : ' + #13#10 + #13#10 + E.Message); end; finally FreeAndNil(oParams); end; end; |