Opakowanie dostepu do bazy we własne klasy

0

Witam. Programuję w Javie, ale wydaje mi się, że pytanie jest na tyle ogólne, że dotyczy w praktyce wielu języków, więc umieszczam je tutaj.

Piszę aplikację desktopową, która będzie korzystać z wbudowanej bazy danych.
Chciałbym wiedzieć, jak profesjonaliści opakowują dostęp do bazy danych we własne klasy.

W internecie znalazłem coś takiego http://www.sitepoint.com/article/java-6-steps-mvc-web-apps/

Fragment kodu, na który zwróciłem uwagę:

package com.sitepoint; 

public class ToDoItem { 
 private int id; 
 private String item; 

 ToDoItem(int id, String item) { 
   this.id = id; 
   this.item = item; 
 } 

 public int getId() { 
   return id; 
 } 

 public String getItem() { 
   return item; 
 } 

 public String toString() { 
   return getItem(); 
 } 
}

Przedstawiony kod pokazuje jak pracować na niemodyfikowalnych rekordach.
Wszystko fajnie, tylko w moim projekcie muszę aktualizować rekordy. Muszę modyfikować dane w nich zawarte. Jak to opakować w ładny interfejs?

Nasunął mi się pomysł z dorobieniem setterów, ale wydaje mi się, że to będzie zły pomysł, gdyż obiekt, który będę modyfikował przez użycie jego metody set będzie musiał znać szczegóły implementacji, sam będzie musiał grzebać w bazie, a w dodatku może być to niewydajne.

Niewydajne, gdyż jesli będe wywoływał kilka setterów i każdy będzie zmieniał jedną rzecz w rekordzie, to będzie to wolniejsze, niż gdybym zrobił to jednym poleceniem w bazie.

Mam nadzieję, że nie namotałem i że da się zrozumieć moją wypowiedź.
Więc jak ładnie opakować w klasy dostęp do bazy danych?

0

Zainteresuj sie Hibernate, czyli mapowaniem obiektowo-relacyjnym. Za jego pomoca dostaniesz obiekty mapowane w baze danych i nie bedziesz sie musial martwic szczegolami dostepu do samej bazy. Co do innych jezykow to istnieja porty Hibernate np. na C#, itp.

0

Przyznam, że siedzę na razie w J2SE i J2ME i trochę się boję wnikać w ten "straszny" świat J2EE. Nie ma jakiś normalnych sposobów? Z żadnymi frameworkami pokroju Hibernate nie miałem jeszcze nigdy doczynienia.

0

No ale chciales wiedziec jak to robia profesjonalisci :P Profesjonalisci uzywaja np. Hibernate. To zaden wielki czolg, wez pierwszy przyklad z manuala i zobacz jak to wyglada. To ma ulatwiac zycie, nie utrudniac. Zwykle takie frameworki sluza do tego, zeby kod miescil sie w paru linijkach i zeby przeczytanie i zrozumienie go zajmowalo 1s ;)

0

No właśnie niefajnie to wyglada dla mnie. XML mnie przeraża xD Chetnie nauczę się Hibernate, ale w przyszłości, a teraz chciałbym się zabrać za tą aplikację i nie mogę sobie pozwolić na naukę nowej technologii :( Programiści musieli sobie jakoś radzić zanim powstał Hibernate ;) Np. tacy programiści C++, którzy nie mają takich fajnych frameworków też muszą sobie jakoś radzić ;P

0

Nie jestem profesjonalistą, ale zrobiłem to następująco :

Celem moim było mapowanie automatyczne mapowanie obiektów jako parametry przy wywoływaniu procedur składowanych na serwerze MSSQL 2005 server.

Kilka założeń (które sam sobie postawiłem):

  • mapowanie musi być automatyczne (pole/właściwość -> kolumna)
  • będzie można wyłączyć określone pola z mapowania
  • będzie można modyfikować "klucz" mapowania - to znaczy nazwa pola != nazwa kolumny, jeśli taka taka potrzeba.

Język C#. Całość napisałem osobiście, gdy Linq2SQL jeszcze był w powijakach.

Najpierw silnik mapowania, czyli dwie klasy: DBEntityTools (przydatne funkcje) oraz DBEntityAttribute - atrybut, który nadajemy polom, aby mapować.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Reflection;

using System.Data.SqlClient;

namespace HAKGERSoft.Common {

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property , AllowMultiple = false)]
    public class DBEntityAttribute : Attribute {
        public DBEntityAttribute(): this(null) {
        }

        public DBEntityAttribute(string map) {
            this.Map = map;
        }

        protected string _Map;
        public string Map {
            get {
                return this._Map;
            }
            private set {
                this._Map = value;
            }
        }
    }

    public static class DBEntityTools {

        public static Dictionary<string,MemberInfo> MapEntity<T>(SqlDataReader sdr) {
            Dictionary<string, MemberInfo> map = new Dictionary<string, MemberInfo>();
            foreach(MemberInfo member in GetEntityMembers<T>()) {
                string path = Map(member);
                try {
                   sdr.GetOrdinal(path);
                   map[path] = member;
               }
               catch(IndexOutOfRangeException) { 
                   // cannot map
               }
            }
            return map;
        }

        public static MemberInfo[] GetEntityMembers<T>() {
            return typeof(T).GetFields().Concat<MemberInfo>(typeof(T).GetProperties()).Where(x =>
                x.IsDefined(typeof(DBEntityAttribute), false)).ToArray();
        }

        public static string Map(MemberInfo member) {
            DBEntityAttribute dbe = (DBEntityAttribute)member.GetCustomAttributes(typeof(DBEntityAttribute), false).First();
            return (dbe.Map!=null) ? dbe.Map : member.Name;
        }





    }
}

W drugim pliku, klasa DBUtility (nieco obszerniejsza), mam metody, która mapuje:

// DB -> object
 static IEnumerable<T> CreateCommonResult<T>(SqlDataReader sdr, bool close) where T: new() {
            while(sdr.Read()) {
                T res = new T();
                foreach(KeyValuePair<string, MemberInfo> pair in DBEntityTools.MapEntity<T>(sdr)) {
                    object chunk = sdr[pair.Key];
                    if(chunk!=DBNull.Value) {
                        if(pair.Value.MemberType==MemberTypes.Field)
                            ((FieldInfo)pair.Value).SetValue(res, chunk);
                        else if(pair.Value.MemberType==MemberTypes.Property && ((PropertyInfo)pair.Value).CanWrite)
                            ((PropertyInfo)pair.Value).SetValue(res, chunk, null);
                    }
                }
                yield return res;
            }
            if(close)
                sdr.Close();
            else
                sdr.NextResult();
        }
// object -> DB
static SqlCommand CreateCommon<T>(CommandType cmdType, SqlCommand cmd, T t) {
            cmd.CommandType=cmdType;
            foreach(MemberInfo member in DBEntityTools.GetEntityMembers<T>()) {
                object value = null;
                if(member.MemberType==MemberTypes.Field)
                    value = ((FieldInfo)member).GetValue(t);
                else if(member.MemberType==MemberTypes.Property)
                    value = ((PropertyInfo)member).GetValue(t, null);
                cmd.Parameters.AddWithValue(DBEntityTools.Map(member), SetObjectSafe(value));
            }
            return cmd;
        }

Przykład obiektu do mapowania:

public class UserInfo {
        public int Id; // nie mapowane - nie ma atrybutu DBEntity
        [DBEntity]
        public UserRange Range; // mapowanie na kolumne "Range"
        [DBEntity]
        public string Password; // mapowanie na kolumne "Password"
        [DBEntity]
        public string Language; // mapowanie na kolumne "Language"
        [DBEntity("Flags")]
        public UserFlags UserFlags; // mapowanie na kolumne "Flags", a nie "UserFlags"
}
0

Jako że nie znam API C# mam pytanie, co robi ta linijka:
cmd.Parameters.AddWithValue(DBEntityTools.Map(member), SetObjectSafe(value));

Generalnie ładnie to wygląda xD
Tylko jak się tym posługujesz? To znaczy załużmy, że chcesz zmodyfikowac jakiś rekord typu UserInfo. Robisz

userInfo.Password = "hasło";

i jak przekazujesz tą modyfikację do bazy?

0

Generalnie ładnie to wygląda xD
Tylko jak się tym posługujesz? To znaczy załużmy, że chcesz zmodyfikowac jakiś rekord typu UserInfo. Robisz
userInfo.Password = "hasło";
i jak przekazujesz tą modyfikację do bazy?

Nie jest to full automat, że zmieniasz dowolne pole, klikasz magiczne update i wszystko się ładnie zapisuje w bazie - nie ma to tak działać bynajmniej. Niemniej jednak, można tak zrobić oczywiście programując metodę Update.

Oto jak wykonuje:

using(SqlConnection sqlConn = DBLayer.CreateConnection()) {
sqlConn.Open();
SqlCommand cmd = DBUtility.CreateCommonProc<UserInfo>("AddSetUser", sqlConn, ui);
cmd.ExecuteNonQuery();
}

CreateCommonProc<> to kolejna funkcja z DBUtility:

 public static SqlCommand CreateCommonProc<T>(string proc, SqlConnection conn, T t) {
            return CreateCommon<T>(CommandType.StoredProcedure, new SqlCommand(proc, conn), t);
        }

Jako że nie znam API C# mam pytanie, co robi ta linijka:
cmd.Parameters.AddWithValue(DBEntityTools.Map(member), SetObjectSafe(value));

Wstawia parametr do obiektu typu SqlCommand (nazwę i wartość). SetObjectSafe to filtr, pomiędzy wartościami w C# a wartosciami SQL:

 public static object SetObjectSafe(object value) {
            if(value==null)
                return DBNull.Value;
            Type type = value.GetType();
            if(type.IsEnum)
                return (int)value;
            return value;
        }
0
using(SqlConnection sqlConn = DBLayer.CreateConnection()) {
                sqlConn.Open();
                SqlCommand cmd = DBUtility.CreateCommonProc<UserInfo>("AddSetUser", sqlConn, ui);<--- parametrem jest zmienna ui
cmd.ExecuteNonQuery();
}

A co jest przekazywane w zmiennej ui? Nie rozumiem także za bardo, czym jest "AddSetUser"?

Jeszcze wracając do mapowania, to czy stworzyłeś jakiś specjalny atrybut do oznaczania, która właściwość służy jako ID?

0

A co jest przekazywane w zmiennej ui? Nie rozumiem także za bardo, czym jest "AddSetUser"?

W zmiennej ui jest obiekt klasy UserInfo. Metoda generyczna CreateCommonProc<T> dzięki sparametryzowaniu typu jest w stanie odczytać pola.

"AddSetUser" to procedura składowalna MSSQL server 2005

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[AddSetUser]
	@Result uniqueidentifier output,	
	@Id uniqueidentifier,
	@PageId uniqueidentifier,
    @Name nvarchar(20),
    @Password nvarchar(50),
    @Language nchar(2),
    @Range smallint,
    @Flags int
AS
begin
  if exists (select Id from Users where Id=@id)
    update Users set 
		[Name]=isnull(@Name,[Name]),
		Password=isnull(@Password,Password),
		[Language]=isnull(@Language,[Language]),
		[Range]=isnull(@Range,[Range]),
		Flags=isnull(@Flags,Flags)
    where Id=@Id
  else
  begin
    if @id is null set @id=newid()
    insert Users values(
		@id,
		@Name,
		@Password,
		@Language,
		@Range,
		@Flags
	)
  end
  set @Result = @id;
end

Jeszcze wracając do mapowania, to czy stworzyłeś jakiś specjalny atrybut do oznaczania, która właściwość służy jako ID?

U mnie w aplikacji Id jest częscią Użytkownika,a nie klasy UserInfo, toteż nie wchodzi w mapowanie UserInfo. Id dodaje ręcznie w razie potrzeby przez Parameters.AddWithValue().

Gdy wykonywane jest Add, odczytuje nowe Id z wyjściowego parametru:

  @Result uniqueidentifier output,      

....

 return (Guid)cmd.Parameters["Result"].Value;

A więc ręcznie, nie oznaczam specjalnie pól jako bazodanowych PK.

0

A jak sobie radzisz, gdy jedna z właściwości danej klasy jest np. listą lub zbiorem? Jak ją zapisujesz do bazy?

0

Zbiór nie idzie w jedną kolumnę (chyba, że mapa bitowa, wtedy Enum z [Flags] i to jest już rozwiązane jak widzisz).

Nie bardzo rozumiem co masz na myśli, zbiór czego ? .. Podaj jakiś przykład.

0

Np. zbiór Stringów:
Set<String> set = new Set<String>();
który jest jedną z właściwości klasy.

Trzeba utworzyć do tego inną tabelę w bazie, ale czy masz jakiś atrybut, który by automatyzował takie mapowanie?

Tzn. masz załużmy UserInfo

public class UserInfo {
// właściwości...
   public Set<Item> Items;
}

czy masz jakiś atrybut, który wskazałby, w jakiej tabeli mają być przechowywane dane z pola Items?

0

Nie, takie coś to już ręcznie - nie mapuje "tabel".

Ale mam inne, przydatne funkcje do takich rzeczy ;-)

0

A gdyby stworzyć taką adnotację/atrybut.
Podejrzewam, że powinna wyglądać jakoś tak:

@DBSet(table = "UserInfo_Item")
public Set<Item> items;

Miałbym instancję klasy UserInfo i wprowadziłbym jakieś zmiany w zbiorze items:

userInfo.items.add(new Item());

Następnie chciałbym zapisać zmiany do bazy. Wywołałbym jakąś metodę zapisującą:

DBUtil.save(userInfo);

Teraz moje pytanie. Macie jakiś pomysł, jak efektywnie sprawdzić, jakie zmiany zostały dokonane w zbiorze i co zapisać do bazy?

1 użytkowników online, w tym zalogowanych: 0, gości: 1