Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 12 Current »

Spis treści

Wstęp

Czasem projekt wymaga, aby przechowywać w bazie danych dodatkowe informacje. Zwykle wymaga to utworzenia tabeli, oraz stworzenia klas do zarządzania tą tabelą. Obecnie najlepszym i zalecanym sposobem rozwiązania takiego problemu jest utworzenie klasy reprezentującej tabelę w bazie danych i mapowanie tej klasy za pomocą biblioteki Hibernate. Ogólnie mapowanie możemy realizować poprzez plik mapowania .hbm.xml lub poprzez adnotację. Zaleca się używanie adnotacji z uwagi na to, że nie musimy zmieniać pliku systemowego hibernate.cfg.xml. Operacje wykonywane na stworzonych obiektach to zazwyczaj dodawanie, usuwanie, modyfikacja i wyszukiwanie dlatego, aby wyeliminować konieczność implementacji metod umożliwiających wymienione operacje w API dostępnych jest kilka mechanizmów ułatwiających pracę na własnych obiektach bazodanowych. W tym rozdziale przedstawię dostępne sposoby zarządzania własnymi obiektami bazodanowymi.

Przykład mapowania

Załóżmy, że klient chce przechowywać informację o telefonach w swojej firmie. Stworzymy przykładową klasę Phone, którą wykorzystamy w kolejnych przykładach. W klasie są zawarte również mapowania.

Klasa Phone
@Entity
@Table( name = "pm_phone" )
@SequenceGenerator( name = "pm_phone_id_seq", sequenceName = "pm_phone_id_seq" )
public class Phone
{
	private Long id;
	private String number;
	private User owner;
	
	@Id
    @GeneratedValue( strategy = GenerationType.AUTO, generator = "pm_phone_id_seq" )
    public Long getId()
    {
        return id;
    }
    public void setId( Long id )
    {
        this.id = id;
    }
    public String getNumber()
    {
        return number;
    }
    public void setNumber( String number )
    {
        this.number = number;
    }
    @ManyToOne( fetch = FetchType.LAZY )
    @JoinColumn( name = "userid" )
    public User getOwner()
    {
        return owner;
    }
    public void setOwner( User owner )
    {
        this.owner = owner;
    }
}

Adnotacja @Entity oznacza, że klasa będzie mapowana na tabelę w bazie danych i podczas uruchomienia systemu zostanie przeskanowana przez Hibernate. W @Table podajemy nazwę tabeli(patrz Konwencje). Adnotacja @SequenceGenerator pozwala utworzyć sequencer. Parametr name musi być unikalny w ramach całego systemu dlatego należy go ustawiać tak samo jak sequenceName(nazwa sequencera w bazie danych). W każdej mapowanej klasie wymagane jest co najmniej jedno pole oznaczone @Id, które zostanie kluczem głównym relacji. @GeneratedValue pozwala powiązać utworzony wcześniej sequencer z polem(powiązanie odbywa się za pomocą paremetru name, a nie sequenceName). 

Konwencje

W PlusWorkflow każda nowo tworzona tabela powinna posiadać przedrostek 'pm_'. Należy również pamiętać, żeby nazwa tabeli i seqencera nie była zbyt długa(max. 30 znaków), ponieważ niektóre bazy danych nie pozwalają na tworzenie długich nazw tabel. Dalej możemy zdefiniować dodatkowe opcje mapować - zachęcam do zajrzenia w dokumentacje Hiberante. W przykładzie mamy powiązanie z tabelą usertable poprzez pole owner. Pole number również zostanie zmapowane(zostanie utworzona kolumna w tabeli pm_phone). Jeżeli chcemy pominąć jakieś pole to korzystamy z @Transient.

Uwaga

Każda klasa oznaczona @Entity musi znajdować się w drzewie pakiety com.suncode.pwfl

 

CustomService

Najprostszym i najszybszym sposobem zarządzania obiektami bazodanowymi jest użycie klasy CustomService. Spójrzmy na przykład:

Użycie CustomService
CustomService<Phone, Long> ps = ServiceFactory.getCustomService( Phone.class, Long.class );
Phone p = new Phone();
p.setNumber( "777 777 777" );        
ps.save( p );
List<Phone> phones = ps.getAll();
for ( Phone phone : phones )
{
    log.debug( phone.getNumber() );
}

Po pierwsze musimy pobrać odpowiednie serwis, jako parametry przekazujemy naszą klasę i klasę pola, której jest kluczem głównym. Tak stworzona klasa serwisu udostępnia wiele przydatnych funkcji pozwalających zarządzać naszym obiektem. W przykładzie zapisujemy nowy telefon w bazie danych, a następnie wczytujemy listę wszystkich telefonów z tabeli pm_phone.

 

Service (zaawansowany)

Jeżeli mamy bardziej złożony problem i podczas operacji na bazie danych powinny zostać wywołane dodatkowe akcje lub wykonanie operacji na naszym obiekcie jest bardziej złożone to możemy utworzyć własną klasę Service. W celu utworzenia klasy Service musimy zbudować cały mechanizm na, który składają się dwie warstwy - DAO i Service. Całość będzie zawierała dwa interfejsy i dwie klasy implementujące. Zacznijmy od warstwy DAO

PhoneDao
public interface PhoneDao
    extends EditableDao<Phone, Long>
{
	public void customMethod();
}

PhoneDaoImpl
@Repository( "phoneDao" )
public class PhoneDaoImpl
    extends HibernateEditableDao<Phone, Long>
    implements PhoneDao
{
	public void customMethod()
    {
        getSession().createSQLQuery( "..." );
    }
}

Ważne jest tutaj to, że w warstwie DAO nie zajmujemy się transakcjami tzn. podczas wywołania funkcji DAO transakcja musi być rozpoczęta(dlatego potrzebujemy klasy Service). W DAO możemy dodać własne funkcje które wykonują jakieś specyficzne operacje bazodanowe(nie implementujemy tutaj logiki biznesowej, walidacji itp.). Z nadklasy otrzymujemy wiele przydatnych metod, oraz jak widać powyżej możemy pobrać sesję. Jeżeli nasz serwis ma umożliwiać tylko operacje odczytu możemy zamiast EditableDao i HibernateEditableDao użyć odpowiedznio BaseDao i HibernateBaseDao.

Warstwa Service:

PhoneService
public interface PhoneService
    extends EditableService<Phone, Long>
{
}
PhoneServiceImpl
@Service( "phoneService" )
public class PhoneServiceImpl
    extends EditableServiceImpl<Phone, Long, PhoneDao>
    implements PhoneService
{
    @Autowired
    public void setDao( PhoneDao dao )
    {
        this.dao = dao;
    }


	@Override
    @Transactional
    public Long save( Phone phone )
    {
		dao.customMethod();//obie metody są wykonane w jednej transakcji
		super.save(phone);
	}
}
 

Zawsze obowiązkowo musimy napisać metodę setDao, która w połączeniu z adnotacją @Autowired spowoduje załadowanie odpowiedniej implementacji do naszego obiektu dao. Pamiętamy o dodaniu adnotacji @Transactional do funkcji, w których wykonujemy operacje bazodanowe.

Tam gdzie w DAO i w Service jest używany typ Long to wynika to z tego że takiego typu jest pole które jest Id w modelu (tu klasa Phone). Jeśli typ pola które jest Id będzie inne to analogicznie trzeba zastosować też inny typ w DAO i Service.

Przykładowe sposoby pobierania instancji utworzonej przez nas klasy
@Autowired
PhoneService ps;//działa tylko w klasach zarządzanych przez kontekst aplikacji
 
PhoneService ps=SpringContext.getBean("phoneService");
PhoneService ps=SpringContext.getBean(PhoneService.class);

Uwaga

Jeżeli w systemie istnieje kilka klas oznaczonych tą samą nawą np. @Service( "phoneService" ) to system nie będzie wiedział którą implementację dopasować dlatego zadziała tylko ostatni z powyższych sposobów.

Użycie PhoneService
PhoneService ps=SpringContext.getBean(PhoneService.class);
Phone p = new Phone();
p.setNumber( "777 777 777" );        
ps.save( p );
List<Phone> phones = ps.getAll();
for ( Phone phone : phones )
{
    log.debug( phone.getNumber() );
}


Przydatne zasoby:

 

  • No labels