2015年10月31日 星期六

Query、SQLQuery、Criteria 快取 (Hibernate3.x 十一)

前面的一級、二級快取對Query沒有作用,要有另外的設定,我指的Query包括Query、SQLQuery、Criteria,以下都講Query

Query q1 = s.createQuery(hql);
Emp e1 = (Emp) q1.uniqueResult();
System.out.println("e1:" + e1.getEname());
    
Emp e2 = (Emp) s.get(Emp.class, 7369);
System.out.println("e2:" + e2.getEname());

※此時get可以讀到快取,看起來好像ok

Emp e2 = (Emp) s.get(Emp.class, 7369);
System.out.println("e2:" + e2.getEname());
    
Query q1 = s.createQuery(hql);
q1.setCacheable(true);
Emp e1 = (Emp) q1.uniqueResult();
System.out.println("e1:" + e1.getEname());

※但把get放在前面就讀不到了,看起來Query會放到快取裡,所以get讀得到
※但相反後Query無法讀取get的快取,看起來Query不去讀取快取的樣子
※我試Query、Criteria都是這樣的結果,但SQLQuery怎麼放都不行


想讓Query讀得到,必需在hibernate.cfg.xml設定
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.QueryCacheEnabled">true</property>
    
<property name="hibernate.cache.use_query_cache">true</property>

※前三行是二級快取的設定,後面才是現在要的設定

※這個設定一定要設二級快取,不然執行時會報「org.hibernate.cache.NoCachingEnabledException: Second-level cache is not enabled for usage [hibernate.cache.use_second_level_cache | hibernate.cache.use_query_cache]」的錯,也就是Query只能用二級快取


Session s = HibernateUtil2.getSession();
    
final String sql = "select * from Emp where empno = 7369";
final String hql = "from Emp where empno = 7369";
    
try {
    Query q1 = s.createSQLQuery(sql).addScalar("ename",
            Hibernate.STRING);
    q1.setCacheable(true);
    System.out.println("e1:" + q1.uniqueResult());
    
    Query q2 = s.createSQLQuery(sql).addScalar("ename",
            Hibernate.STRING);
    q2.setCacheable(true);
    System.out.println("e2:" + q2.uniqueResult());
    
    Query q3 = s.createQuery(hql);
    q3.setCacheable(true);
    Emp e3 = (Emp) q3.uniqueResult();
    System.out.println("e3:" + e3.getEname());
    
    Query q4 = s.createQuery(hql);
    q4.setCacheable(true);
    Emp e4 = (Emp) q4.uniqueResult();
    System.out.println("e4:" + e4.getEname());
    
    Criteria q5 = s.createCriteria(Emp.class);
    q5.setCacheable(true);
    q5.add(Restrictions.eq("empno", 7369));
    Emp e5 = (Emp) q5.uniqueResult();
    System.out.println("e5:" + e5.getEname());
    
    Criteria q6 = s.createCriteria(Emp.class);
    q6.setCacheable(true);
    q6.add(Restrictions.eq("empno", 7369));
    Emp e6 = (Emp) q6.uniqueResult();
    System.out.println("e6:" + e6.getEname());
    
    Emp xxx = (Emp) s.get(Emp.class, 7369);
    System.out.println("xxx:" + xxx.getEname());
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※只有設定還不夠,還要在程式寫setCacheable(true),而且每一個Query都要,不然都抓不到快取

※每個快取不能混用,如SQLQuery對SQLQuery、Criteria對Criteria、Query對Query,混用一樣也是抓不到快取

※以第二張圖的程式碼,加上setCacheable(true),還是抓不到快取

管理二級快取 (Hibernate3.x 十)

※二級快取模式

SessionFactory sf = HibernateUtil2.getSessionFactory();
Session s1 = sf.openSession();
Session s2 = sf.openSession();
    
    try {
    //    CacheMode.GET;
    //    CacheMode.IGNORE;
    //    CacheMode.NORMAL;
    //    CacheMode.PUT;
    //    CacheMode.REFRESH;
        
        
    //    s1.setCacheMode(CacheMode.GET);
    //    s2.setCacheMode(CacheMode.PUT);
    s2.setCacheMode(CacheMode.REFRESH);
    
    Emp e1 = (Emp) s1.get(Emp.class, 7369);
    System.out.println("e1:" + e1.getEname());
    
    Emp e2 = (Emp) s2.get(Emp.class, 7369);
    System.out.println("e2:" + e2.getEname());
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s1.isOpen()) {
        s1.close();
    }
    if (s2.isOpen()) {
        s2.close();
    }
}

※CacheMode共有五種模式:
.GET:向二級快取讀取,update時才有寫入

.IGNORE:忽略二級快取

.NORMAL:向二級快取讀、寫

.PUT:向二級快取寫入不讀

.REFRESH:向二級快取寫入不讀,設定hibernate.cache.use_minimal_puts可強制讀取資料庫

官方說明在這裡,沒有介紹IGNORE,但程式碼確實是有的,反正就是不用快取,沒什麼好講的

※如以上的程式如果只有設定二級快取設定時,就會有二級快取
※增加一行s1.setCacheMode(CacheMode.GET),就沒有快取了,因為GET讀取,沒有將資料寫入快取,所以s2讀不到
※而 s2.setCacheMode(CacheMode.PUT),因s1還是有快取,但s2只寫不讀一樣讀不到
※而REFRESH和PUT在這個例子一樣


※清除二級快取

SessionFactory sf = HibernateUtil2.getSessionFactory();
Session s1 = sf.openSession();
Session s2 = sf.openSession();
    
Emp e1 = (Emp) s1.get(Emp.class, 7369);
System.out.println("e1:" + e1.getEname());
    
sf.evict(Emp.class);
    
Emp e2 = (Emp) s2.get(Emp.class, 7369);
System.out.println("e2:" + e2.getEname());

※雖然可清除快取,但此方法已廢棄了,而且看起來也沒有類似clear()的清除全部的方法

二級快取 (Hibernate3.x 九)

二級快取又稱SessionFactory快取,必需設定才有辦法使用

※二級快取

二級快取必需由第三方廠商提供,3.6有支緩的廠商在這裡,各家的差別在這裡,由於EHCache看起來都有支緩,所以現在以EHCache為例

pom.xml
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
    <version>3.6.10.Final</version>
</dependency>

※先抓個jar檔


hibernate.cfg.xml
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.QueryCacheEnabled">true</property>

※從這裡的Second-level caching複製

Emp.hbm.xml
<class name="vo.Emp" table="Emp">
    <cache usage="read-only" />
    <id name="empno" type="int">
        ...
    </id>
    <property name="ename" type="java.lang.String">
        ...
    </property>
    ...
</class>

※順序要正確,如果把cache打在最下面,執行時會出「org.hibernate.InvalidMappingException: Unable to read XML」的錯


Emp.java
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@Entity
@Table(name = "DEPT", schema = "c##scott")
public class Dept {
    ...
}

※打在class層級的地方,xml和annotation選其一即可,只是hibernate.cfg.xml的mapping resource和mapping class要打對


TestHibernate.java
SessionFactory sf = HibernateUtil2.getSessionFactory();
Session s1 = sf.openSession();
Session s2 = sf.openSession();
    
try {
    Emp e1 = (Emp) s1.get(Emp.class, 7369);
    Emp e2 = (Emp) s2.get(Emp.class, 7369);
    System.out.println("e1:" + e1.getEname());
    System.out.println("e2:" + e2.getEname());

} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s1.isOpen()) {
        s1.close();
    }
    if (s2.isOpen()) {
        s2.close();
    }
}

※因為有用二級快取,所以只會查詢一次

還可以對整個package設定二級快取,這樣hbm.xml或annotation就可以不用設定了,如下:

hibernate.cfg.xml
<session-factory>
        ...
        <mapping class="vo.Dept" />
        <mapping class="vo.Emp" />
    <class-cache usage="read-only" class="vo.Emp"/>
</session-factory>

※打在最下面是ok的,但其他順序會出現「org.hibernate.MappingException: invalid configuration」的錯

※usage有以下五種
.NONE:不設定快取,annotation才有,xml不打就可以了

.READ_ONLY:查詢才有快取,修改和刪除會出「java.lang.UnsupportedOperationException: Can't write to a readonly object」錯,新增ok,但沒快取


------以下三個選項在JPA環境要設定hibernate.transaction.manager_lookup_class------
以下三個選項我沒試,是參考網友的文章和官方的文件寫的

.NONSTRICT_READ_WRITE:增刪改查都會快取,但資料改變時,快取資料不會立刻修改,進行下一次查詢時才會重新保存,所以才會叫nonstrict(不嚴格)

.READ_WRITE:增刪改查都有快取

.TRANSACTIONAL:commit有快取,rollback也會回滾快取

官方可參考這裡

2015年10月30日 星期五

一級快取與Session.flush() (Hibernate3.x 八)

一級快取,又名Session級快取,因為生命週期和Session一樣,Session消毀,它也同時消毀;可用 clear 和 evict 管理

※一級快取

Session s1 = HibernateUtil2.getSession();
Session s2 = HibernateUtil2.getSession();
try {
    Emp e1 = (Emp) s1.get(Emp.class, 7369);
    System.out.println("first:" + e1.getEname());
    Emp e2 = (Emp) s2.get(Emp.class, 7369);
    System.out.println("second:" + e2.getEname());
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if(s1.isOpen()){
        s1.close();
    }
    if(s2.isOpen()){
        s2.close();
    }
}

※以控制台出現訊息為標準,預設就有一級快取,因為只有Select一次


SessionFactory sf = HibernateUtil2.getSessionFactory();
Session s1 = sf.openSession();
Session s2 = sf.openSession();
    
Emp e1 = (Emp) s1.get(Emp.class, 7369);
System.out.println("first:" + e1.getEname());
Emp e2 = (Emp) s2.get(Emp.class, 7369);
System.out.println("second:" + e2.getEname());

※有快取是因為基於同一個Session,像上面的程式碼,有兩個Session,就沒有快取了,會輸出兩次Select


Session s = HibernateUtil2.getSession();
    
Emp emp = new Emp();
emp.setEmpno(7369);
emp.setEname("XXX");
s.update(emp);
s.beginTransaction().commit();
    
Emp e = (Emp) s.get(Emp.class, 7369);
System.out.println("xxx:" + e.getEname());

※update時也一樣有快取,此時控制台輸出update語句,但沒有Select,照樣查詢的到
※save和delete我試的結果,在控制台除了insert和delete語句外,還是有Select語句

※管理快取

尤於上面的程式碼會快取,但如果有100萬條或更多的記錄也把它快取下來,這樣子極有可能造成效能變低和OutOfMemoryException,記憶體爆了,所以有以下兩種方法來管理快取


Session s = HibernateUtil2.getSession();
    
Emp emp = new Emp();
emp.setEmpno(7369);
emp.setEname("SMITH");
s.update(emp);
s.beginTransaction().commit();
    
s.clear();
    
Emp e = (Emp) s.get(Emp.class, 7369);
System.out.println("second:" + e.getEname());


※可以用clear清除全部快取,所以查詢有語句


Session s = HibernateUtil2.getSession();
    
//    Emp e1 = (Emp) s.get(Emp.class, 7369);
//    System.out.println("e1:" + e1.getEname());
        
Emp e1 = new Emp();
e1.setEmpno(7369);
e1.setEname("SMITH");
s.update(e1);
s.beginTransaction().commit();
    
Dept d1 = (Dept) s.get(Dept.class, 20);
System.out.println("d1:" + d1.getDname());
    
s.evict(e1);
    
Emp e2 = (Emp) s.get(Emp.class, 7369);
System.out.println("e2:" + e2.getEname());
    
Dept d2 = (Dept) s.get(Dept.class, 20);
System.out.println("d2:" + d2.getDname());

※如果只想清除某個物件的快取,就要用evict,像我是將e1的快取清除,所以控制台輸出update語句,然後輸出Select Dept的語句,但因為有evict(e1),所以Select Emp也輸出了

※如果有二級快取,就沒有效果了


※Session.flush()

Session s = HibernateUtil2.getSession();
    
Emp emp = new Emp();
emp.setEmpno(7369);
emp.setEname("SMITH");
s.update(emp);
s.flush();
// s.beginTransaction().commit();

※像上面的程式碼,因為commit註解掉,控制台理論上是空白的,但因為有flush(),所以會輸出語句,但因為沒commit(),所以資料庫沒作用

※用commit(),其實也會flush後再提交

※flush總共有五種模式,先看原始碼的註解怎麼說
/**
 * The {@link Session} is never flushed unless {@link Session#flush}
 * is explicitly called by the application. This mode is very
 * efficient for read only transactions.
 *
 * @deprecated use {@link #MANUAL} instead.
 */
public static final FlushMode NEVER = new FlushMode( 0, "NEVER" );
    
/**
 * The {@link Session} is only ever flushed when {@link Session#flush}
 * is explicitly called by the application. This mode is very
 * efficient for read only transactions.
 */
public static final FlushMode MANUAL = new FlushMode( 0, "MANUAL" );
    
/**
 * The {@link Session} is flushed when {@link Transaction#commit}
 * is called.
 */
public static final FlushMode COMMIT = new FlushMode(5, "COMMIT");
    
/**
 * The {@link Session} is sometimes flushed before query execution
 * in order to ensure that queries never return stale state. This
 * is the default flush mode.
 */
public static final FlushMode AUTO = new FlushMode(10, "AUTO");
    
/**
 * The {@link Session} is flushed before every query. This is
 * almost always unnecessary and inefficient.
 */
public static final FlushMode ALWAYS = new FlushMode(20, "ALWAYS");

※NEVER:此模式不能flush,除非明確呼叫應用程式,在只有唯讀時,效能很高,此模式已廢棄了,要使用MANUAL代替

※MANUAL:當明確呼叫應用程式,此模式永遠flush,在唯讀時,效能很高

※COMMIT:當呼叫commit時,清除快取

※AUTO:此Session查詢執行之前有時會flush,以確保查詢不會返回已經失效的狀態,這是預設的選項

※ALWAYS:在所有查詢之前都會清除快取,這幾乎是不必要且效率低的

Session s = HibernateUtil2.getSession();
// 五種模式
s.setFlushMode(FlushMode.AUTO);
s.setFlushMode(FlushMode.ALWAYS);
s.setFlushMode(FlushMode.COMMIT);
s.setFlushMode(FlushMode.MANUAL);
s.setFlushMode(FlushMode.NEVER);
    
System.out.println("first");
Emp e1 = (Emp) s.load(Emp.class, 7369);
s.beginTransaction().commit();
    
System.out.println("second");
Emp e2 = (Emp) s.get(Emp.class, 7369);
s.beginTransaction().commit();


※整理如下:
.NEVER:等同MANUAL,因以廢棄,註解也要我們使用MANUAL

.MANUAL:不使用flush

.COMMIT:commit前flush

.AUTO:這個我不知如何試,註解也是說有時會,什麼是「有時會」並沒有說清楚

.ALWAYS:這個我試不出來,以上面的程式碼來說,我用load,畫面還是一片空白,不過反正註解都說是效率低的,就不要用就好了

找到這篇文章,已後有時間再測

2015年10月28日 星期三

Eclipse import另一個專案

假設有兩個專案Test1和Test2,Test2寫個public void helloWorld(),但Test1和Test2不同專案,所以import不到,這時有兩種方式解決

※打包jar

選File->Export後,如下畫面


勾想要打包的專案並輸入匯出的路徑
然後將jar檔放在lib下即可import的到了
但每次改程式都要重包一次很麻煩,所以Eclipse還有提供另外一種方式,如下:

※Eclipse設定

選擇專案右鍵Properties

在左邊選擇Java Build Path,然後選擇Projects活頁標籤,然後按Add按鈕,會出現所有的專案,選擇想要import的專案即可
這種好處是改了檔案後,直接就抓的到了


2015年10月27日 星期二

Criteria1 (Hibernate3.x 七)

※一般使用

Criteria c1 = session.createCriteria(Dept.class);
c1.setFirstResult(0);
c1.setMaxResults(3);
    
@SuppressWarnings("unchecked")
List<Dept> l = c1.list();
for (Dept d : l) {
    System.out.println(d.getDname());
}
System.out.println("----------");
    
Criteria c2 = session.createCriteria(Dept.class);
c2.add(Restrictions.eq("deptno", 20));
Dept d = (Dept) c2.uniqueResult();
System.out.println(d.getDname());
System.out.println("----------");
    
Criteria c3 = session.createCriteria(Dept.class);
c3.add(Restrictions.between("deptno", 20, 40));
for (Object o : c3.list()) {
    System.out.println(((Dept) o).getDname());
}
System.out.println("----------");
    
List<String> inCondition = new ArrayList<String>();
inCondition.add("SALESMAN");
inCondition.add("IT");
inCondition.add("PRESIDENT");
    
Criteria c4 = session.createCriteria(Emp.class);
c4.add(Restrictions.in("job", inCondition));
Iterator<?> it = c4.list().iterator();
while (it.hasNext()) {
    System.out.println(((Emp) it.next()).getEname());
}
System.out.println("----------");
    
Criteria c5 = session.createCriteria(Emp.class);
c5.add(Restrictions.like("job", "%中%"));
Iterator<?> i = c5.list().iterator();
while (i.hasNext()) {
    System.out.println(((Emp) i.next()).getEname());
}

※org.hibernate.criterion.Restrictions還有很多看就會的方法
gt:大於(greater than)
lt:小於(less than)
eq:等於(equal)
ge:大於等於(greater than and equal)
le:小於等於(less than and equal)
ne:不等於(not equal)
※以上六個在xml、jQuery都是類似的
※between、like、in、and、or、not:和SQL一樣
※舊版的Hibernate用得是org.hibernate.criterion.Expression,可是這個方法在3.6版已經過時了

※排序

Criteria criteria = session.createCriteria(Emp.class);
criteria.add(Restrictions.and(
    Restrictions.between("sal", 1500d, 3000d),
    Restrictions.like("ename", "T%"))
);
criteria.addOrder(Order.desc("empno"));
Iterator<?> it = criteria.list().iterator();
while (it.hasNext()) {
    System.out.println(((Emp) it.next()).getEname());
}


※匯總函數

Criteria criteria = session.createCriteria(Emp.class);
ProjectionList plist = Projections.projectionList();
plist.add(Projections.rowCount());
plist.add(Projections.avg("sal"));
plist.add(Projections.groupProperty("deptno"));
criteria.setProjection(plist);
    
@SuppressWarnings("unchecked")
List<Object[]> list = criteria.list();
for (Object[] oA : list) {
    for (Object o : oA) {
        System.out.println(o);
    }
}

※其實就是select count(*), avg(sal) from emp group by deptno

※org.hibernate.criterion.Projections也有很多看就會的方法
如avg、count、distinct、max、min、rowCount、sum等

更多的用法請參考三十五篇

2015年10月25日 星期日

HQL 的CRUD 及分頁 (Hibernate3.x 六)

※刪除

Session session = HibernateUtil2.getSession();
try {
    Query hqlDelete = session.createQuery(" delete from Dept where deptno = :deptno");
    hqlDelete.setParameter("deptno", 60);
    int rtn = hqlDelete.executeUpdate();
    if(rtn > 1){
        System.out.println("刪除成功!");
    } else {
        System.out.println("無資料可刪除!");
    }
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    session.close();
}

※因為hibernate是資料庫和物件的轉換,而HQL是物件的操作,所以必需注意Dept大小寫要和hbm.xml的java類名稱一致,否則會出「xxx is not mapped」的錯誤

※刪除時,from可以不寫

※hibernate-mapping有個屬性auto-import,預設是true,所以Dept才可以不用寫包.類,但如果有兩個以上都是同名,還是會報錯,還有另一種方法,就是使用別名

Dept.hbm.xml

<hibernate-mapping>
    <import class="vo.Dept" rename="xxx"/>
    <class>
        <!-- ... -->
    </class>
</hibernate-mapping>

※這時就可以使用xxx代替Dept了(class一定要包.類),但別名如果和其他的class同名(cfg.xml有設定mapping resource或mapping class),而且auto-import是true或沒設,假設我打Emp,這時就會出「duplicate import: Emp refers to both vo.Dept and vo.Emp (try using auto-import="false")」的錯



※修改

Session session = HibernateUtil2.getSession();
try {
    Query hqlUpdate = session.createQuery(" update Dept set dname = ?, loc = :loc where deptno = 60 ");
    hqlUpdate.setString(0, "IT");
    hqlUpdate.setString("loc", "zh_CN");
    int rtn = hqlUpdate.executeUpdate();
    if(rtn > 1){
        System.out.println("修改成功!");
    } else {
        System.out.println("無資料可修改!");
    }
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    session.close();
}

※Query有很多的setXxx可以用,還可以像刪除那樣,用setParameter,會自動偵測

※Query的第一個參數可用數字或字串,數字從0開始,字串必須對應「:字串」,也可以混用

※混用時必須注意數字(也就是?)必須在字串前面,不然會出「cannot define positional parameter after any named parameters have been defined」的錯

※新增

Session session = HibernateUtil2.getSession();
try {
    Query hqlInsert = session.createQuery(" insert into Dept(deptno, dname, loc) select 60, 'IT', 'zh_TW' from Emp where empno = 7369 ");
    int rtn = hqlInsert.executeUpdate();
    if(rtn > 1){
        System.out.println("新增成功!");
    } else {
        System.out.println("無資料可新增!");
    }
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    session.close();
}

※新增不支援values的寫法,這裡的最後一張圖,往上一點點,only開頭的有寫
可能是hibernate一定要先查詢才有辦法做到吧!

※新增時不能用setXxx,像我是寫死的,這個例子舉的不好

※所以大部分新增都是用save()或者是Session的createSQLQuery()達成

※createSQLQuery(),裡面的SQL就是怕hibernate的功能不能達到時,可以根據各家的SQL進行撰寫,而且還加入一些較特殊的用法

※查詢

Session session = HibernateUtil2.getSession();
try {
    Query hqlQuery = session.createQuery(" from Dept ");
    List<?> list = hqlQuery.list();
    for(Object o:list){
        Dept d = (Dept) o;
        System.out.println(d.getDeptno());
    }
} catch (Exception e) {
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    session.close();
}

※不能寫select *,會說「*」是不認識的字元

※from Dept 等同 select d from Dept d

※List<Object>會出黃黃的警告,所以我用List<?>

※取特定的欄位

Query hqlQuery1 = session.createQuery(" select dname from Dept ");
List<?> list = hqlQuery1.list();
for(Object o:list){
    System.out.println(o);
}



Query hqlQuery2 = session.createQuery(" select deptno, dname, loc from Dept ");
    
@SuppressWarnings("unchecked")
List<Object[]> list = hqlQuery2.list();
for (Object[] oArray : list) {
    for (Object o : oArray) {
        System.out.println(o);
        System.out.println("-----");
    }
}

※所以只有一個欄位回傳的是List<Object>;
多個欄位就是List<Object[]>,官方說一條結果返回多個物件叫tuples

※尤於沒有List<?[]>,因為?是什麼都不知道了,怎麼可能還有陣列,所以只好寫List<Object[]>

※將欄位轉換成VO物件

Query hqlQuery = session.createQuery(" select deptno as deptno, dname as dname, loc as loc from Dept ");
    
hqlQuery.setResultTransformer(new AliasToBeanResultTransformer(Dept.class));
List<?> list = hqlQuery.list();
Iterator<?> it = list.iterator();
while(it.hasNext()){
    Dept d = (Dept) it.next();
    System.out.println(d.getDname());
}

※雖然select的欄位名稱和vo物件一樣,但還是要「as欄位名」,連as都要打,as能省略的只有物件名,如from Dept d,如果不打會出「PropertyNotFoundException: Could not find setter for 0 on class vo.Dept」的錯

※將SQL或HQL分離

※XML設定


hbm.xml
<class name="vo.Dept" table="DEPT">
    <!-- ... -->
</class>
<query name="xxx">from Dept</query>
<sql-query name="ooo">SELECT * FROM DEPT</sql-query>

※query name為hql,類似createQuery(); sql-query為sql,類似createSQLQuery()

TestHibernate.java
Query hqlQuery = session.getNamedQuery("xxx");
List<?> listXxx = hqlQuery.list();
for(Object o:listXxx){
    Dept d = (Dept)o;
    System.out.println(d.getDname());
}
    
Query sqlQuery = session.getNamedQuery("ooo");
List<Object[]> listOoo = sqlQuery.list();
    
for(Object[] oArray:listOoo){
    for(Object o:oArray){
        System.out.println(o);
    }
}

※Annotation設定

Dept.java
@NamedQueries({ @NamedQuery(name = "xxx", query = "from Dept") })
@NamedNativeQueries({ @NamedNativeQuery(name = "ooo", query = "SELECT * FROM DEPT", resultClass=Dept.class) })
@Entity
@Table(name = "DEPT", schema = "c##scott")
public class Dept {
    //...
}

※sql一定要加resultClass,不然執行時會報「org.hibernate.cfg.NotYetImplementedException: Pure native scalar queries are not yet supported」的錯; 而hql不用,也沒這個選項可設定

TestHibernate.java
Query query = session.getNamedQuery("ooo");
List<?> list = query.list();
    
for(Object o:list){
    Dept d = (Dept)o;
    System.out.println(d.getDname());
}

※用Annotation時輸出的結果,統一都用hql的寫法才不會報強制轉換的錯誤

※回傳只有一筆

Query hql = session.createQuery(" from Dept where deptno = 20 ");
Dept dept1 = (Dept) hql.list().get(0);
Dept dept2 = (Dept) hql.uniqueResult();
    
System.out.println(dept1.getDname());
System.out.println(dept2.getDname());

※使用uniqueResult()看起來較方便

※分頁

Query hql = session.createQuery(" from Emp ");
hql.setFirstResult(2);//從第幾筆開始
hql.setMaxResults(3);//一頁顯示幾筆
    
System.out.println("從0開始,機器的角度");
Iterator<?> it1 = hql.list().iterator();
while (it1.hasNext()) {
    Emp e = (Emp) it1.next();
    System.out.println(e.getEmpno());
}
    
System.out.println("從1開始,人的角度");
Iterator<?> it2 = hql.iterate();
while (it2.hasNext()) {
    Emp e = (Emp) it2.next();
    System.out.println(e.getEmpno());
}

※資料庫的內容
第一個迴圈的結果為: 7521、7566、7654

第二個迴圈的結果為: 7499、7521、7566

※工作時常這樣用

※機器角度的算法:
int currentPage = 3;//模擬前端輸入第幾頁
int pageSize = 3;
    
Query hql = session.createQuery(" from Emp ");
hql.setFirstResult((currentPage - 1) * pageSize);//公式
hql.setMaxResults(pageSize);
    
Iterator<?> it1 = hql.list().iterator();
while (it1.hasNext()) {
    Emp e = (Emp) it1.next();
    System.out.println(e.getEmpno());
}

※注意setFirstResult的算法

結果為: 7782、7788、7839




人角度的算法:
int currentPage = 3;//模擬前端輸入第幾頁
int pageSize = 3;
    
Query hql = session.createQuery(" from Emp ");
hql.setFirstResult(currentPage * pageSize - pageSize + 1);//公式
hql.setMaxResults(pageSize);
    
Iterator<?> it2 = hql.iterate();
while (it2.hasNext()) {
    Emp e = (Emp) it2.next();
    System.out.println(e.getEmpno());
}

結果當然還是和上面一樣

如果在一級或二級快取已有資料,那iterate()比list()快,否則較慢

2015年10月23日 星期五

使用HibernateUtil做PK的CRUD (Hibernate3.x 四)

依照前面兩篇合起來做一個測試,只針對Emp做測試

※使用XML設定


hibernate.cfg.xml
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@127.0.0.1:1521:orcl</property>
        <property name="hibernate.connection.username">account</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.connection.autocommit">true</property>
    
        <property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
        <property name="hibernate.current_session_context_class">thread</property>
    
        <mapping resource="vo/Emp.hbm.xml" />
    </session-factory>
</hibernate-configuration>

Emp.hbm.xml
<hibernate-mapping>
    <class name="vo.Emp" table="Emp">
        <id name="empno" type="int">
            <column name="EMPNO" />
            <generator class="assigned" />
        </id>
        <property name="ename" type="java.lang.String">
            <column name="ENAME" />
        </property>
        <property name="job" type="java.lang.String">
            <column name="JOB" />
        </property>
        <property name="mgr" type="java.lang.Integer">
            <column name="MGR" />
        </property>
        <property name="hiredate" type="java.util.Date">
            <column name="HIREDATE" />
        </property>
        <property name="sal" type="java.lang.Double">
            <column name="SAL" />
        </property>
        <property name="comm" type="java.lang.Double">
            <column name="COMM" />
        </property>
        <property name="deptno" type="java.lang.Integer">
            <column name="DEPTNO" />
        </property>
    </class>
</hibernate-mapping>

Emp.java
private int empno;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;
    
//setter/getter

DAOFactory.java
public class DAOFactory {
    public static IEmpDAO getIEmpDAOInstance() {
        return new EmpDAOImpl() ;
    }
}

IEmpDAO.java
public interface IEmpDAO {
    public boolean saveOrUpdate(Emp vo) throws Exception;
    public boolean delete(int id) throws Exception;
    public Emp load(int id) throws Exception;
}

EmpDAOImpl.java
public class EmpDAOImpl implements IEmpDAO {
    @Override
    public boolean saveOrUpdate(Emp vo) throws Exception {
        // 使用官方的HibernateUtil
        Transaction tx = HibernateUtil.getSessionFactory().getCurrentSession()
                .beginTransaction();
        HibernateUtil.getSessionFactory().getCurrentSession().saveOrUpdate(vo);
        tx.commit();
    
        // 使用自定的 HibernateUtil
        // HibernateUtil2.getSession().saveOrUpdate(vo);
        // HibernateUtil2.getSession().flush();
        return true;
    }
    
    @Override
    public boolean delete(int id) throws Exception {
        // 使用官方的HibernateUtil
        Transaction tx = HibernateUtil.getSessionFactory().getCurrentSession()
                .beginTransaction();
        HibernateUtil.getSessionFactory().getCurrentSession()
                .delete(this.load(id));
        tx.commit();
    
        // 使用自定的 HibernateUtil
        // HibernateUtil2.getSession().delete(this.load(id));
        // HibernateUtil2.getSession().flush();
        return true;
    }
    
    @Override
    public Emp load(int id) throws Exception {
        // 使用官方的HibernateUtil
        HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
        return (Emp) HibernateUtil.getSessionFactory().getCurrentSession()
                .load(Emp.class, id);
    
        // 使用自定的 HibernateUtil
        // return (Emp) HibernateUtil2.getSession().load(Emp.class, id);
    }
}

IEmpServiceTest
public class IEmpServiceTest {
    
    // @Test
    public void testSaveOrUpdate() throws Exception {
        Emp emp = new Emp();
        emp.setEmpno(1);
        emp.setEname("bruce");
        emp.setJob("IT");
        emp.setMgr(7839);
        emp.setHiredate(new Date());
        emp.setSal(20000.35);
        emp.setComm(251.13);
        emp.setDeptno(20);
        boolean rtn = DAOFactory.getIEmpDAOInstance().saveOrUpdate(emp);
        TestCase.assertEquals(rtn, true);
    }
    
    @Test
    public void testDelete() throws Exception {
        boolean rtn = DAOFactory.getIEmpDAOInstance().delete(1);
        TestCase.assertEquals(rtn, true);
    }
    
    // @Test
    public void testLoad() throws Exception {
        Emp emp = DAOFactory.getIEmpDAOInstance().load(1);
        TestCase.assertEquals(emp != null, true);
    }
    
    @After
    public void testCloseSession() {
        if (HibernateUtil2.getSession().isOpen()) {
            System.out.println("session2 未關閉!");
            HibernateUtil2.getSession().close();
        } else {
            System.out.println("session2 已關閉!");
        }
    
        if (HibernateUtil.getSessionFactory().getCurrentSession().isOpen()) {
            System.out.println("session 未關閉!");
            HibernateUtil.getSessionFactory().close();
        } else {
            System.out.println("session 已關閉!");
        }
    }
}

這一次的專案圖:


※HibernateUtil和HibernateUtil2,依照IEmpServiceTest.java的測試,看起來都是使用同一個session
※使用官方的HibernateUtil一定要beginTransaction(查詢也要),最後也要commit(查詢可不用)
※使用自定的HibernateUtil只要flush即可

※使用Annotation設定

將xml設定的hibernate.cfg.xml改成下面的樣子
<!-- <mapping resource="vo/Dept.hbm.xml" /> -->
<mapping class="vo.Emp" />

Emp.java
package vo;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name = "EMP", catalog = "c##scott") //, schema = "c##scott")
public class Emp {
    private int empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;
    
    @Id
    @Column(name = "EMPNO", unique = true, nullable = false)
    public int getEmpno() {
        return empno;
    }
    
    public void setEmpno(int empno) {
        this.empno = empno;
    }
    
    @Column(name = "ENAME")
    public String getEname() {
        return ename;
    }
    
    public void setEname(String ename) {
        this.ename = ename;
    }
    
    @Column(name = "JOB")
    public String getJob() {
        return job;
    }
    
    public void setJob(String job) {
        this.job = job;
    }
    
    @Column(name = "MGR")
    public Integer getMgr() {
        return mgr;
    }
    
    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "HIREDATE")
    public Date getHiredate() {
        return hiredate;
    }
    
    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }
    
    @Column(name = "SAL")
    public Double getSal() {
        return sal;
    }
    
    public void setSal(Double sal) {
        this.sal = sal;
    }
    
    @Column(name = "COMM")
    public Double getComm() {
        return comm;
    }
    
    public void setComm(Double comm) {
        this.comm = comm;
    }
    
    @Column(name = "DEPTNO")
    public Integer getDeptno() {
        return deptno;
    }
    
    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }
    
}

※注意不要import錯了,都是import javax.persistence開頭的哦!

※@Table的catalog和schema全不打或取其一是ok的,但全打會出「could not load an entity:」的錯

※@Temporal(TemporalType.TIMESTAMP)是預設值可不打,其他還有兩種
TemporalType.DATE和TemporalType.TIME,主要就是取得的日期要不要有「年月日」,或是「時分秒」,或者兩個都要(預設)

※@Column預設是屬性名,而資料庫不分大小寫,如果兩個一樣可不寫

自動生成DDL語句 (Hibernate3.x 五)

※程式設定

//SchemaExport export = new SchemaExport(HibernateUtil.getConfiguration());
SchemaExport export = new SchemaExport(new Configuration().configure());
export.create(true, false);
export.drop(true, true);
    
//SchemaUpdate update = new SchemaUpdate(new Configuration().configure());
//update.execute(true, true);
     
//SchemaValidator validator = new SchemaValidator(new Configuration().configure());
//validator.validate();

※最上面是自己寫的HibernateUtil,第一行是官方寫的

※第一個參數表示要不要在控制台輸出DDL語句

※第二個參數表示要不要真的執行

※生成是根據hibernate.cfg.xml的mapping resource,當然用其他方式也行,反正給一個設定檔就對了

※第二區塊是根據mapping resource的hbm.xml,增加新的欄位,資料庫就會跟著增加
但我試的結果 改名和修改資料型態沒用

※第三區塊是根據mapping resource的hbm.xml,會檢查欄位和資料庫有沒有對應,如不對應就會出錯,如「org.hibernate.HibernateException: Wrong column type in 帳號.EMP for column COMM. Found: float, expected: double precision」,因為資料庫是float,我hbm設double,雖然還是能運行,但如果你要驗證,就是會報錯

※XML設定

<property name="hibernate.hbm2ddl.auto">create</property>

總共有四種值,看這裡的 Table 3.7

※create: 根據mapping resource,刪除表,然後新增表,所以永遠沒資料,類似上面的第一區塊的create方法,但控制台不會輸出DDL語句

※create-drop: 根據mapping resource,刪除表,然後新增表,但session factory關閉後又刪除,所以關閉後,連表都沒了,類似上面的第一區塊的drop方法,但控制台不會輸出DDL語句

※update:類似上面的第二區區,但還要有setter/getter方法,且控制台不會輸出DDL語句,聽說較常用

※validate:等同上面的第三區塊

2015年10月18日 星期日

HibernateUtil (Hibernate3.x 三)

尤於每次使用Hibernate,都要使用以下三行開頭
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
實在太麻煩,而且效能也太差,所以有人寫了一支公用的方法

※自己寫HibernateUtil

public class HibernateUtil {
    // 保存此thread之中所有打開的Session
    private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
    private static SessionFactory sessionFactory;
    private static Configuration config = new Configuration();
    
    static {
        try {
            config.configure();
            sessionFactory = config.buildSessionFactory();
        } catch (Exception e) {
            System.err.println("取得sessionFactory錯誤!");
            e.printStackTrace();
        }
    }
    
    public static Session getSession() throws HibernateException {
        // 從thread池之中取得一個Session,第一次一定沒有
        Session session = (Session) threadLocal.get();
    
        if (session == null || !session.isOpen()) {
            if (sessionFactory == null) {
                rebuildSessionFactory();
            }
            session = (sessionFactory != null) ? sessionFactory.openSession()
                    : null;
            // 將取得的Session保存在thread池之中
            threadLocal.set(session);
        }
        return session;
    }
    
    public static void rebuildSessionFactory() {
        try {
            config.configure();
            sessionFactory = config.buildSessionFactory();
        } catch (Exception e) {
            System.err.println("取得sessionFactory錯誤!");
            e.printStackTrace();
        }
    }
    
    public static void closeSession() throws HibernateException {
        Session session = (Session) threadLocal.get();
        threadLocal.set(null);
        if (session != null) {
            session.close();
        }
    }
    
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
    
    public static Configuration getConfiguration() {
        return config;
    }
}

現在只要這要子用就可以了
Session session = HibernateUtil2.getSession();
Emp emp = (Emp) session.get(Emp.class, 8888);
System.out.println(emp);
session.close();

※官方的HibernateUtil

官方提供的HibernateUtil在這裡,使用時,如下的方式
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
//session.beginTransaction();
Emp emp = (Emp) session.get(Emp.class, 8888);
System.out.println(emp);
session.close();

此時會報「No CurrentSessionContext configured!」,沒有設定CurrentSession
Hibernate本身沒有交易管理功能,它依賴於JDBC(DB只有一個)或JTA(DB有多個)的交易管理功能,所以在hibernate.cfg.xml增加以下兩行
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="hibernate.current_session_context_class">thread</property>

這時又報「get is not valid without active transaction」,沒有啟動transaction,所以把上面程式碼的註解打開就可以了,雖然是查詢,而且我還有設自動commit,但還是要加這行才不會報錯

hibernate.current_session_context_class在這裡有說明,有四個選項

完整的應用程式伺服器,必需包括:連線池、交易管理、安全管理
Tomcat不完整,它只有連線池的功能、交易和安全管理都沒有
它只能設thread而已
而hibernate.transaction.factory_class預設就是JDBC,所以可以不設定

如果是JTA可以將上面的兩行改成這樣
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
<property name="hibernate.current_session_context_class">jta</property>


2015年10月16日 星期五

依PK的CRUD、自動提交、動態模型的CRUD (Hibernate3.x 二)

※依PK的CRUD

SQL腳本
CREATE TABLE DEPT 
   ( DEPTNO NUMBER(10,0) NOT NULL, 
 DNAME VARCHAR2(255 BYTE), 
 LOC VARCHAR2(255 BYTE), 
 PRIMARY KEY ("DEPTNO")
   );
    
Insert into DEPT (DEPTNO,DNAME,LOC) values (70,'DDD','zzz');
Insert into DEPT (DEPTNO,DNAME,LOC) values (100,'eee','jjj');
Insert into DEPT (DEPTNO,DNAME,LOC) values (10,'ACCOUNTING','NEW YORK');
Insert into DEPT (DEPTNO,DNAME,LOC) values (20,'RESEARCH','DALLAS');
Insert into DEPT (DEPTNO,DNAME,LOC) values (30,'SALES','CHICAGO');
Insert into DEPT (DEPTNO,DNAME,LOC) values (40,'OPERATIONS','BOSTON');
Insert into DEPT (DEPTNO,DNAME,LOC) values (90,'bruce','ZH_TW');
Insert into DEPT (DEPTNO,DNAME,LOC) values (80,'bruce','ZH_TW');

pom.xml
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>3.6.10.Final</version>
</dependency>
    
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.19.0-GA</version>
</dependency>

javassist不加的話,會在執行時,出「java.lang.ClassNotFoundException: javassist.util.proxy.MethodFilter」

hibernate.cfg.xml
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@127.0.0.1:1521:orcl</property>
        <property name="hibernate.connection.username">account</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>

        <mapping resource="org/hibernate/vo/Dept.hbm.xml" />
    </session-factory>
</hibernate-configuration>

Dept.hbm.xml
<hibernate-mapping>
    <class name="org.hibernate.vo.Dept" table="DEPT">
        <id name="deptno" type="int">
            <column name="DEPTNO" />
            <generator class="assigned" />
        </id>
        <property name="dname" type="java.lang.String" access="field">
            <column name="DNAME" />
        </property>
        <property name="loc" type="java.lang.String" column name="LOC" />
    </class>
</hibernate-mapping>
※hibernate-mapping有個屬性package,打上package名稱,而class name打class名稱也是可以
※property也可只寫一行,最後一行的property就是這樣


Dept.java
private int deptno;
private String dname;
private String loc;
//setter/getter

TestHibernate.java
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
    
Dept dept = new Dept();
dept.setDeptno(80);
dept.setDname("bruce");
dept.setLoc("ZH_TW");
    
Transaction tx = session.beginTransaction();
try {
    session.save(dept);
    tx.commit();
    System.out.println("成功!");
} catch (Exception e) {
    tx.rollback();
    System.err.println("失敗!");
    e.printStackTrace();
} finally {
    session.close();
    factory.close();
}

專案圖:

TestHibernate.java的session.save(dept);是新增的意思,重要的有六個
※delete:刪除
※save:新增
※update:修改
※saveOrUpdate:新增或修改(資料庫沒有-->新增,有-->修改)
較專業的講法是:如果物件是Transient,就呼叫save(); 如果物件是Detached,則呼叫update()
可參考官方網站良葛格
只是官網只有三種狀態,良葛格的有四種,而且還有圖,不知從哪來的?

※load:查詢
※get:查詢

※get與load的差別

Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
    
System.out.println("0");
//Dept dept = (Dept) session.get(Dept.class, 80);
Dept dept = (Dept) session.load(Dept.class, 80);
System.out.println(dept);

※如果最後一行註解掉,get在控制台還是會有sql語法,而load什麼都沒有,
   因為load()為lazy-initialization,只有真正要用到時,才會真的去做事,
   所以load()效能比get()還要高
※如果沒查到:
   get()是null
   load()會拋org.hibernate.ObjectNotFoundException: No row with the given identifier exists:

※注意事項

Dept dept = (Dept) session.get(Dept.class, 80);
dept.setDname("apple");
Transaction tx = session.beginTransaction();
try {
    //session.save(dept);此行不加也行
    tx.commit();
    System.out.println("成功!");
} catch (Exception e) {
    tx.rollback();
    System.err.println("失敗!");
    e.printStackTrace();
} finally {
    session.close();
    factory.close();
}

先get或load,然後再set也能成功修改,但還是要commit
如上面的程式碼,整個都在Transaction外面,還是可以

※session混合
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
    
Dept dept = (Dept) session.load(Dept.class, 80);
dept.setDname("xxx3");
session.close();
    
Session session2 = factory.openSession();
dept.setLoc("ZH_TW");// 此行對DB無效,因為session已關閉,dept是包在裡面的
// session2.update(dept);// 這行才有用
    
Transaction tx = session2.beginTransaction();
try {
    tx.commit();
    System.out.println("成功!");
} catch (Exception e) {
    tx.rollback();
    System.err.println("失敗!");
    e.printStackTrace();
} finally {
    session2.close();
    factory.close();
}

注意下面的Transaction接收的是session2
session關閉對資料庫沒作用,但java也不會出錯

※資料庫的抓取欄位策略

<hibernate-mapping default-access="field">
    <class name="org.hibernate.vo.Dept" table="DEPT">
        <id name="deptNo" type="int" access="property">
            <column name="DEPTNO" />
            <generator class="assigned" />
        </id>
        <property name="dName" type="java.lang.String" access="property">
            <column name="DNAME" />
        </property>
        <property name="loc" type="java.lang.String" access="property">
            <column name="LOC" />
        </property>
    </class>
</hibernate-mapping>

id和property有個屬性access,有三個可以設定
※property:預設值,vo一定要設setter/getter,不然會報「Could not find a setter for property xxx in class xxx」
※field:hibernate會用反射的技術獲取值
※noop:自定的策略,要implements org.hibernate.property.PropertyAccessor介面
可參考這裡的5.1.4.2

※hibernate-mapping也有default-access可以設定,上面是針對一個field,這邊的設定是針對整個java bean,但如果都設定,以上面的為主

※我設定field後,將setter/getter刪除,欄位還是private
使用get可以抓到值; 使用load,值都是null

※事務自動提交

在hibernate.cfg.xml加上
<property name="hibernate.connection.autocommit">true</property>

TestHibernate.java
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
    
Dept dept = new Dept();
dept.setDeptno(80);
dept.setDname("bruce");
dept.setLoc("zh_TW");
    
session.saveOrUpdate(dept);
session.flush();
// Transaction tx = session.beginTransaction();
try {
    session.saveOrUpdate(dept);
    // tx.commit();
    System.out.println("成功!");
} catch (Exception e) {
    // tx.rollback();
    System.err.println("失敗!");
    e.printStackTrace();
} finally {
    session.close();
    factory.close();
}

主要就是加上session.flush();

※多個執行一起commit

再創一個腳本
CREATE TABLE EMP
   (    EMPNO NUMBER(4,0), 
    ENAME VARCHAR2(10 BYTE), 
    JOB VARCHAR2(9 BYTE), 
    MGR NUMBER(4,0), 
    HIREDATE DATE, 
    SAL NUMBER(7,2), 
    COMM NUMBER(7,2), 
    DEPTNO NUMBER(2,0), 
    CONSTRAINT "PK_EMP" PRIMARY KEY (EMPNO)
   );
    
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (9999,'Bruce','MANAGER',null,to_date('18-10月-15','DD-MON-RR'),5000,null,80);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7369,'SMITH','CLERK',7902,to_date('17-12月-80','DD-MON-RR'),800,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7499,'ALLEN','SALESMAN',7698,to_date('20-2月 -81','DD-MON-RR'),1600,300,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7521,'WARD','SALESMAN',7698,to_date('22-2月 -81','DD-MON-RR'),1250,500,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7566,'JONES','MANAGER',7839,to_date('02-4月 -81','DD-MON-RR'),2975,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7654,'MARTIN','SALESMAN',7698,to_date('28-9月 -81','DD-MON-RR'),1250,1400,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7698,'BLAKE','MANAGER',7839,to_date('01-5月 -81','DD-MON-RR'),2850,null,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7782,'CLARK','MANAGER',7839,to_date('09-6月 -81','DD-MON-RR'),2450,null,10);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7788,'SCOTT','ANALYST',7566,to_date('24-1月 -87','DD-MON-RR'),3000,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7839,'KING','PRESIDENT',null,to_date('17-11月-81','DD-MON-RR'),5000,null,10);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7844,'TURNER','SALESMAN',7698,to_date('08-9月 -81','DD-MON-RR'),1500,0,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7876,'ADAMS','CLERK',7788,to_date('02-4月 -87','DD-MON-RR'),1100,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7900,'JAMES','CLERK',7698,to_date('03-12月-81','DD-MON-RR'),950,null,30);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7902,'FORD','ANALYST',7566,to_date('03-12月-81','DD-MON-RR'),3000,null,20);
Insert into EMP (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) values (7934,'MILLER','CLERK',7782,to_date('23-1月 -82','DD-MON-RR'),1300,null,10);

Emp.java
private int empno;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;
//setter/getter

Emp.hbm.xml
<class name="org.hibernate.vo.Emp" table="Emp">
    <id name="empno" type="int">
        <column name="EMPNO" />
        <generator class="assigned" />
    </id>
    <property name="ename" type="java.lang.String">
        <column name="ENAME" />
    </property>
    <property name="job" type="java.lang.String">
        <column name="JOB" />
    </property>
    <property name="mgr" type="java.lang.Integer">
        <column name="MGR" />
    </property>
    <property name="hiredate" type="java.util.Date">
        <column name="HIREDATE" />
    </property>
    <property name="sal" type="java.lang.Double">
        <column name="SAL" />
    </property>
    <property name="comm" type="java.lang.Double">
        <column name="COMM" />
    </property>
    <property name="deptno" type="java.lang.Integer">
        <column name="DEPTNO" />
    </property>
</class>

hibernate.cfg.xml要多增加一行
<mapping resource="org/hibernate/vo/Emp.hbm.xml" />

TestHibernate.java
public static void main(String[] args) {
    Configuration cfg = new Configuration().configure();
    SessionFactory factory = cfg.buildSessionFactory();
    Session session = factory.openSession();
    
    try {
        session.saveOrUpdate(saveOrUpdateDept());
        session.save(saveEmp());
        session.flush();
        System.out.println("成功!");
    } catch (Exception e) {
        System.err.println("失敗!");
        e.printStackTrace();
    } finally {
        session.close();
        factory.close();
    }
}
    
public static Dept saveOrUpdateDept(){
    Dept dept = new Dept();
    dept.setDeptno(80);
    dept.setDname("bruce");
    dept.setLoc("zh_TW");
    return dept;
}
    
public static Emp saveEmp(){
    Emp emp = new Emp();
    emp.setEmpno(9999);
    emp.setEname("Bruce");
    emp.setJob("MANAGER");
    emp.setHiredate(new Date());
    emp.setSal(5000D);
    emp.setDeptno(80);
    return emp;
}

※如果是PK可以設定int,不是的話就設Integer,因為int不可以是null,如果怕錯就全部設Wrapper類別
※先用get方法測試Emp行不行,如果如上行設成int,查詢時若資料庫對應的欄位有null值,會出「Null value was assigned to a property of primitive type setter of xxx」的錯
※save是新增,所以執行第二次之後,一定會報錯
※讀取到session.flush();因為是同一個session,會一起commit
※如果要一個一個commit,就在下方多增加一行session.flush(),如下:
session.saveOrUpdate(saveOrUpdateDept());
session.flush();
session.save(saveEmp());
session.flush();



※動態模型

就是沒有class和資料庫的表對應,直接取一個entity名稱就搞定了


Dept.hbm.xml

<class entity-name="entityDept" table="DEPT">
    <id name="deptno" type="int">
        <column name="DEPTNO" />
        <generator class="sequence">
            <param name="sequence">DEPT_SEQ</param>
        </generator>
    </id>
    <property name="dname" type="java.lang.String" access="field">
        <column name="DNAME" />
    </property>
    <property name="loc" type="java.lang.String" column="LOC" />
</class>

※這裡的PK用自動的,如果資料庫沒有就下CREATE SEQUENCE DEPT_SEQ吧!

※property的name為必填

新增測試

Session s = HibernateUtil2.getSession();
Transaction tx = s.beginTransaction();
try {
    Map<String, Object> dept = new HashMap<>();
    dept.put("dname", "業務部");
    dept.put("loc", "芝加哥"); 

    s.save("entityDept", dept);
    tx.commit();
} catch (Exception e) {
        tx.rollback();
        System.err.println("例外錯誤!");
        e.printStackTrace();
    } finally {
        if (s.isOpen()) {
            s.close();
        }
    }
}

※map的key是xml的property裡面的name屬性


修改測試

// Map<String, Object> dept = new HashMap<>();
// dept.put("deptno", 41);
// dept.put("dname", "業務部");
// dept.put("loc", "舊金山"); 
// s.update("entityDept", dept);

Map<String, Object> dept = (Map<String, Object>) s.get("entityDept", 41);
dept.put("loc", "舊金山");
s.update("entityDept", dept);
tx.commit();

※註解的部分也可以,但不寫出來就會給null,這張表只有三個欄位,全寫出來也還好,但如果有很多欄位,譬如20個,那實在是太累了,所以可以用get或load取出來,然後再針對想修改的欄位做修改即可


刪除測試

s.delete(s.load("entityDept", 41));
tx.commit();

Callbacks (jQuery26)

官網連結

Callbacks分成五種,也就是$.Callbacks() 裡面可以放參數,
once、memory、unique、stopOnFalse 和預設(沒參數),
參數裡的字不在這四種裡面,如xxx,視同預設

※預設

<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>jQuery.extend demo</title>
        <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
    </head>
    <body>
        <input type="button" value="click me" onclick="test()" />
    </body>
    
    <script>
        function f1(arg) {
            alert("1-" + arg);
            return false; // 除了用「stopOnFalse」(後面會說明),回傳什麼不受影響
        }
        function f2(arg) {
            alert("2-" + arg);
        }
        
        function test(){
            var callbacks = $.Callbacks();
            callbacks.add(f1);
            callbacks.fire("1");

            callbacks.add(f2);
            callbacks.fire("2");
        }
    </script>
</html>

結果:1-1-->1-2-->2-2

順序是紅-->藍-->綠
也就是每次執行fire方法時,會執行上次的add()+這次的fire參數,最後執行自己的

不一定要add方法,才能fire(),可以連用,如下:
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);
callbacks.fire("2");
callbacks.fire("3");
callbacks.fire("4");

結果:1-1-->1-2-->2-2-->    1-3-->2-3-->    1-4-->2-4


順序是圖1的順序,再加上紅-->藍-->綠-->黃

還可以用remove(),將裡面的方法移除,同一個方法也可以繼續add,但只要一remove(),就會就所有同名的方法移除

如下:
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);
callbacks.fire("2");
    
callbacks.add(f1);
callbacks.fire("3");
    
callbacks.remove(f1);
callbacks.fire("4");

結果:1-1-->1-2-->2-2-->    1-3-->2-3-->1-3-->    2-4

順序是圖1的順序,再加上紅-->藍-->綠-->黃

※once

var callbacks = $.Callbacks('once');
      
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);
callbacks.fire("2");
callbacks.fire("3");
     
callbacks.add(f1);
callbacks.fire("3");

xxx如同once這個單字,只會執行第一次add的方法名稱,和fire裡的參數,其他都不會執行,所以是1-1

※memory

var callbacks = $.Callbacks('memory');
    
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);

執行到第一次的fire方法,就會把它記起來,以後只要add方法,就會用記起來的參數自動執行,不用fire,所以是1-1-->2-1
當然要加fire還是可以,假如在最後一行加上callbacks.fire("2");
那結果就是1-1-->2-1-->1-2-->2-2

※unique

var callbacks = $.Callbacks('unique');
    
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);
callbacks.fire("2");
    
callbacks.add(f1);
callbacks.fire("3");

相同方法只會執行一次,也是是等同把第二次出現的callbacks.add(f1)刪除
結果為1-1-->1-2-->2-2-->1-3-->2-3
如果是預設的最後還會多一個1-3

※stopOnFalse

var callbacks = $.Callbacks('stopOnFalse');
    
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);
callbacks.fire("2");
    
callbacks.add(f1);
callbacks.fire("3");

f1方法回傳的是false,此功能就不會執行以後add進去的方法,就好像沒有第6、9行一樣
所以結果為1-1-->1-2-->1-3

※總結以上四個功能,不包括預設

once:只會執行第一次add的方法名稱,和fire裡的參數,其他都不會執行
memory:執行到第一次的fire方法,就會把它記起來,以後只要add方法,就會用記起來的參數自動執行,不用fire
unique:相同方法只會執行一次
stopOnFalse:如果回傳false,就停止執行以後add進去的方法,但此方法還是會做完,不包括重覆的方法

這幾個功能還能連用,用空格隔開,如下:
var callbacks = $.Callbacks('memory once');
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);
callbacks.fire("2");
    
callbacks.add(f1);
callbacks.fire("3");


※順序不會影響結果,所以還有以下10種:
1.once memory:1-1-->2-1-->1-1
※變成add才有用,然後fire只有一次
2.once unique:1-1
3.once stopOnFalse:1-1
4.once memory unique:1-1-->2-1-->1-1
5.once memory stopOnFalse:1-1
6.once memory unique stopOnFalse:1-1

7.memory unique:1-1-->2-1-->1-2-->2-2-->1-3-->2-3
8.memory stopOnFalse:1-1-->1-2-->1-3
9.memory unique stopOnFalse:1-1-->1-2-->1-3

10.unique stopOnFalse:1-1-->1-2-->1-3



※其他方法

※disable()、disabled()

disable後就不能add了
disabled()為判斷有沒有被disable過


var callbacks = $.Callbacks();
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);
    
console.log(callbacks.disabled()); // false
callbacks.disable();
console.log(callbacks.disabled()); // true
    
callbacks.fire("2");
    
callbacks.add(f1);
callbacks.fire("3");

※disable()後就都沒有作用了,所以是1-1

※如果是用memory,結果是1-1-->2-1



※remove(fn)、empty()

remove(fn):此方法之前有add的,會移除add同名的方法
empty():此方法之前有add方法的,全部清空
此兩個方法之後當然還可以再add

var callbacks = $.Callbacks();
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);
callbacks.fire("2");
callbacks.empty();
callbacks.add(f2);
callbacks.fire("3");
    
callbacks.fire("4");

※1-1-->1-2-->2-2-->2-3-->2-4



※fired()

判斷有沒有呼叫過fire方法,就算清空也還是true

var callbacks = $.Callbacks();
callbacks.add(f1);
console.log(callbacks.fired()); // false
callbacks.fire("1");
console.log(callbacks.fired()); // true
callbacks.add(f2);
callbacks.fire("2");
callbacks.empty();
console.log(callbacks.fired()); // true
callbacks.add(f2);
callbacks.fire("3");
callbacks.fire("4");

※false-->1-1-->true-->1-2-->2-2-->true-->2-3-->2-4



※fireWith()

有兩個參數,都是可選的,第一個參數是context,第二個參數是對應方法的參數

var callbacks = $.Callbacks();
callbacks.add(f1);
callbacks.fire("1");
callbacks.fireWith(window, ["w1"]);
callbacks.fireWith(document, ["p1"]);
callbacks.fireWith(["n1"]);
    
callbacks.add(f2);
callbacks.fire("2");
callbacks.fireWith(window, ["w1", "w2"]);
callbacks.fireWith(document, ["p1", "p2"]);
callbacks.fireWith(["n1", "n2"]);

※1-1-->1-w1-->1-p1-->1-undefined-->
1-2-->2-2-->1-w1-->2-w1-->
1-p1-->2-p1-->1-undefined-->2-undefined

※n1、n2,如果改成fireWith(),結果也是一樣



※lock()、locked()

lock():鎖定之後,無法fire
locked():判斷有沒有鎖定

var callbacks = $.Callbacks();
callbacks.add(f1);
callbacks.fire("1");
    
callbacks.add(f2);
console.log(callbacks.locked()); // false
callbacks.lock();
console.log(callbacks.locked()); // true
    
callbacks.fire("2");
callbacks.fire("3");

※1-1-->false-->true

※如果用memory會是1-1-->2-1-->false-->true



※has(fn)

判斷方法有沒有add進去

var callbacks = $.Callbacks();
callbacks.add(f1);
callbacks.fire("1");
    
console.log(callbacks.has(f2));
callbacks.add(f2);
console.log(callbacks.has(f2));

※1-1-->false-->true

2015年10月15日 星期四

javascript 的類別

<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>prototype demo</title>
    </head>
    <body>
        <input type="button" value="click me" onclick="test()" />
    </body>
    
    <script>
        function Chess(name, price){
            this.name = name;
            this.price = price;
            this.setName = function(name){
                this.name = name
            }
            this.getName = function(){
                return this.name;
            }
            this.setPrice = function(price){
                this.price = price
            }
            this.getPrice = function(){
                return this.price;
            }
            
            Chess.prototype.CO_LTD = "娛樂科技股份有限公司";
            Chess.prototype.say = function(){
                return "象棋類別,軍啦!";
            }
        }

        function test(){
            var c = new Chess('象棋', 40);
            alert(c.name)
            alert(c.getName());
            alert(c.price)
            alert(c.getPrice());
            alert(c.say());
            alert(c.CO_LTD);
            alert(Chess.toString());// 回傳Chess類別,為內鍵方法
        }
    </script>
</html>

Chess.prototype也可以寫在外面,如下:
function Chess(name, price){
    this.name = name;
    this.price = price;
    this.setName = function(name){
        this.name = name
    }
    this.getName = function(){
        return this.name;
    }
    this.setPrice = function(price){
        this.price = price
    }
    this.getPrice = function(){
        return this.price;
    }
}

Chess.prototype.CO_LTD = "娛樂科技股份有限公司";
Chess.prototype.say = function(){
    return "象棋類別,軍啦!";
}

差別在Chess.toString()就沒有Chess.prototype的程式碼了
prototype類似java的static,共用同一個空間,但呼叫不能類別.方法或屬性
this.屬性,看起來類似java的public,所以不用寫setter/getter也可以
類別裡一定要用this;呼叫時一定要用new,否則不具意義

公用:$.extend 和$.fn.extend (jQuery27)

官網連結

※$.extend

$(function(){
    var object1 = {
        apple: 0,
        banana: { weight: 52, price: 100 },
        cherry: 97
    };
    
    var object2 = {
        banana: { price: 200 },
        durian: 100
    };
    
    var object3 = {
        banana: {name:'bruce',price: 400},
        apple: 20,
        duck:{name:'quack', attribute:'blue'},
        durian:200
    };
    
    console.log(JSON.stringify(object1));
    console.log(JSON.stringify(object2));
    console.log(JSON.stringify(object3));
    console.log("");console.info("");
    
    $.extend(true, object1, object2, object3);
    // $.extend(object1, object2, object3);
    // var xxx = $.extend({}, object1, object2, object3);
    
    console.log(JSON.stringify(object1));
    console.log(JSON.stringify(object2));
    console.log(JSON.stringify(object3));
});

※$.extend 只會影響第一個物件,然後後面的物件如果 KEY 一樣,就會一直覆蓋下去

※第一個參數可以寫 true 和 {} 兩個,官方說 false 不支援,但第一個參數不是必要的
如果寫{},不會影響原本的 JSON,必需要宣告一個變數來接
如果不寫第一個參數,結果和寫{}一樣,但會影響原本的 JSON,所以不用宣告變數來接
以上兩個 (不寫和{}),一個 KEY是一個整體,是不可拆開的,如果有一樣的 KEY,後面的物件會一直覆蓋

如果寫 true 表示 KEY 不是一個整體,如果有一樣的 KEY,後面的物件會一直附加到後面



※$.fn.extend

可在物件增加方法


$(function(){
    $.fn.extend({
        ooo: function() {
            return this.each(function() {
                this.checked = true;
            });
        },
        xxx: function() {
            return this.each(function() {
                this.checked = false;
            });
        },
        zzz: function() {
            return this.each(function(){
                this.title = 'he he he radio';
            });
        }
    });
    $("input[type='checkbox']").ooo();
    $("input[type='radio']").zzz();
});
----------
<input type="checkbox" value="a1" name="ch" />
<input type="checkbox" value="a2" name="ch" />
<input type="checkbox" value="a3" name="ch" />
<input type="radio" value="r1" name="ra" />
<input type="radio" value="r2" name="ra" />

※將鼠標放在radio會出現title

2015年10月14日 星期三

泛型類別和問號

class Chess<T> {
    private T xxx;
    //setter/getter
}


public class TestGeneric {
    public static void main(String[] args) {
        Chess<Integer> c1 = new Chess<Integer>();
        Chess<String> c2 = new Chess<String>();
        Chess<? extends List<String>> c3 = new Chess<ArrayList<String>>();
        Chess<? extends List<Integer>> c4 = new Chess<LinkedList<Integer>>();
        Chess<? super Integer> c5 = new Chess<Number>();
        c5.setXxx(111);
        System.out.println(c5.getXxx());
        
        Chess c6 = new Chess<>();
        c6.setXxx(123);
        System.out.println(c6.getXxx());
        
        Chess<?> c7 = c6;
        System.out.println(c7.getXxx());
        c7.setXxx(null);
        System.out.println(c7.getXxx());
        //c7.setXxx(456);
    }
}

※Chess類別的T為呼叫者決定什麼型別,就是什麼型別,如c1~c5,如果沒有泛型就要寫很多類似的類別了
※c3、c4只能是List的兒子
※c5只能是Integer的爸爸

※c6和c7的差別在有沒有泛型
  c6(沒有泛型)塞值後給有問號泛型的c7
  c7能印出來,也能把它變null
  但不能塞值,這就是兩者的差別
  如果能塞值也很奇怪,因為宣告問號了,java怎麼知道你要塞什麼值?
  所以直接給編譯錯誤

※問號可以使用強轉,但要轉成正確的型態

※和泛型方法配合使用,泛型方法可以覆蓋泛型類別,但會有警告,叫你用 hiding

※泛型類別不能用在 static 方法