JPA2/Hibernate kao fil na torti ili kao temelj kuće
JPA je Java persistence API, zvanični standard (ili kako ga oni zovu specifikacija) za objektno-relaciono mapiranje u Javi. Deo je JEE 5 specifikacije iako se može koristiti i u običnoj javi (JSE). Kao svaki standard JPA je tu da uvede red među različitim specifičnim ORM bibliotekama i pruži jedinstven način za rad s njima na dobrobit programera koji sad mogu na isti način da rade sa Hibernate i sa TopLink ORM biblotekama.
Prva JPA verzija kao deo JEE 5 je bila prilično siromašna u mogućnostima. Od svih velikih ORM biblioteka uzeto je samo ono što je zajedničko, najveći zajednički sadržalac. Uz JEE6 6 je izašlo drugo izdanje JPA standarda a već je implementiran u Hibernate 3.5+ i EclipseLink 2.0+ (bivšem TopLinku). Nova verzija je donela dosta novina ali i par razočarenja (o tome više u nekom drugom postu).
E sad da pokažemo kako nam JPA preko Hibernate (JPA/Hibernate) može pomoći. Nastavljam se na prethodni post. Pokazao sam kako se treba aplikacija podeliti u slojeve i kako se sa Springom pojednostavljuje sistem. Uvođenje JPA/Hiberate u takav sistem ima vrlo mali uticaj na arhitekturu. Ako je sve urađeno po JUS-u prelazak sa JDBC na JPA način rada sa bazom će uticati samo na sloj perzistencije. Eventualno će uticati ali minimalno na domenski sloj ako se podaci o tome kako se klase povezuju sa tabelama stave u kod u vidu anotacija. Drugi pristup je da se ti podaci smeste u xml fajl a kod ostavi netaknut. Ovolika izmena sa tako fino lociranim mestom menjanja pokazuje kvalitet višeslojnosti. Da je aplikacija loše podeljena preomena bi se prenosila u gomilu klasa.
Pošto smo u prošlom primeri imali samo jednu klasu u sloju perzistencije dajem njen izgled kad se radi preko JDBC-a:
Prelaskom na JPA dobija se sledeći izgled klase:
U ovom kodu samo je TypedQuery deo JPA2. U JPA1 bi koristili Query interface koji nije generički pa bi morali Object referencu da pretvorimo u Customer prilikom poziva getSingleResult metode.
A da bi sve ovo radilo potrebo je u klasi u domenskoj klasi Customer ili u persistence.xml datoteci opisati povezivanje klasa i tabeli. Primer za opisivanje u domenskoj klasi preko antotacije:
Tabela sličnosti pojmova u JDBC i JPA:
Mogli smo promene da izvedemo i na drugi način. Mogli smo da uopšte ne menjamo CustomersInDB klasu nego napravimo novu implementaciju Cusotmers interfejsa, recimo JPACustomersInDB i da u xml config datoteci za Spring DefaultCusotmersService beanu podmtnemo preko dependency injectiona tu novu implementaciju. To pokazuje kako je dependenci injection moćna stvar. Možemo izmeniti sistem a da ne menjamo postojeći kod!
Ako ste zainteresovani za JPA pogledajte tutorijal na http://www.vogella.de/articles/JavaPersistenceAPI/article.html Iako se tu obrađuje JPA 1 i to u Java SE, mislim da je za početak sasvim odgovarajuć i pošto se radi o običnoj javi nema dependency injection-a, Springa, aplikacionog servera, web programiranja i ostalih zbunjujućih stvari.
Prva JPA verzija kao deo JEE 5 je bila prilično siromašna u mogućnostima. Od svih velikih ORM biblioteka uzeto je samo ono što je zajedničko, najveći zajednički sadržalac. Uz JEE6 6 je izašlo drugo izdanje JPA standarda a već je implementiran u Hibernate 3.5+ i EclipseLink 2.0+ (bivšem TopLinku). Nova verzija je donela dosta novina ali i par razočarenja (o tome više u nekom drugom postu).
E sad da pokažemo kako nam JPA preko Hibernate (JPA/Hibernate) može pomoći. Nastavljam se na prethodni post. Pokazao sam kako se treba aplikacija podeliti u slojeve i kako se sa Springom pojednostavljuje sistem. Uvođenje JPA/Hiberate u takav sistem ima vrlo mali uticaj na arhitekturu. Ako je sve urađeno po JUS-u prelazak sa JDBC na JPA način rada sa bazom će uticati samo na sloj perzistencije. Eventualno će uticati ali minimalno na domenski sloj ako se podaci o tome kako se klase povezuju sa tabelama stave u kod u vidu anotacija. Drugi pristup je da se ti podaci smeste u xml fajl a kod ostavi netaknut. Ovolika izmena sa tako fino lociranim mestom menjanja pokazuje kvalitet višeslojnosti. Da je aplikacija loše podeljena preomena bi se prenosila u gomilu klasa.
Pošto smo u prošlom primeri imali samo jednu klasu u sloju perzistencije dajem njen izgled kad se radi preko JDBC-a:
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.springframework.jdbc.datasource.DataSourceUtils; public class CustomersInDB implements Customers { private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } private Connection getConnection() { return DataSourceUtils.getConnection(dataSource); } @Override public Customer loadCustomer(String customerId) { try { PreparedStatement st = getConnection() .prepareStatement("select * from customers where id=?"); try { st.setString(1, customerId); ResultSet rs = st.executeQuery(); if (!rs.next()) { throw new RuntimeException("Customer has been deleted"); } return new Customer( customerId, rs.getString("name"), rs.getString("address")); } finally { st.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } @Override public void saveCustomer(Customer customer) { try { PreparedStatement st = getConnection().prepareStatement( "update customers set name=?, address=? where id=?"); try { st.setString(1, customer.getName()); st.setString(2, customer.getAddress()); st.setString(3, customer.getId()); st.executeUpdate(); } finally { st.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } }
Prelaskom na JPA dobija se sledeći izgled klase:
import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; public class CustomersInDB implements Customers { @PersistenceContext private EntityManager entityManager; // Spring će uraditi dependency injection @Override public Customer loadCustomer(String customerId) { TypedQuery<Customer> q = entityManager .createQuery("select c from Customer as c where c.id=:id", Customer.class); q.setParameter("id", customerId); return q.getSingleResult(); } @Override public void saveCustomer(Customer customer) { entityManager.merge(customer); } }
U ovom kodu samo je TypedQuery deo JPA2. U JPA1 bi koristili Query interface koji nije generički pa bi morali Object referencu da pretvorimo u Customer prilikom poziva getSingleResult metode.
A da bi sve ovo radilo potrebo je u klasi u domenskoj klasi Customer ili u persistence.xml datoteci opisati povezivanje klasa i tabeli. Primer za opisivanje u domenskoj klasi preko antotacije:
@Entity public class Customer implements Serializable, Cloneable { @Id @GeneratedValue private long internId; private String id; private String name; private String address; public Customer() { } public Customer(String id, String name, String address) { this.id = id; this.name = name; this.address = address; } // getters, setters, equals, hash, clone kao i ranije }
Tabela sličnosti pojmova u JDBC i JPA:
JDBC | JPA |
---|---|
database | persistence unit |
DataSource | EntityManagerFactory |
Connection | EntityManager |
Mogli smo promene da izvedemo i na drugi način. Mogli smo da uopšte ne menjamo CustomersInDB klasu nego napravimo novu implementaciju Cusotmers interfejsa, recimo JPACustomersInDB i da u xml config datoteci za Spring DefaultCusotmersService beanu podmtnemo preko dependency injectiona tu novu implementaciju. To pokazuje kako je dependenci injection moćna stvar. Možemo izmeniti sistem a da ne menjamo postojeći kod!
Ako ste zainteresovani za JPA pogledajte tutorijal na http://www.vogella.de/articles/JavaPersistenceAPI/article.html Iako se tu obrađuje JPA 1 i to u Java SE, mislim da je za početak sasvim odgovarajuć i pošto se radi o običnoj javi nema dependency injection-a, Springa, aplikacionog servera, web programiranja i ostalih zbunjujućih stvari.
Comments