Google Code Prettify

2016年3月20日 星期日

Hibernate + Spring + JPA (configure)

Java 有很多的 ORM framework,在 JPA 制定後得到統一的寫法,當然啦~ JPA 還不夠完善,但是在 JPA 可行的範圍內,還是建議改用 JPA。

底下的例子 database layout 如下:

不過,這個 layout 倒也不是重點,因為這一篇主要是要說明環境的設定。

使用 JPA 連線到資料庫,所先要將連線設定在 classpath:META-INF/persistence.xml,如下: (在這個設定檔裡決定採用那一個 ORM framework,這裡決定採用 Hibernate。)
<persistence 
    xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0">
    
    <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
         <provider>org.hibernate.ejb.HibernatePersistence</provider>
         <properties>
             <property name="hibernate.ejb.autodetection" value="class"/>
             <property name="hibernate.connection.url" value="jdbc:oracle:thin:@oracledb:1521:myvote"/>
             <property name="hibernate.connection.driver_class" value="oracle.jdbc.OracleDriver"/>
             <property name="hibernate.connection.username" value="steven"/>
             <property name="hibernate.connection.password" value="Borland##"/>
             <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
             <property name="hibernate.connection.provider_class" value="org.hibernate.connection.C3P0ConnectionProvider" />
             <property name="hibernate.c3p0.min_size" value="20"/>
             <property name="hibernate.c3p0.initial_Pool_Size" value="20"/>
             <property name="hibernate.c3p0.max_size" value="100"/>
             <property name="hibernate.c3p0.timeout" value="120"/>
             <property name="hibernate.c3p0.max_statements" value="50"/>
             <property name="hibernate.c3p0.idle_test_period" value="3000"/>
             <property name="hibernate.show_sql" value="true"/>
         </properties>
      </persistence-unit>
</persistence>
  1. 為這個連線設定取個名字吧! 如綠色部份,這裡取名為 persistenceUnit。
  2. 因為這裡使用了 connection pool (橘色),在 Maven 的 pom.xml 中要加入如下設定,這樣專案會加入兩個 jar 檔 - hibernate-c3p0-5.0.7.Final.jar、c3p0-0.9.2.1.jar,第一個檔案的版號會隨設定檔設定的 hibernate.version 而定。
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-c3p0</artifactId>
    <version>${hibernate.version}</version>
</dependency> 
接著在 spring 的設定檔 (beans-config.xml) 裡如下設定:
<?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:aop="http://www.springframework.org/schema/aop"
  xmlns:c="http://www.springframework.org/schema/c"
  xmlns:cache="http://www.springframework.org/schema/cache"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:p="http://www.springframework.org/schema/p"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
        
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
  
      <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
 
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="persistenceUnit"/>
      </bean>
   
      <context:component-scan base-package="idv.steven.orm.market.dao">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
      </context:component-scan>
      
      <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager"
        p:entityManagerFactory-ref="entityManagerFactory" 
      />
  
      <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager" />

    <bean id="app1" class="idv.steven.orm.market.App1" />
</beans>
  1. 在 DAO 裡會用到一個 annotation - @PersistenceContext,在這裡要引入綠色部份。
  2. entityManagerFactory 只會在這個設定檔裡出現,目的是產生注入 DAO 的 EntityManager。
  3. 所有的 DAO 會放在 idv.steven.orm.market.dao 這個 package 下,並且在每個類別前加上 @Repository,透過這個設定 spring 即會掃描 package 裡所有類別,將所有的 DAO 載入。
  4. 紅色部份是關於 transaction 的管理。
設定到這裡基本上是完成了,接著要為每個 table 建立一個相對應的 table,這裡舉 Sales 為例。
@Entity
public class Sales implements Serializable {
    private static final long serialVersionUID = 2672157806527049297L;
    
    private String emNo;
    private String name;
    
    public Sales() {}
    
    public Sales(String emNo, String name) {
        this.emNo = emNo;
        this.name = name;
    }

    @Id
    public String getEmNo() {
        return emNo;
    }
    public void setEmNo(String emNo) {
        this.emNo = emNo;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Sales [emNo=" + emNo + ", name=" + name + "]";
    }
}
如上所示,類別前加上 @Entity,primary key 相關欄位的 getter method 前則加上 @Id。
完成每個 Entity 後,接著寫 DAO,一般來說會先為每個 DAO 建立一個 interface (非必要)。
public interface SalesDao {
    public List<Sales> findAll();
    public List<Customer> findCustomerByEmno(String emno);
}
這個 interface 規範了 DAO 將會有兩個 method,第一個查詢出所有的 sales,第二個查詢出指定的 sales 的所有客戶,DAO 當然要實作這個介面,如下:
@Repository
public class SalesDaoImpl implements SalesDao {
    @PersistenceContext
    private EntityManager entityManager;
      
    @SuppressWarnings("unchecked")
    @Override
    public List<Sales> findAll() {
        Query query = entityManager.createQuery("from Sales as s order by s.emNo");
        return query.getResultList();
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Customer> findCustomerByEmno(String emNo) {
        Query query = entityManager.createNativeQuery("select c.cid, c.name, c.emNo from Sales s join Customer c on s.emNo = c.emNo where c.emNo = :emNo order by c.emNo, c.cid", Customer.class);
        query.setParameter("emNo", emNo);
        return query.getResultList();
    }
} 
DAO 類別前要加上 @Repository,以供 spring 識別並載入,DAO 中要注入 EntityManager 連線到資料庫,要特別注意的是,這裡不是以 @Autowired 注入,一定要用 @PersistenceContext。設定程式都寫好,寫個小程式測試一下吧 ~ 結果當然可以正常執行 ... 至於上面 DAO 中如何進行查詢,為什麼有兩種方法? 待後續說明。
public class App1 {
    static ApplicationContext context = null;
    
    @Autowired
    private SalesDaoImpl salesDao;
    
    public void run() {
        List<Sales> sales = salesDao.findAll();
        for(Sales s:sales) {
            System.out.println(s.toString());
        }
        
        List<Customer> customers = salesDao.findCustomerByEmno("007528");
        for(Customer c:customers) {
            System.out.println(c.toString());
        }
    }

    public static void main(String[] args) {
        context = new ClassPathXmlApplicationContext("classpath:beans-config.xml");
        App1 app1 = (App1) context.getBean("app1");
        app1.run();
    }
}
  • see also
JPA + Hibernate + Spring + JUnit (annotation)





【日劇 - 別讓我走】
以複製人為主題,綾瀨遙主演,應該是一齣可以讓觀眾產生許多想法的影集,但是,其中許多不合理的劇情,尤其是複製人擁有部份的人身自由,可在申請之下與外界接觸,且不同複製人所居住的農舍間並不禁止相互往來,這應該會提供複製人集結反抗的機會,編劇卻讓複製人多半順從於社會體制,這就弱化了複製人對人類社會衝擊所產生的戲劇張力。

2016年3月6日 星期日

Hibernate: 與 spring framework 整合


在 JavaEE 領域裡,程式開發不用 spring framework 的非常少,這裡說明 Hibernate 如何與 spring 整合。延續之前例子,設定檔如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd  
        ">
        
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="ignoreUnresolvablePlaceholders" value="false"/>
        <property name="locations">
          <list>
            <value>classpath:datasource.properties</value>
          </list>
        </property>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}"
        p:username="${jdbc.username}"
        p:password="${jdbc.password}"
    />

    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" 
        p:dataSource-ref="dataSource"
    >
        <property name="mappingResources">
            <list>
                <value>idv/steven/orm/market/Customer.hbm.xml</value>
                <value>idv/steven/orm/market/Goods.hbm.xml</value>
                <value>idv/steven/orm/market/Orders.hbm.xml</value>
                <value>idv/steven/orm/market/Sales.hbm.xml</value>
            </list>
        </property>
        
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"
        p:sessionFactory-ref="sessionFactory">
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate"
        p:sessionFactory-ref="sessionFactory"
    />
    
    <bean id="myApp6" class="idv.steven.orm.market.MyApp6">
    </bean>
</beans>
上面的設定檔,與 hibernate 有關的部份,我用紅、綠、藍三色標示出來,整合的方式非常簡單,說明如下:
  1. 在spring 的 bean 設定檔中加入 hibernate 的 sessionFactory (紅色)
  2. 將 transaction 的管理委由 spring 來管理 (綠色)
  3. 宣告一個 HibernateTemplate,這是 spring 提供的一個模板類別,方便程式設計者對資料庫進行 CRUD,這一部份不是必要的,因為 spring 還提供有其它方法,這一篇先說明這個方法。(藍色)
接著寫個測試程式,所有 session 委由 spring 來管理,所以程式的寫法又更精簡了!
 1 public class MyApp6 {
 2     @Autowired
 3     private HibernateTemplate ht;
 4     
 5     public void query() {
 6         Sales s1 = ht.get(Sales.class, "007528");
 7         Iterator<Customer> customer = s1.getCustomer().iterator();
 8         while (customer.hasNext()) {
 9             Customer c = customer.next();
10             System.out.println(c.toString());
11         }
12     }
13     
14     public static void main(String[] args) {
15          ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans-config.xml");
16          MyApp6 myApp6 = context.getBean(MyApp6.class);
17          myApp6.query();
18     }
19 }
第 2~3 行透過 spring 的自動注入機制取得 HibernateTemplate,並使用它來查詢出我們要的資料,這程式執行結果會查詢出編員為007528的這位業務員的所有客戶。
【注意】
Sales.hbm.xml 的設定,lazy 要設定為 false,否則在上面程式的第 7 行會因為延遲載入而發生錯誤!
(錯誤訊息: failed to lazily initialize a collection of role:  could not initialize proxy - no Session)
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="idv.steven.orm.market.entity">
    <class name="Sales" table="SALES">
        <id name="emNo" type="java.lang.String">
            <column name="EMNO" />
            <generator class="assigned" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        
        <set name="customer" inverse="true" lazy="false" fetch="select">
            <!-- foreign key -->
            <key column = "EMNO" />
            <one-to-many class="Customer" />
        </set>
    </class>
</hibernate-mapping>