joi, 30 octombrie 2008

Oracle si functii ce returneaza mai multe inregistrari

Aceasta ideea mi-a venit intrucat am folosit destul de mult serverul de baze de date opensource PostgreSQL. In Postgres exista posibilitatea ca o functie sa apara in clauza from a unui select, fapt ce are mari avantaje. Acest lucru poate fi realizat si in oracle. Daca va intrebati de ce am avea nevoie de asa ceva, exemplul de mai jos ar trebui sa va lamureasca.

EX: Presupun ca folosesc un sistem de BI(Business Intelligence) pentru a realiza anumite rapoarte. Birt, Oracle Reports, Crystal Reports, etc. Este cunoscut faptul ca exista probleme de integrare intre Crystal Report in Eclipse si proceduri stocate ce returneaza cursoare.

Intrucat pot folosi functii in clauza from a unui select nu am decat sa scriu ca model al datelor pentru raport un query in loc de o procedura stocata. In realitate voi folosi tot functia stocata astfel incat decuplez logica din baza de date de partea de client.

Rezolvarea pentru problema descrisa consta in rezolvarea functiilor pipelined. Vom lua un caz simplu in care presupun ca am o tabela persoane(cnp, nume, prenume) si as vrea o functie care sa returneze toate inregistrarile.

Pasul 1:
Declar un tip persoana:
create or replace type tp_Persoana is object(CNP NUMBER,
Nume VARCHAR2(200),
Prenume VARCHAR2(200));


Pasul 2:
Declar un tip tabel de tp_Persoana.

create or replace type tp_TblPersoana is table of tp_persoana index by binary_integer;

Pasul 3:
Creez functia stocata:

create or replace function fc_TestPersoane return tp_tblpersoana pipelined
as
cursor c is select * from persoane;
rec tp_Persoana;
begin
OPEN c;

rec := tp_Persoana(null, null, null);

LOOP
FETCH c INTO rec.cnp, rec.nume, rec.prenume;
exit when c%notfound;

pipe row(rec);
END LOOP;

CLOSE c;
return;
end fc_TestPersoane;


Pasul 4:

In fine ultimul pas este apelul propriu-zis.

SELECT * FROM table(fc_TestPersoane());

Cateva observatii:

Aceasta facilitate este extrem de utila si de flexibila. Totusi trebuie sa o folositi doar in cazuri in care nu aveti alta solutie intrucat se obtine o utilizare suplimentara a memorie prin deschiderea cursorului si parcurgerea lui. Sper ca acest post sa fie util.

Cosnita Radu Viorel!

marți, 28 octombrie 2008

Hibernate si chei composite

Hibernate este un ORM extrem de utilizat in zilele noastre. In ultimele versiuni a aparut si Hibernate Annotations. In aceasta maniera se reuseste o uniformizare a framework-ului si o apropiere de JPA. Se foloseste Hibernate in Java pentru a mapa entitati(obiecte) la tabele din baza de date. In continuare prezint o modalitatea de a manipula o relatie many-to-many.


Problema intalnita este enuntata in cele ce urmeaza: am o relatie many-to-many intre un pachet de gazduire si tehnologie. Entitatea TehnologiiPachet, rezultata este implementata prin hibernate. Este evident faptul ca pentru aceasta entitate, cheia primara va fi: (pachet, tehnologie) => intr-un pachet nu pot sa am aceeasi tehnologie de mai multe ori. In continuare postez codul pentru aceste entitati precum si pentru cheia primara:

@Entity
@Table(name="tblpachete")
public class PachetGazduire
{
@Id
private int id;

/* aici vin implementate alte proprietati si metodele getter/setter */
@OneToMany(fetch=FetchType.EAGER, mappedBy="id.pachet")
private ArrayList tehnologii;
}


@Entity
@Table(name="tbltehnologii")
public class Tehnologie
{
@Id
private int id;

/* aici vin implementate alte proprietati si metodele getter/setter */
}



/* aici implementez cheia primara compozita */
@Embeddable
public class TehnologiiPachetPK
{
@ManyToOne
@JoinColumn(name="fk_pachet", referencedColumnName="id")
private PachetGazduire pachet;


@ManyToOne
@JoinColumn(name="fk_tehnologie", referencedColumnName="id")
private Tehnologie tehnologie;

/* aici vin implementate alte proprietati si metodele getter/setter */
}



/* aici vine entitatea de legatura */
@Entity
@Table(name="tbltehnologiipachet")
public class TehnologiiPachet
{
@Id
private TehnologiiPachetPK id;

int laCerere;
int nrConturi;
int pretLaCerere;

/* aici vin implementate alte proprietati si metodele getter/setter */
}


Acesta este codul care poate fi testat. In principiu, tabelele care se ascund in spatele acestor entitati pot fi generate relativ usor. O explicatie ce cred ca este binevenita: fetch=FetchType.EAGER. In mod implicit, hibernate face instantiere lazy. Asta inseamna ca vectorul de TehnologiiPachet ar fi incarcat doar daca incerc sa apelez un element din el. Acest caz presupune ca sesiune de hibernate ramane deschisa. Intr-o aplicatie de desktop acest lucru functioneaza, dar pentru o aplicatie de web nu mai este viabil. Din acest motiv folosesc o incarcare EAGER. Asta imi permite ca la orice moment ulterior incarcarii din baza de date accessare unei tehnologii din vector.

Cam atat pentru acum. Voi reveni cu noi solutii pentru probleme mai speciale.

Cosnita Radu!

vineri, 24 octombrie 2008

Accesare webservice din oracle

Aceasta problema mi se pare extrem de interesanta, dar foarte putin documentata pe internet. Am avut nevoie de asa ceva la unul din taskurile pe care trebuie sa le fac astazi pentru un proiect. Pentru rezolvarea problemei a trebuit intai sa creez un serviciu web de test. Intrucat este extrem de usor de creat si de publicat utilizand tehnologia J2EE am ales aceasta cale. Presupunand ca utilizati eclipse, creati un modul ejb. Dupa ce ati creat acest modul, adaugati urmatoarele clase/interfete in el:

ro.anaf.ws.HelloWorldWS

package ro.anaf.ws;

import javax.ejb.Remote;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;

@WebService
@SOAPBinding(style = Style.RPC)
@Remote
public interface HelloWorldWS
{
@WebMethod
public String helloWorld();

@WebMethod
public String receiveBinary(String s);
}


ro.anaf.ws.HelloWorldWSBean

package ro.anaf.ws;

import javax.ejb.Stateless;
import javax.jws.WebService;

@Stateless
@WebService(endpointInterface = "ro.anaf.ws.HelloWorldWS")
public class HelloWorldWSBean implements HelloWorldWS
{
public String helloWorld()
{
return "Salutari de pe WS";
}

public String receiveBinary(String s)
{
return "Am primat binarul: \"" + s + "\"";
}
}

Se poate remarca usor ca interfata reprezinta de fapt endpointul pentru serviciul web de test in timp ce clasa reprezinta implementarea operatiilor din endpoint. In acest moment serviciul web este implementat. Pentru a face deploy nu ramane decat sa creati un nou proiect de tip Enterprise Application si sa alegeti un server pe care sa faceti deploy. Personal, am folosit JBoss 4.2.3(ultima versiune stabila la aceasta data), dar puteti folosi orice alt container doriti. Dupa ce ati facut deploy la proiect puteti folosi adresa: http://localhost:8080/jbossws pentru a vizualiza lista de servicii web existente pe acest server. Ar trebui sa localizati si serviciul web pe care l-ati creat. In acest moment partea de java este incheiata.



Discutam acum de partea de oracle. Pentru inceput va trebui sa downloadati dbws-callout-utility-10131.zip. Dupa ce ati downloadat aceasta arhiva dezarhivati-o. In functie de versiunea utilizata va trebui sa executati una din comenzile de mai jos:
# 10gR2
loadjava -u scott/tiger -r -v -f -genmissing dbwsclientws.jar dbwsclientdb102.jar
# 11g
loadjava -u scott/tiger -r -v -f -genmissing dbwsclientws.jar dbwsclientdb11.jar
Obs: daca serverul nu ruleaza pe aceeasi masina pe care faceti development este posibil sa nu aveti utilitarul loadjava. Acesta este inclus in pachetul de developer ce poate fi downloadat gratuit de pe site-ul http://www.oracle.com.

Dupa ce ati executat pasii de mai sus, nu mai trebuie decat sa rulati scriptul urmator care realizeaza un test pentru metoda helloWorld:

declare
webservice utl_dbws.service;
webserviceCall utl_dbws.call;

wsdlURL VARCHAR2(1000) := 'http://10.18.14.32:8080/TestareServiciiEAR-ServiciiWeb/HelloWorldWSBean?wsdl';
webserviceNS VARCHAR2(1000) := 'http://ws.anaf.ro/';
webserviceName utl_dbws.qname;
webservicePort utl_dbws.qname;
webserviceOperation utl_dbws.qname;
params utl_dbws.anydata_list;
results anydata;
begin
webserviceName := utl_dbws.to_qname(webserviceNS, 'HelloWorldWSBeanService');
webservicePort := utl_dbws.to_qname(webserviceNS, 'HelloWorldWSBeanPort');
webserviceOperation := utl_dbws.to_qname(webserviceNS, 'helloWorld');

webservice := utl_dbws.create_service(wsdl_document_location => HTTPURITYPE(wsdlURL),
service_name => webservicename);

webserviceCall := utl_dbws.create_call(service_handle => webservice,
port_name => webservicePort,
operation_name => webserviceOperation);

results := utl_dbws.invoke(call_handle => webserviceCall, input_params => params);

utl_dbws.release_service(webservice);
utl_dbws.release_call(webserviceCall);

DBMS_OUTPUT.PUT_LINE(ANYDATA.AccessVarchar2(results));
end;


Setarile variabilelor: wsdlURL si webserviceNS le gasiti in wsdl. La fel si numele serviciului si numele portului. La executia blocului pl/sql de mai sus ar trebui sa primiti mesajul: 'Salutari de pe WS'.
La cele mentionate anterior mai exista un caz posibil care ar putea aparea: transmiterea parametrilor catre operatia serviciului web. Pentru a putea transmite parametrii trebuie sa utilizati vectorul params. De exemplu, pentru a adauga un parametru:
params(0):=ANYDATA.ConvertVarchar2('Cosnita Radu Viorel');
Pentru mai multe informatii consultati documentatia pentru ANYDATA.
Cam asta ar fi pentru azi. Nu ezitati sa adaugati comentarii despre acest articol.

Radu!

joi, 23 octombrie 2008

Despre ce va fi postat

Acesta este primul meu blog precum si primul meu post. Consider ca este momentul sa scriu despre anumite probleme pe care le intampin in domeniul IT, sperand ca rezolvarile propuse sa ajute si alti oameni din aceeasi industrie(stresanta si enervanta). Acestea fiind spuse, este timpul sa ma intorc la treaba ....... In cel mai scurt timp voi incepe sa postez probleme si rezolvari gasite.