- 在persisting data中,JDBC就像自行車,對於份內的工作能夠很好地完成並且在一些特定場合表現出色;但隨著application越來越複雜,需求也變得複雜
- 我們需要將object的property mapping到database上
- 需要自動生成query, statement
- 可以不用再寫問號
- lazy loading: 允許在需要的時候獲得data
- eager fetching: 與lazy loading相對。預先取得,可藉由一個操作將全部從db取出,節省多次查詢的成本
- cascading: 關聯刪除。
- 一些可用的framework提供這樣的服務。這些服務的通用名稱是object-relational mapping, ORM
- 在persistent layer使用ORM,可以節省數千行的code和大量的開發時間
- ORM可以把你的注意力從容易出錯的SQL轉向如何實現應用程序的真正需求
- Spring對不同的ORM的support很類似。一旦掌握其中一種對ORM的support,可以很輕鬆的切換到另一種framework
- Hibernate
- Declaring a Hibernate session factory
- 使用Hibernate的主要interface是org.hibernate.Session
- Session interface提供基本的data access功能,如save, update, delete, load object
- 透過Session interface,application的Repository能夠滿足所有的persistence needs
- 要得到一個object,結鈾Hibernate SessionFactory interface的implementation
- 其他像Hibernate session open, close, manage與是在SessionFactory中負責
- 在Spring中,要透過Spring的某一個Hibernate Session factory bean來獲取Hibernate SessionFactory,以下提供三個Session factory bean
- org.springframework.orm.hibernate3.LocalSessionFactoryBean
- org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
- org.springframework.orm.hibernate4.LocalSessionFactoryBean
- 上述都是Spring FactoryBean interface的implementaion,會產生SessionFactory,能夠wire到任何SessionFactory的property中
- 如果使用的是Hibernate3.2或更高,而且使用XML mapping,則需要定義Spring的org.springframework.orm.hibernate3的LocalSessionFactoryBean
- datasource: wired
- mappingResources: 一個或多個Hibernate mapping file,定義了application的persistence strategy
- HibernateProeprties: Hibernate的操作細節
- 如果希望使用annotated方式來定義persistence strategy,而且還沒使用Hibernate4,則需要使用AnnotationSessionFactoryBean來代替LocalSessionFactoryBean
- 使用packageToSacne告訴Spring scan一個或多個package找出scope class,這些class通常都通過annotation的方式表明要使用Hibernate進行persistence
- 這些class可以使用的annotation包含JPA的@Entity或@MappedSuperclass以及Hibernate的@Entity
- 也可以使用annotatedClass property將applicaiont中所有persistence class以完整名稱方式列出
- 配置玩Hibernate的Session factory bean以後,就可以開始建自己的Repository class
- Building Spring-free Hibernate
- 在早期,寫Repository class會涉及到使用Spring的HibernateTemplate。HibernateTemplate可以保證每個transaction只使用一個Hibernate session,但缺點是Repository implementation會和Spring coupling
- 目前最佳實作方式是不再使用HibernateTemplate,而是使用contextual session。透過這個方式,會直接將Hibernate SessionFactory wired到Repository內,並使用之獲取Session
- 使用@Injects讓Spring自動將sessionFactory injects到HibernateSpitterRepository
- 在class上使用了@Repositry
- @Repository是String的另一種stereotype annotation,能像其他annotation一樣被scan到,這樣就不用明確聲明HibernateSpitterRepository bean。也就是幫助減少顯示配置
- template class有一個任務就是捕捉platform相關exception,再使用Spring統一unchecked exception的形式throw。如果使用的是Hibernate context session而不是Hibernate template,exception的轉換會如何處理?
- 為了不使用template Hibernate Repository加入exception handling,只需要在Spring application context加上PersistenceExceptionTranslationPostProcessor bean
- 此為一個bean post-processor,會在所有擁有@Repositroy annotation的class上添加一個advisor。這樣就會捕捉任何platform相關exception,並以Spring unchecked data access形式重新拋出
- 這樣,Hibernate的Repository就完成了。開發時,沒有依賴Spring的特定class。
- Spring and the Java Persistence API (JPA)(
- JPA基於POJO persistence mechanism,從Hibernate和Java Data Object(JDO)上借鑑很多理念並加入Java5的annotation特性
- 在Spring使用JPA的第一步是要在Spring application context中將entity manager factory按照bean的形式進行配置
- Configuring an entity manager factory
- 基於JPA的application需要使用EntityManagerFactory來得到EntityManager instance。JPA有兩種entity manager
- Application-managed: 當application再向entity manager factory request entity manager時,factory會建立一個entry manager。這種方式適合不運行在JavaEE container中的獨立應用程式。取得的EntityManager是透過EntityManagerFactory建立。entity manager factory由LocalEntityManagerFactoryBean產生
- Container-managed: entity manager factory由JavaEE建立及管理,application部會管理entity manager factory。instance manager直接透過injects JNDI獲取。適合用在Java EE container。取得的是透過PersistenceProvider 的createContainerEntityManagerFactory() method得到的。entity manager factory由LocalContainerEntityManagerFactoryBean產生
- 以上兩種entity manager implement同一個EntityManager interface,區別在於EntityManager的create及manager方式。
- 值得注意的不相同處在於兩者在Spring application context中的配置
- Configuring Application-managed JPA
- 對Application-managed entity factory來說,決大部分的配置來源是名叫persistence.xml的file。這個文件必須為在classpath下的META-INF下。
- persistence.xml的作用在於定義一個或多個persistence unit。
- persistence unit是同一個datasource下的一個或多個persistence class
- 透過以下的@Bean在Spring中declare LocalEntityManagerFactoryBean
- 在application manager的場景下,完全由application本身負責獲取EntityManagerFactory,這是透過JPA的PersistenceProvider做到的。如果每次eequest都要定義persistence-unit,則code會迅速膨脹。透過將其配置在persistence.xml,JPA就能夠在這個特定的位置找到persistence-unit的定義
- 但若借助於Spring對JPA的support,就不需要再直接處理PersistenceProvider。因此,再將配置message放在persistence.xml就不太正確。事實上,這樣做妨礙在Spring中配置的EntityManagerFactory。因此可以關注Container-managed JPA
- Configuring Container-managed JPA
- 當run在container時,可以使用container提供的information來生成EntityManagerFactory。
- 可以將data source配置在Spring application context中,而不是在persistence.xml。
- 如下,儘管datasource可以在persistence.xml進行配置,但這邊的property指定的datasource有更高的優先等級
- 本例中的JPAVendorAdapter使用HibernateJPAVendorAdapter,因此
- database還支援:
- DB2
- DERBY
- H2
- HSQL
- INFORMIX
- MYSQL
- ORACLE
- POSTGRESQL
- SQLSERVER
- SYBASE
- persistence.xml主要作用在於識別persistence-unit entity。但是Spring3.1開始,能夠在LocalContainerEntityManagerFactoryBean直接設定packgesToScan property
- 如此會scan com.habuma.spittr.domain中的package,找出帶有@Entity的class。因此,沒有必要在persistence.xml declare
- 同時,因為dataSource也是injects到LocalContainerEntityManagerFactoryBean,因此也沒有必要在persistence.xml再定義
- 結論是:persistence.xml沒有存在的必要了,可以只讓LocalContainerEntityManagerFactoryBean完成這些事情
- 從JNDI獲取entity manager factory
- 如果將Spring application deploy在application server中,EntityManagerFactory可能已經建好,而且未在JNDI中等待查詢使用。
- 這種情況下,可以使用Spring jee namespace的 <jee:jndi- lookup>來得到對EntityManagerFactory的引用
- 也可以使用java configuration
- 雖然沒有return EntityManagerFactory,但結果就是一個EntityManagerFactory bean。因為return的jndiObjectFB其實就是FactoryBean的interface implementation,因此能夠建立EntityManagerFactory
- 接下來就可以開始寫repository
- JPA-based repository
- 有鑒於 pure JPA勝於templated-based JPA,因此主要在於構建不依賴Spring的JPA repository
- @PersistenceUnit: Spring會將EntityManagerFactory注入到@Repository中
- 有個問題是,每次都會呼叫emf.createEntityManager(),表示使用repository的method都會建立一個新的EntityManager。如果可以先準備好EntityManager,可能會更加方便
- 因為EntityManager不是thread-safe,所以一般來說不適合injects到Repository這樣的singleton bean中。
- 可能會擔心thread-safe的問題
- 這邊的真相其實是,EntityManager注入的是一個proxy,真正的EntityManager是和目前關聯的那一個,如果不存在則會建立。如此就能使用thread-safe的方式使用entity manager
- @PersistenceUnit&@PersistenceContext是由JPA規範的。使用這些annotation之前必須先配置Spring的PersistenceAnnotationBeanPostProcessor。如果已經使用了<context:annotation-config>和<context:component-scan>就不用擔心,因為會自動register PersistenceAnnotationBeanPostProcessor bean。否則需要直接配置
- @Transactional: 表示這個Repository中的persistence method是在transactional context中執行
- @Repository: 作用和開發Hibernate context session的Repository是醫治的。由於沒有使用template class處理exception,所以需要加上@Repository,這樣PersistenceExceptionTranslationPostProcessor就知道要將這個bean產生的exception轉換成Spring的統一data access exception
- 對於JPA或是Hibernate,persistenceTranslation都不是強求的。
- Automatic JPA repositories with Spring Data
- 看一下addSpitter()
- 事實上,除了persistence 的Spitter object不同以外,其他都一樣。所有repository的method都是common。
- Spring Data JPA終結這種bolierplate,不需要一遍遍的寫相同的Repository implementation。Spring Data能夠只寫Repository interface,不需要implementation class
- 再看一下SpitterRepository interface
- 編寫Spring Data JPA Repository的關鍵在於從interface挑選一個extends
- 這裡,SpitterRepository extends Spring Data JPA的JpaRepository
- 通過這種方式,JpaRepository進行了parameterized,所以他能夠知道這是一個用來persisting Spitter object的repository
- 且Spitter的ID是ling,且還extends 18個執行persisting操作的common method(CRUD..)
- 使用Spring Data來做這些事情。做的方法就是提出request
- 為了要求Spring Data create SpitterRepository的implementation,需要在Spring配置中加一個element。
- XML啟用Spring Data JPA所需要加的內容:
- <jpa:repositories>就像是<context:component-scan>,需要指定進行掃描的base-package。不過<context:component-scan>會掃描package來找帶有@Component的class,<jpa:repositories>會掃描base-pacakge找出extends Spring Data JPA repository interface的所有interface。如果發現了extends Repository的interface,會自動產生這個interface的implementation
- Java config
- 當Spring Data找到SpitterRepository後,就會create SpitterRepository的implementation class。其中包含extends JpaRepository, PagingAndSortingRepository, and CrudRepository等18個method。注意這個implementation class是在application start up的時候generated。也就是Spring application context created的時候建立的。並不是在build-time時code generation,也不是在 interface’s methods are called產生的
- Spring Data JOPA很棒的一點在於它能夠替Spitter提供18個便利的method,無需寫任何的persistence code。如果需求超過,有幾種方式添加自訂義method
- 定義customer query method
- 本質上,Spring Data定義了一組小心domain-specific language DSL,在這裡,persistence detail都是透過repository method的signature來描述的
- Spring data知道這個方分是要找到Spitter,method名稱findByUsername()也就是需要根據username property找到Spitter,而username由parameter傳到method中。又因為findByUsername需要return一個Spitter object,而不是collection,因此只會找一個username匹配的Spitter object
- SpringData判斷method的方法,如findByUsername匹配到下面
- verb(Spring Data允許(get, read, find)這三個都是query, count(return 數目)): find
- subject(是由interface參數化決定的,如果是以Distinct為開頭,則會返回不重複者): Spitter(implied)
- By
- predicate(會有一個或多個限制結果的條件):Username
- IsAfter, After, IsGreaterThan, GreaterThan IsGreaterThanEqual, GreaterThanEqual
- IsBefore, Before, IsLessThan, LessThan
- IsLessThanEqual, LessThanEqual
- IsBetween, Between IsNull, Null
- IsNotNull, NotNull IsIn, In
- IsNotIn, NotIn
- IsStartingWith, StartingWith, StartsWith IsEndingWith, EndingWith, EndsWith
- IsContaining, Containing, Contains
- IsLike, Like
- IsNotLike, NotLike
- IsTrue, True
- IsFalse, False
- Is, Equals
- IsNot, Not
- 又,readSpitterByFirstnameOrLastname()也可以匹配
- verb: read
- subject: Spitter
- By
- predicate:FirstnameOrLastname
- 如果要在firstname及lastname忽略大小寫
- 可使用排序
- 其他範例
- 問題:
- 有時候通過method name表達很煩瑣,甚至無法實現
- 解法:
- 使用@Query
- Declaring custom queries
- 假設想要找E-mail是G-mail的Spitter,有個方式是定義findByEmailLike(),傳入"%gmail.com"
- 更好的方法是定義findAllGmailSpitters() method
- 因為SpinrData無法實現,所以會拋出錯誤。方法就是加入@Query,為Spring Data執行query。
- 注意,依然不需要寫method的implementation
- 當使用命名約定特別長的時候,就可以使用這個annotation
- 問題:
- 限於單一個JPA query,如果需要更複雜功能,無法在一個簡單查詢中處理怎麼辦
- 解法:
- 混合自定義功能
- Mixing in custom functionality
- 直接使用EntityManager
- 如果需要做的事情無法透過Spring Data JPA,則必須在一個比Spring Data JPA更低的層級上使用JPA。
- 當Spring Data JPA為repository interface生成implementation 的實好,會找名字和interface相同,並且加上Impl suffix 的class,如果存在,就會將他的method和Spring Data JPA生成的method合併再一起。
- 對于SpitterRepository,要找的class就是SpitterRepositoryImpl
- 假設需要在SpitterRepository加一個method,找到Spitters發表1000則訊息以上,設定為Elite status,且@Query及約定的方法命名都無法使用時:
- 注意:沒有implement SpitterRepository
- SpitterSweeper
- 接著讓他SpitterRepository extends SpitterSweeper
- 後綴加上Imp是默認做法,如果要其他的suffix,則在配置@EnableJpaRepositories時加上設定
- XML:
- 如此就能將suffix設定為Helper,Spring Data JPA會找名叫SpitterRepositoryHelper的class,並將他匹配到SpitterRepository interface
@Bean public LocalSessionFactoryBean sessionFactory(DataSource dataSource) { LocalSessionFactoryBean sfb = new LocalSessionFactoryBean(); sfb.setDataSource(dataSource); sfb.setMappingResources(new String[] { "Spitter.hbm.xml" }); Properties props = new Properties(); props.setProperty("dialect", "org.hibernate.dialect.H2Dialect"); sfb.setHibernateProperties(props); return sfb; }
@Bean public AnnotationSessionFactoryBean sessionFactory(DataSource ds) { AnnotationSessionFactoryBean sfb = new AnnotationSessionFactoryBean(); sfb.setDataSource(ds); sfb.setPackagesToScan(new String[] { "com.habuma.spittr.domain" }); Properties props = new Properties(); props.setProperty("dialect", "org.hibernate.dialect.H2Dialect"); sfb.setHibernateProperties(props); return sfb; }
sfb.setAnnotatedClasses( new Class<?>[] { Spitter.class, Spittle.class } );// 可以準確的指定少量的scope class
public HibernateSpitterRepository(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; // injects } private Session currentSession() { return sessionFactory.getCurrentSession(); } public long count() { return findAll().size(); } public Spitter save(Spitter spitter) { Serializable id = currentSession().save(spitter); return new Spitter((Long) id, spitter.getUsername(), spitter.getPassword(), spitter.getFullName(), spitter.getEmail(), spitter.isUpdateByEmail()); } public Spitter findOne(long id) { return (Spitter) currentSession().get(Spitter.class, id); } public Spitter findByUsername(String username) { return (Spitter) currentSession() .createCriteria(Spitter.class) .add(Restrictions.eq("username", username)) .list().get(0); } public List <Spitter> findAll() { return (List <Spitter> ) currentSession() .createCriteria(Spitter.class).list(); } }
@Bean public BeanPostProcessor persistenceTranslation() { return new PersistenceExceptionTranslationPostProcessor(); }
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0"> <persistence-unit name="spitterPU"> <class>com.habuma.spittr.domain.Spitter</class> <class>com.habuma.spittr.domain.Spittle</class> <properties> <property name="toplink.jdbc.driver" value="org.hsqldb.jdbcDriver" /> <property name="toplink.jdbc.url" value= "jdbc:hsqldb:hsql://localhost/spitter/spitter" /> <property name="toplink.jdbc.user" value="sa" /> <property name="toplink.jdbc.password" value="" /> </properties> </persistence-unit> </persistence>
@Bean public LocalEntityManagerFactoryBean entityManagerFactoryBean() { LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean(); emfb.setPersistenceUnitName("spitterPU"); // persistence-unit name return emfb; }
@Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean(); emfb.setDataSource(dataSource); emfb.setJpaVendorAdapter(jpaVendorAdapter); // 指名所使用的是哪一個factory的JPA implementation return emfb; }
@Bean public JpaVendorAdapter jpaVendorAdapter() { HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); adapter.setDatabase("HSQL"); adapter.setShowSql(true); adapter.setGenerateDdl(false); adapter.setDatabasePlatform("org.hibernate.dialect.HSQLDialect"); return adapter; }
@Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean(); emfb.setDataSource(dataSource); emfb.setJpaVendorAdapter(jpaVendorAdapter); emfb.setPackagesToScan("com.habuma.spittr.domain"); return emfb; }
<jee:jndi-lookup id="emf" jndi-name="persistence/spitterPU" />
@Bean public JndiObjectFactoryBean entityManagerFactory() {} JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean(); jndiObjectFB.setJndiName("jdbc/SpittrDS"); return jndiObjectFB; }
package com.habuma.spittr.persistence; import java.util.List; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceUnit; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import com.habuma.spittr.domain.Spitter; import com.habuma.spittr.domain.Spittle; @Repository @Transactional public class JpaSpitterRepository implements SpitterRepository { @PersistenceUnit private EntityManagerFactory emf; // injects EntityManagerFactory public void addSpitter(Spitter spitter) { emf.createEntityManager().persist(spitter); //createEntityManager,並使用entity manager } public Spitter getSpitterById(long id) { return emf.createEntityManager().find(Spitter.class, id); } public void saveSpitter(Spitter spitter) { emf.createEntityManager().merge(spitter); }... }
package com.habuma.spittr.persistence; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import com.habuma.spittr.domain.Spitter; import com.habuma.spittr.domain.Spittle; @Repository @Transactional public class JpaSpitterRepository implements SpitterRepository { @PersistenceContext //會使用proxy private EntityManager em; //Inject EntityManager public void addSpitter(Spitter spitter) { em.persist(spitter); // Use EntityManager } public Spitter getSpitterById(long id) { return em.find(Spitter.class, id); } public void saveSpitter(Spitter spitter) { em.merge(spitter); } ... }
@Bean public PersistenceAnnotationBeanPostProcessor paPostProcessor() { return new PersistenceAnnotationBeanPostProcessor(); }
@Bean public BeanPostProcessor persistenceTranslation() { return new PersistenceExceptionTranslationPostProcessor(); }
public void addSpitter(Spitter spitter) { entityManager.persist(spitter); }
public interface SpitterRepository
extends JpaRepository<Spitter, Long>{
}
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd"> <jpa:repositories base-package="com.habuma.spittr.db" /> ... </beans>
@Configuration @EnableJpaRepositories(basePackages="com.habuma.spittr.db") public class JpaConfiguration { ... }
public interface SpitterRepository extends JpaRepository<Spitter, Long> { Spitter findByUsername(String username); }
List<Spitter> readByFirstnameIgnoringCaseOrLastnameIgnoresCase( String first, String last);
List<Spitter> readByFirstnameOrLastnameOrderByLastnameAscFirstnameDesc( String first, String last);
List<Pet> findPetsByBreedIn(List<String> breed) int countProductsByDiscontinuedTrue() List<Order> findByShippingDateBetween(Date start, Date end)
List<Spitter> findAllGmailSpitters();
@Query("select s from Spitter s where s.email like '%gmail.com'") List<Spitter> findAllGmailSpitters();
public class SpitterRepositoryImpl implements SpitterSweeper { @PersistenceContext private EntityManager em; public int eliteSweep() { String update = "UPDATE Spitter spitter " + "SET spitter.status = 'Elite' " + "WHERE spitter.status = 'Newbie' " + "AND spitter.id IN (" + "SELECT s FROM Spitter s WHERE (" + " SELECT COUNT(spittles) FROM s.spittles spittles) > 10000" + ")"; return em.createQuery(update).executeUpdate(); } }
public interface SpitterSweeper{ int eliteSweep(); }
public interface SpitterRepository extends JpaRepository<Spitter, Long>, SpitterSweeper { .... }
@EnableJpaRepositories( basePackages="com.habuma.spittr.db", repositoryImplementationPostfix="Helper")
<jpa:repositories base-package="com.habuma.spittr.db" repository-impl-postfix="Helper" />
沒有留言:
張貼留言