poniedziałek, 17 listopada 2014

Czego nie robić w kodzie - switch + enum

Switch, czyli bardziej czytelna sterta ifów, która jest najczęściej łączona w javie z enumami. Switch sam w sobie nie jest niczym złym, tak samo jak if. Niestety bardzo często wpadamy w pułapkę, ponieważ odpowiedzialność switcha jest zbyt duża. Powstaje wtedy "switch'ologia" czyli kolejny sposób na sprawienie żeby nasz kod był coraz bardziej paskudny. Standardowe użycie switcha może wyglądać mniej więcej tak:
public enum DiscountType {

 NORMLAL, VIP, EMPLOYEE;

}
public int calculateDiscount(DiscountType discount) {

 switch (discount) {
 case NORMLAL:
  return 10;
 case VIP:
  return 30;
 case EMPLOYEE:
  return 40;
 default:
  throw new IllegalArgumentException();
 }
}
Na pierwszy rzut oka wszystko jest ok. Pojawia się tylko jedno małe pytanie: "Ile rzeczy w kodzie zależy od tego enuma? Ile takich switchów z tym enumem jest w kodzie?". Jeśli jest to prosta aplikacja, gdzie jest jedno miejsce to wszystko wydaje się ok, ale jeśli mamy taki kodzik w kilku miejscach to pojawia się problem z utrzymaniem takiego kodu ponieważ:
  • co zrobić jeśli do enuma dojdzie nowa wartość? Musimy wyszukać wszystkie switche i obsłużyć nową wartość, co jak się domyślacie na pewno spowoduje problemy. Może nie za pierwszym razem, może nie za drugim, ale kiedyś ktoś może czegoś nie dopilnować.
  • czemu dodanie czegoś do enuma musi skutkować zmianą kodu biznesowego aplikacji? To też może powodować problemy i jest kolejnym miejscem gdzie można popełnić błąd. Dodając nową wartość, musimy zobaczyć gdzie enum jest używany, co od niego zależy i teraz odpowiedzieć na pytanie - jak ten kod powinien się zachować przy nowych wartościach?
  • jak czytelny będzie nasz kod jeśli switch będzie użyty z dużym enumem (kilkanaście/dziesiąt elementów), a logika jaka będzie od niego zależeć będzie trochę bardziej skomplikowana? Pewnie wielu z was będzie uciekać wtedy w prywatne metody, ale to nie jest w 100% dobre wyjście w tym przypadku.
  • no i testy - ile testów potrzeba by taki kawałek kodu przetestować?
Pytanie więc - jak możemy zrobić to inaczej? Czy w ogóle potrzebujemy switcha? No właśnie, może switch nie jest nam potrzebny. Czemu nie skorzystać z możliwości jakie tkwią w enumie? Przecież to jest coś więcej niż tylko zbiór pewnych stałych. Wystarczy trochę chęci i nasze rozwiązanie może wyglądać tak:
public int calculateDiscount(DiscountType discount) {
 return discount.calculateDiscount();
}
public enum DiscountType {

 NORMLAL(10), VIP(30), EMPLOYEE(40);
 
 private int discountInPercent;
 
 DiscountType(int discountInPercent){
  this.discountInPercent = discountInPercent;
 }
 
 public int calculateDiscount() {
  return discountInPercent;
 }
}
Zastosowanie takiego rozwiązania dość mocno usprawnia nasz development. Nasz kod jest o wiele bardziej czytelny, a sam enum posiada jakieś biznesowe wartości, które nie są rozsmarowane po aplikacji. Wszystko w jednym miejscu. Ale pojawia się kolejne pytanie - co jeśli każdy element w enumie musi posiadać pewną logikę? Np. algorytm do wyliczenia zniżki będzie trochę bardziej skomplikowany? Pisanie przy każdym elemencie metody nie jest dobrym rozwiązaniem, ponieważ kod enuma stanie się mega nieczytelny. O wiele lepiej jest stworzyć jakiś wspólny interfejs. Najprostsza wersja tego rozwiązania może wyglądać w ten sposób:
public interface DiscountProvider {

 int calculateDiscount();
}
public class SimpleDiscountProvider implements DiscountProvider {

 private final int discountInPercent;
 
 public SimpleDiscountProvider(int discountInPercent) {
  super();
  this.discountInPercent = discountInPercent;
 }

 @Override
 public int calculateDiscount() {
  return discountInPercent;
 }
}
public enum DiscountType {

 NORMLAL(new SimpleDiscountProvider(10)), 
 VIP(new SimpleDiscountProvider(30)), 
 EMPLOYEE(new SimpleDiscountProvider(40));
 
 private DiscountProvider provider;
 
 DiscountType(DiscountProvider provider){
  this.provider = provider;
 }
 
 public int calculateDiscount() {
  return provider.calculateDiscount();
 }
}
Oczywiście każdy element może mieć swoją własną implementacje, które mogą być o wiele bardziej złożone, np:
public class VipDiscountProvider implements DiscountProvider {

 private DayHolder dayHolder;
 
 public VipDiscountProvider(DayHolder dayHolder) {
  this.dayHolder = dayHolder;
 }

 @Override
 public int calculateDiscount() {
  
  if(dayHolder.getSaleDay().contains(LocalDate.now())){
   return 50;
  }
  return 35;
 }

}

sobota, 23 sierpnia 2014

Typy generyczne w kontekście kolekcji

Typy generyczne w javie są powszechnie znane i używane. Praktycznie każdy programista, który używa javy, używa typów generycznych. Znaczna większość z nas wykorzystuje je świadomie, ale znajdą się też tacy, którzy używają ich przez przypadek, bo po prostu skopiowali przykład użycia z jakiegoś forum :). Dziś krótkie przypomnienie jak korzystać z typów generycznych w kontekście kolekcji.

Zaczniemy od najprostszego przykładu, czyli listy w której określamy jakie obiekty będą się tam znajdować. Taka konstrukcja sprawdza się w 99,9% sytuacj z jakimi mamy do czynienia na co dzień. Lista persons jest w pełni użyteczna, można do niej dodawać elementy, usuwać, pobierać i modyfikować. Jedyne na co nie pozwoli nam kompilator, to przypisanie jednej listy do innej, która zawiera typ pokrewny, np. listy teachers nie możemy przypisać do listy persons, mimo tego, że obiekt Teacher dziedziczy po Person. Poniżej kawałek kodu, który pokazuje to zachowanie.
 List<Person> persons = createList();
 persons.add(new Person());
 Person p = persons.get(0);

 List<Teacher> teachers = createList();
// persons = teachers; error
Oczywiście jeśli będziemy potrzebowali takiego przypisania, możemy lekko zmodyfikować nasz kodzik poprzez powiedzenie kompilatorowi, że nasza lista będzie zawierać obiekty, które dziedziczą po obiekcie Person. Po modyfikacji nasz kod prezentuje się następująco.
 List<? extends Person> persons = createList();
// persons.add(new Person()); error
 Person p = persons.get(0);
 
 List<Teacher> teachers = createList();
 persons = teachers;
Możemy teraz przypisywać listę do listy i nie mamy błędu, ale w momencie, gdy chcemy coś wstawić do listy persons dostajemy błąd kompilacji. Na początku może nam się wydawać, że jest to dziwne zachowanie, ale po dłuższym zastanowieniu uświadamiamy sobie, że jest to nawet logiczne :). Kompilator zgłosi błąd w tym przypadku dlatego, że nie może jednoznacznie stwierdzić, jaki typ znajduje się w liście. Wiadomo tylko, że lista zawiera obiekty, które będą dziedziczyć po Person. Żeby lepiej zrozumieć ten przypadek, napiszmy sobie przykładową implementację metody createList().
 private List<Teacher> createList() {
  List<Teacher> teachers = new ArrayList<>();
  teachers.add(new Teacher());
  return teachers;
 }
Metoda zwraca listę obieków Teacher i taką listę możemy z powodzeniem przypisać do listy persons. Teraz jeśli kompilator pozwoliłby nam dodać do listy jakikolwiek obiekt, który dziedziczy po Person to mielibyśmy niespójność, bo na liście, w której powinny znajdować się obiekty Teacher znalazłby się obiekt innego typu, np. Person.

Typy generyczne posiadają jeszcze jedną magiczną odsłonę, a mianowicie możemy zadeklarować listę z nadtypami pewnej klasy. Wygląda to mniej więcej tak:
 List<? super Person> persons = createList();
 persons.add(new Person());
// Person p = persons.get(0); error
Do listy, która jest zadeklarowana w ten sposób, możemy dodawać obiekty, które dziedziczą po Person, ale nie możemy z takiej listy pobierać elementów. Tutaj znowu musimy się chwilę zastanowić, ponieważ cała sytuacja jest dość dziwna i niecodzienna. Dla lepszego jej zrozumienia napiszmy sobie kolejną implementację metody createList().
 private List<Base> createList() {
  List<Base> bases = new ArrayList<>();
  bases.add(new Base());
  bases.add(new Teacher());
  return bases;
 }
Załóżmy, że klasa Teacher dziedziczy po Person, a klasa Person dziedziczy po Base. Metoda zwraca listę obiektów Base, w naszym przykładzie jest jeden obiekt Base i jeden Teacher. Taka lista może być przypisana do listy persons, ponieważ konstrukcja List<? super Person> zakłada, że możemy tutaj przypisać każdą listę, na której są obiekty znajdujące się w hierarchii dziedziczenia nad Person (Base znajduje się nad Person). Jeśli wstawimy do takiej listy obiekt Person to lista będzie cały czas spójna. Niestety taka lista nie nadaje się do odczytu elementów, ponieważ nie wiemy jakiego typu obiekty się tam znajdują, wiemy tylko, że typ Person jest nadtypem do typów na liście.

Jeśli macie jakieś przemyślenia albo pytania, to zachęcam do umieszczania ich w komentarzach.

piątek, 3 maja 2013

JSF2 - własne komponenty

JSF2 pozwalaja w bardzo szybki i prosty sposób tworzyć własne komponenty, dzięki czemu nie musimy powielać kodu w kilku miejscach na stronach xhtml'owych. Pytanie tylko kiedy warto poświęcić trochę czasu i stworzyć własny komponent? Oczywiście nie ma dobrej odpowiedzi na takie pytanie, wszystko zależy od tego jak dużo i jak długo będziemy dany system/aplikację utrzymywać. Pewnie znajdą się systemy w których stosowanie własnych komponentów jest niepotrzebne, jak i takie, gdzie przykrywa się standardowe jsf'owe kontrolki, własnymi, aby mieć większą kontrolę nad ich wyglądem/zachowaniem. Warto zwrócić również uwagę, że dobrze ułożone komponenty, nie tylko ułatwiają pisanie kodu, ale również jego czytanie.

Oto kilka przykładów, gdzie moim zdaniem warto się zastanowić nad stworzeniem własnego taga:
  • komponent pojawia się w wielu miejscach w systemie (np. inputy formularza, panele ze zdefiniowanym nagłówkiem, popup'y, itp.)
  • zestaw kontrolek do obsługi ValueObject tj. MoneyVO, PeselVO, PostCodeVO, itd.
  • bardzo złożony fragment kodu, który może i teoretycznie nie wymaga stworzenia własnego tag'a, ale dzięki czemu nasz kod stanie się o wiele bardziej czytelny

Jak stworzyć własny tag
Zaczynamy od stworzenia pliku, który będzie naszym szablonem. Definiujemy w nim wszystkie komponenty, które będą tworzyły nasz komponent.
Ważne fragmenty:
  • <ui:param> czyli możliwość definiowania własnych parametrów, dzięki którym nasz kod jest bardziej czytelny
  • <ui:insert> tag, który definiuje nam pewną sekcję, którą możemy wykorzystać/zastąpić w momencie wykorzystania naszego taga. Atrybut name defiuje nazwę sekcji.
  • #{name} zmienna name, która może zostać ustawiona w momencie wykorzystania taga
Mój plik znajduje się w \WEB-INF\tags\com\blogspot\mkorwel\formInputText.xhtml.
<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:ui="http://java.sun.com/jsf/facelets">

 <ui:param name="labelMsg" value="#{not empty labelMsg ? labelMsg : 'defaultLabel'}" />

 <div>
  <ui:insert name="label">
   <div style="width: 150px; float:left;">
    <h:outputText id="#{name}_label" value="#{labelMsg}" />
   </div>
  </ui:insert>
 
  <h:inputText id="#{name}_inputText" value="#{value}" />
 </div>
</ui:composition>
Następnym krokiem jest stworzenie facletowego tagliba, w którym definiujemy:
  • namespace,
  • miejsce położenia naszych tagów.
U mnie, plik znajduje się w: WEB-INF\my.taglib.xml.
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib>
    <namespace>http://mkorwel.blogspot.com/components</namespace>
    <tag>
  <tag-name>formInputText</tag-name>
  <source>tags/com/blogspot/mkorwel/formInputText.xhtml</source>
    </tag>
</facelet-taglib>
Ostatnim krokiem jest zarejestrowanie naszego tagliba w web.xml.
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0"> 
       
 <context-param>
     <param-name>facelets.LIBRARIES</param-name>
     <param-value>/WEB-INF/my.taglib.xml</param-value>
  </context-param>

</web-app>
U mnie struktura plików wygląda następująco:


Wykorzytanie
Poniżej krótki fragment kodu, który przedstawia przykładowe wykorzystanie tagu formInputText.
Ważne fragmenty:
  • <ui:define> możliwość nadpisania sekcji <ui:insert> zdefiniowanej wewnątrz tagu. Atrybut name wskazuje którą sekcję chcemy nadpisać.
<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
 xmlns:ui="http://java.sun.com/jsf/facelets"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:my="http://mkorwel.blogspot.com/components"
 template="/WEB-INF/templates/default.xhtml">
 <ui:define name="content">

  <h:form>

   <my:formInputText name="example1" labelMsg="Example1: "
    value="#{myFormController.example1}" />
   
   <my:formInputText name="example2" labelMsg="Example2: "
    value="#{myFormController.example2}" />
  
   <my:formInputText name="example3" value="#{myFormController.example3}" />
   
   <my:formInputText name="example4" value="#{myFormController.example4}">
    <ui:define name="label"><div>My custom label:</div></ui:define>
   </my:formInputText>
   
  </h:form>
 </ui:define>
</ui:composition>

Dokumentacja
Kiedy komponent/tag jest już gotowy, to warto go opisać. Dokumentacja jak wiadomo jest bardzo ważna, czasami ważniejsza od samego kodu, dlatego należy zadbać o ten punkt niezwykle starannie. Do tej pory nasz komponent jest widoczny przez nasze IDE, ale niestety nie podpowiada nam co możemy z nim zrobić. Aby to się zmieniło wystarczy stworzyć plik TLD (Tag Library Descriptor) w którym starannie opisujemy nasz tag oraz każdy atrybut, jaki w nim występuje. Do opisu możemy używać znaczników html, dzięki czemu nasz opis będzie bardziej przyjemny.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
                        "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
 <tlib-version>1.2</tlib-version>
 <jsp-version>2.0</jsp-version>
 <short-name>mir</short-name>
 <uri>http://mkorwel.blogspot.com/components</uri>
 <display-name>mkorwel</display-name>
 <description>My Components</description>
  
 <tag>
  <name>formInputText</name>
  <tag-class />
  <body-content>empty</body-content>
  <description>Pole formularza renderowane wraz z etykietą.</description>
  <attribute>
      <name>name</name>
         <required>true</required>
         <type>java.lang.String</type>
         <description>Nazwa pola. Na podstawie tego atrybutu generowane jest id komponentów.</description>
  </attribute>
  <attribute>
      <name>value</name>
         <required>true</required>
         <type>java.lang.String</type>
         <description>Wartość pola tekstowego.</description>
  </attribute>
  <attribute>
      <name>labelMsg</name>
         <type>java.lang.String</type>
         <description>Etykieta pola</description>
  </attribute>
 </tag>
</taglib>

sobota, 10 listopada 2012

JPA2/Hibenrate - MultipleBagFetchException

Opisywałem już jedną niespodziankę, jaką szykuje nam hibenrate jeśli używamy java.util.List zamiast java.util.Set. Niestety to nie jest jedyna niespodzianka :). Podczas pracy z hibenratem możemy nagle trafić na taki oto błąd:
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
wynikający z chęci pobrania co najmniej dwóch kolekcji z jednej encji. Oczywiście błąd ten dotyczy sytuacji w której korzystamy z java.util.List. Wystarczy w takim razie zmienić naszą kolekcję na java.util.Set i problem zostanie rozwiązany.

Chciwe pobieranie - krótkie wyjaśnienie na czym polega
Chciwe pobieranie (EAGER) polega na wybraniu wszystkich potrzebnych danych od razu. Możemy to zrobić na dwa sposoby:
  • dodania do adnotacji @OneToMany, @ManyToMany, itd. fetch=FetchType.EAGER. Trzeba uważać na pobranie zbyt dużej ilości danych. Więcej o tym problemie tutaj. W takiej konfiguracji dostaniemy nasz błąd już podczas deployu aplikacji.
  • podczas tworzenia zapytania, określić które wiązania/relacje mają zostać chciwie dobrane poprzez oznaczenie ich jako FETCH. W takiej konfiguracji nasz błąd zostanie odkryty w momencie uruchomienia zapytania.

wtorek, 16 października 2012

Zasięg @ViewScoped w CDI/Weld

Szybki przepis jak w CDI/Weld stworzyć nowy zasięg @ViewScoped, ponieważ niestety nie został on domyślnie uwzględniony w specyfikacji. Weld oferuje nam 4 domyślne zasięgi:
  • @RequestScoped,
  • @ConversationScoped,
  • @SessionScoped,
  • @ApplicationScoped.
Po przejrzeniu tej listy od razu nasuwa się pytanie - gdzie jest @ViewScoped? Co prawda pojawił się nowy zasięg @ConversationScoped, który znany jest wszystkim tym, którzy mieli styczność z Seam'em, ale to chyba jeszcze nie powód, aby usuwać @ViweScoped (w Seam'ie - @Scope(ScopeType.PAGE)). Niestety z jakiś względów tak się właśnie stało. Na szczęście specyfikacja CDI pozwala nam tworzyć nowe zasięgi, co skłoniło mnie do tego, aby trochę poszperać i stworzyć zapomniany zasięg @ViewScoped. Swoją drogą autorzy dokumentacji Weld nie pomagają w tego typu czynnościach.

Potrzebny kod
Potrzebujemy dodać następujące klasy do naszego projektu.
package com.blogspot.mkorwel.context;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.enterprise.context.NormalScope;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

@Target(value = { METHOD, TYPE, FIELD })
@Retention(value = RUNTIME)
@NormalScope
@Inherited
public @interface ViewScoped {

}
package com.blogspot.mkorwel.context.spi;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;

public class ViewContextExtension implements Extension {

 public void afterBeanDiscovery(@Observes AfterBeanDiscovery event,
   BeanManager manager) {
  event.addContext(new ViewContext());
 }
}
package com.blogspot.mkorwel.context.spi;

import java.util.Map;

import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.faces.context.FacesContext;

import com.blogspot.mkorwel.context.ViewScoped;

public class ViewContext implements Context {

 public Class<ViewScoped> getScope() {
  return ViewScoped.class;
 }

 public <T> T get(Contextual<T> contextual,
   CreationalContext<T> creationalContext) {
  Bean<T> bean = (Bean<T>) contextual;
  Map<String, Object> viewMap =FacesContext.getCurrentInstance()
    .getViewRoot().getViewMap(true);
  
  if (viewMap.containsKey(bean.getName())) {
   return (T) viewMap.get(bean.getName());
  } else {
   T t = bean.create(creationalContext);
   viewMap.put(bean.getName(), t);
   return t;
  }
 }

 public <T> T get(Contextual<T> contextual) {
  Bean<T> bean = (Bean<T>) contextual;
  Map<String, Object> viewMap = FacesContext.getCurrentInstance()
    .getViewRoot().getViewMap(true);
  
  if (viewMap.containsKey(bean.getName())) {
   return (T) viewMap.get(bean.getName());
  } else {
   return null;
  }
 }

 public boolean isActive() {
  return true;
 }
}
W META-INF tworzymy plik: services/javax.enterprise.inject.spi.Extension, w którym zadeklarujemy użycie naszego zasięgu
com.blogspot.mkorwel.context.spi.ViewContextExtension
Użycie
Nasza "Managed Bean" o zasięgu @ViewScoped może wyglądać mniej więcej tak:
@ViewScoped
public class SomeControler implements Serializable {

 //some field and method
}

niedziela, 14 października 2012

Hibernate - Problem z listami przy relacji ManyToMany

Hibernate mimo swoich wielu zalet, ma również wiele wad :). Jedną z nich jest sposób obsługi java.util.List przy próbie mapowania @ManyToMany. W sumie przy pierwszym kontakcie wszystko działa bez zarzutu, ale w momencie gdy chcemy dodać coś do listy, w której są już jakieś inne elementy, to okazuje się, że jednak nie wszystko działa w 100% dobrze. W naszej konsoli widzimy mniej więcej taki log:
Hibernate: insert into Item (name, id) values (?, ?)
Hibernate: delete from OrderTab_Item where OrderTab_id=?
Hibernate: insert into OrderTab_Item (OrderTab_id, items_id) values (?, ?)
Hibernate: insert into OrderTab_Item (OrderTab_id, items_id) values (?, ?)
Hibernate: insert into OrderTab_Item (OrderTab_id, items_id) values (?, ?)
Hibernate: insert into OrderTab_Item (OrderTab_id, items_id) values (?, ?)
Czyli w momencie wstawienia jakiegoś wiersza do listy, Hibenrate usuwa wszystkie relacje a następnie z powrotem je dodaje. W moim przykładzie dodajemy 1 element do listy już 3 elementowej. W związku z tym Hibenrate postanowił usunąć wszystkie relacji a następnie po kolei dodał całkiem nowe 4 wiersze. Przykład encji oraz kodu który który dodaje do encji widzimy poniżej.
Encje w relacji many-to-many.
@Entity
@Table(name="OrderTab")
public class Order implements Serializable {

 @Id
 @GeneratedValue
 private Long id;

 private String name;

 @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
 private List items;

 // ... GET and SET
}
@Entity
public class Item implements Serializable {

 @Id
 @GeneratedValue
 private Long id;

 private String name;

 // ... GET and SET

}
Prosta metoda dodająca Item do Orderu.
public void addItem(long orderId, Item item) {

 Order o = em.find(Order.class, orderId);
 o.getItems().add(item);

 em.merge(o);

}
Rozwiązanie problemu
Wystarczy zmienić typ kolekcji w której przechowywujemy elementy z java.util.List na java.util.Set i wszystko będzie działać zgodnie z oczekiwaniami. Encja Order po zmianie wygląda tak:
@Entity
@Table(name="OrderTab")
public class Order implements Serializable {

 @Id
 @GeneratedValue
 private Long id;

 private String name;

 @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
 private Set items;

 // ... GET and SET
}
a log w konsoli tak:
Hibernate: insert into Item (name, id) values (?, ?)
Hibernate: insert into OrderTab_Item (OrderTab_id, items_id) values (?, ?)
Pamiętajcie tylko że jak korzystacie ze zbiorów zamiast list, to należy doimplementować metody equals i hashCode.

wtorek, 4 września 2012

Joda Time - czyli koniec problemów z datą w javie

Joda Time, czyli darmowa biblioteka, dzięki której zapomnimy o mało przyjemnych operacjach na dacie w javie. Java mimo swojej ewolucji nie zadbała o porządną implementację klas reprezentujących czas i datę.
Na szczęście internet jest pełen darmowych bibliotek, które ułatwiają życie developerom. Jedną z nich jest Joda Time, czyli biblioteczka dostarczająca proste i funkcjonalne API do operacji na dacie. Odpowiednie jary potrzebne do uruchomienia Joda Time w projekcie można ściągnąć z oficjalnej strony.

Tworzenie
Pytanie - w czym Joda Time jest lepsza od standardowych klas Javowych? Odpowiedz - praktycznie we wszystkim :). Już przy tworzeniu można zauważyć mnogość konstruktorów. Nasz obiekt reprezentujący datę można stworzyć na kilkanaście sposób, m.in. można podać rok, miesiąc, dzień, itd. Dla przykładu standardowy obiekt java.util.Date posiada 2 konstruktory.
DateTime dt = new DateTime();
DateTime dt2000 = new DateTime(2000, 12, 7, 12, 0); //2000-12-07 12:00
Dodatkowo każdy stworzony obiekt jest immutable. Obiekty org.joda.time.DateTime możemy w łatwy sposób zamienić na obiekty java.util.Date i na odwrót.
// from Joda to JDK
DateTime dt = new DateTime();
Date jdkDate = dt.toDate();

// from JDK to Joda
dt = new DateTime(jdkDate);

Pobieranie danych
W standardowych obiektach typu java.util.Date, aby pobrać jakąś składową daty (rok, miesiąc, dzień, itp.), trzeba było tworzyć obiekt java.util.Calendar albo używać zakazanych/nieaktualnych (Deprecated) metod getDay(), getMonth(), .... Nowe API dostarcza wiele metod, które jednoznacznie wskazują co pobierają (w java.util.Calendar istniała magiczna metoda get która przyjmowała magiczny parametr typu int co ma zwrócić :) ). Kilka wybranych metod z org.joda.time.DateTime:
dt.getMonthOfYear();
dt.getDayOfWeek();
dt.getHourOfDay();
dt.getDayOfYear();
Modyfikacja daty
W standardowym podejściu należałoby stworzyć obiekt java.util.Calendar, który posiada ogólne metody add(int field, int amount), set(int field, int amount), itd. Zmiana czegokolwiek wymagała częściowej znajomości klasy java.util.Calendar, ponieważ parametry podajemy jako int'y, które co prawda są stałymi klasy java.util.Calendar, ale nadal są tylko int'ami. Korzystając z Joda Time nie musimy się takimi rzeczami przejmować. Po prostu należy użyć jednej z wielu metod dodających, odejmujących, itp.
dt.plusWeeks(1);
dt.plusMinutes(10);
dt.minusHours(3);
dt.minusYears(2);
Formatowanie daty
W javie mamy coś takiego jak DateFormat, dzięki któremu możemy nadać dacie taki wygląd, jaki potrzebujemy. Korzystając z Joda Time mamy dwa szybkie sposoby z użyciem bezpośrednio naszej klasy lub tworzeniem obiektu formatera:
// Sposób 1
System.out.println(dt.toString("yyyy-MM-dd"));

// Sposób 2
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd hh:mm:ss");
System.out.println(formatter.print(dt));