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