Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Polish

Spis treści

Table of Contents
exclude.*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.

Code Block
languagejava
titleKlasa 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). 

Info
titleKonwencje

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.

Info
titleUwaga

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:

Code Block
languagejava
titleUż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

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

Code Block
languagejava
titlePhoneDaoImpl
@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:

Code Block
languagejava
titlePhoneService
public interface PhoneService
    extends EditableService<Phone, Long>
{
}
Code Block
languagejava
titlePhoneServiceImpl
@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.

Code Block
titlePrzykł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);
Warning
titleUwaga

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.

Code Block
languagejava
titleUż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:

 

English

Table of contents

Table of Contents
exclude.*Spis treści.*

Introduction

Sometimes a project requires that you store additional information in the database. This usually requires the creation of a table, and the creation of classes to manage this table. Currently, the best and recommended way to solve such a problem is to create a class representing a table in the database, and map this class using the Hibernate library. In general, we can implement the mapping through a .hbm.xml mapping file or through an annotation. It is recommended to use annotation due to the fact that we do not have to change the system file hibernate.cfg.xml. The operations performed on the created objects are typically adding, deleting, modifying and searching therefore, to eliminate the need to implement methods to enable the above-mentioned operations, several mechanisms are available in the API to facilitate working on custom database objects. In this chapter, I will present the available ways to manage your own database objects.

Example of mapping

Suppose a customer wants to store information about phones in his company. We will create an example Phone class, which we will use in the following examples. The class also includes mappings.

Code Block
languagejava
titlePhone class
@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;
    }
}

The @Entity annotation means that the class will be mapped to a table in the database and will be scanned by Hibernate during system startup. In @Table we specify the name of the table(see Conventions). The @SequenceGenerator annotation allows you to create a sequencer. The name parameter must be unique within the entire system, so it should be set the same as sequenceName(the name of the sequencer in the database). At least one field marked @Id is required in each mapped class, which will become the primary key of the relationship. The @GeneratedValue allows you to bind a previously created sequencer to a field(the binding is done using the name paremeter, not sequenceName).

Info
titleConventions

In PlusWorkflow, each newly created table should have the prefix 'pm_'. Also remember that the table and seqencer name should not be too long(30 characters max), as some databases do not allow long table names. Next we can define additional mapping options. In the example, we have a link to the usertable table via the owner field. The number field will also be mapped(a column will be created in the pm_phone table). If we want to omit a field then we use @Transient.

Info
titleAttention

Any class marked @Entity must be in the com.suncode.pwfl package tree

 

CustomService

The easiest and fastest way to manage database objects is to use the CustomService class. Let's look at an example:

Code Block
languagejava
titleCustomService usage
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() );
}

First, we need to download the appropriate service, as parameters we transfer our class and the class of the field of which it is the primary key. The service class created in this way provides many useful functions to manage our object. In the example, we save the new phone in the database, and then load the list of all phones from the pm_phone table.

 

Service (advanced)

If we have a more complex problem and additional actions should be called during database operations or the execution of operations on our object is more complex then we can create our own Service class. In order to create the Service class, we need to build the whole mechanism on which consists of two layers - DAO and Service. The whole thing will contain two interfaces and two implementation classes. Let's start with the DAO layer

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

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

The important thing here is that in the DAO layer we do not deal with transactions, i.e. when calling a DAO function, the transaction must be started (that's why we need a Service class). In DAO we can add our own functions that perform some specific database operations (we do not implement business logic, validation, etc. here). From the superclass we get many useful methods, and as you can see above we can retrieve a session. If our service is to allow only read operations we can use BaseDao and HibernateBaseDao instead of EditableDao and HibernateEditableDao respectively.

Service Layer:

Code Block
languagejava
titlePhoneService
public interface PhoneService
    extends EditableService<Phone, Long>
{
}
Code Block
languagejava
titlePhoneServiceImpl
@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();//both methods are performed in a single transaction
		super.save(phone);
	}
}
 

It is always mandatory for us to write a setDao method, which, combined with the @Autowired annotation, will load the appropriate implementation into our dao object. Remember to add the @Transactional annotation to the functions where we perform database operations.

Where the type Long is used in the DAO and in the Service it is because that is the type of the field that is the Id in the model (here the Phone class). If the type of the field which is the Id will be different then, similarly, you also need to use a different type in DAO and Service.

Code Block
titleExamples of how to retrieve instances of the class we created
@Autowired
PhoneService ps;//works only in classes managed by the application context
 
PhoneService ps=SpringContext.getBean("phoneService");
PhoneService ps=SpringContext.getBean(PhoneService.class);
Warning
titleAttention

If there are several classes in the system marked with the same name, for example. @Service( "phoneService" ) then the system will not know which implementation to match therefore only the last of the above will work.

Code Block
languagejava
titlePhoneService usage
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() );
}


Useful resources: