Hibernate問題集錦

首先奉上
Hibernate3.2 API地址:http://docs.jboss.org/hiberna...
Hibernate4.3 API地址:http://docs.jboss.org/hiberna...
Hibernate 4.3文檔:http://hibernate.org/orm/docu...html

問題1、No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here

情景0:請設置OpenSessionInViewFilterjava

情景1:在service外使用了dao.getSession()從而致使問題。mysql

解決思路:
其實個人Service層是加了@Transactional註解的,可是因爲某些緣由,在service外使用了dao.getSession()從而致使該問題。
問題代碼以下:(這段代碼沒有放在被@Transactional註解的Serivce層,從而致使問題)spring

Criteria c=storeDao.getSession().createCriteria(Store.class).add(Restrictions.or(Restrictions.isNull("mainImgJson"),Restrictions.isNull("introImgJson")));
c.createAlias("terrace", "terrace").add(Restrictions.eq("terrace.keyId", "1"));
c.addOrder(Order.desc("updateTime"));
Page<Store> page=storeManager.findPage(c, 1, 100);

解決方法就是在這段代碼調用方法上加上@Transactional註解。
參考:http://stackoverflow.com/ques...
固然此處更好的辦法是採用DetachedCriteriasql

情景2:Service[抽象]父類數據庫方法沒有加@Transactional
假設有如下類:數據庫

@Transactional
public class SubClass extends SuperClass {

    public void loadDb(){
//數據庫操做
    }

}
public class SuperClass {

    public void savedb() {
//數據庫操做
    }
}

savedb是父類的方法,loadDb是子類的方法。若是有如下調用:api

@Test
public void test(){
    SubClass o = new SubClass();
    o.savedb();//將會報沒有Session的錯誤
    o.loadDb();//正常
}

解決方法:在父類中標註@Transactional(父類是抽象類也能夠):數組

@Transactional
public class SuperClass {

    public void savedb() {

    }
}

參考:http://www.cnblogs.com/xiefei...緩存

情景3:其餘場景終極解決方案:session

有多是在非請求線程(請求線程有openssessioninviewfilter把控)某些查詢延遲加載致使的.
對於這種情形,
第一:你能夠設置lazy=false,不過不少狀況下不現實;
第二:對於須要後續的數據,查詢出來以後當即get(),或者size(),這樣就能觸發查詢。還可使用Hibernate.initialize方法

在使用hibernate進行持久化時,有時須要動態的改變對象的加載,好比在編輯頁面裏面lazy=true,而在瀏覽頁面lazy=false,這樣能夠在須要lazy的地方纔進行控制。而配置文件中Lazy屬性是全局控制的,如何處理呢?
  當元素或者元素的lazy屬性爲true時,load() or get() or
find()加載這些對象時,Hibernate不會立刻產生任何select語句,只是產生一個Obj代理類實例,只有在session沒有關閉的狀況下運行Obj.getXxx()時纔會執行select語句從數據庫加載對象,若是沒有運行任何Obj.getXxx()方法,而session已經關閉,Obj已成遊離狀態,此時再運行Obj.getXxx()方法,Hibernate就會拋出"Could
not initialize proxy - the owning Session was
closeed"的異常,是說Obj代理類實例沒法被初始化。然而想在Session關閉以前不調用Obj.getXxx()方法而關閉Session以後又要用,此時只要在Session關閉以前調用Hibernate.initialize(Obj)或者Hibernate.initialize(Obj.getXxx())便可,net.sf.hibernate.Hibernate類的initialize()靜態方法用於在Session範圍內顯示初始化代理類實例。
  在配置文件裏面能夠用lazy=true,在程序裏面能夠用強制加載的方法Hibernate.initialize(Object
proxy) 方法強制加載這樣就至關於動態改變爲lazy=false。
  但在使用時須要注意的一點是:其中的proxy是持久對象的關聯對象屬性,好比A實體,你要把A的關聯實體B也檢出,則要寫Hibernate.initialize(a.b)。

Hibernate 4.2以後,你還能夠配置hibernate.enable_lazy_load_no_trans:
hibernate.xml:
<property name="hibernate.enable_lazy_load_no_trans">true</property>

Sinche Hibernate 4.2 you can use
.setProperty("hibernate.enable_lazy_load_no_trans", "true"); this
option solves the dreaded org.hibernate.LazyInitializationException:
could not initialize proxy - no Session which took so many hours of
life to programmers away.

具體能夠看https://docs.jboss.org/hibern...

對於使用HQL的童鞋,能夠顯式join fetch

Query query = session.createQuery(
    "from Model m " +
    "join fetch m.modelType " +
    "where modelGroup.id = :modelGroupId"
);

第三:查詢時動態設置當即join,好比

crit.setFetchMode("pays", FetchMode.JOIN);
crit.setFetchMode("ville", FetchMode.JOIN);
crit.setFetchMode("distination", FetchMode.JOIN);

第三部分總結:

0、在場景爲Request請求線程時,配置Open Session in View 具體原理,請看https://vladmihalcea.com/2016...
一、使用HQL時能夠join fetch (推薦);或者你也能夠用uniqueResult()方法,該方法會當即查出關聯對象。
二、使用criteria時能夠FetchMode.JOIN
三、顯式調用Hibernate.initialize(obj),Hibernate.initialize(obj.getXXX())
四、配置hibernate.enable_lazy_load_no_trans:(使用時請注意開銷)

hibernate.xml:
<property name="hibernate.enable_lazy_load_no_trans">true</property>
 persistence.xml:
<property name="hibernate.enable_lazy_load_no_trans" value="true"/>

解釋請參考:https://vladmihalcea.com/2016...
可是,使用hibernate.enable_lazy_load_no_trans配置時,你須要注意下面這一點:

Behind the scenes, a temporary Session is opened just for initializing
every post association. Every temporary Session implies acquiring a
new database connection, as well as a new database transaction.

The more association being loaded lazily, the more additional
connections are going to be requested which puts pressure on the
underlying connection pool. Each association being loaded in a new
transaction, the transaction log is forced to flush after each
association initialization

翻譯一下就是:這種情形下,每次初始化一個實體的關聯就會建立一個臨時的session來加載,每一個臨時的session都會獲取一個臨時的數據庫鏈接,開啓一個新的事物。這就致使對底層鏈接池壓力很大,並且事物日誌也會被每次flush.
設想一下:假如咱們查詢了一個分頁list每次查出1000條,這個實體有三個lazy關聯對象,那麼,恭喜你,你至少須要建立3000個臨時session+connection+transaction.

五、使用 DTO projection (推薦):解釋請參考https://vladmihalcea.com/2016...
其實就是定義一個單獨的DTO來保存查詢結果。

public class PostCommentDTO {
    private final Long id;
 
    private final String review;
 
    private final String title;
 
    public PostCommentDTO(
        Long id, String review, String title) {
        this.id = id;
        this.review = review;
        this.title = title;
    }
 
    public Long getId() {
        return id;
    }
 
    public String getReview() {
        return review;
    }
 
    public String getTitle() {
        return title;
    }
}
List<PostCommentDTO> comments = doInJPA(entityManager -> {
    return entityManager.createQuery(
        "select new " +
        "   com.vladmihalcea.book.hpjp.hibernate.fetching.PostCommentDTO(" +
        "       pc.id, pc.review, p.title" +
        "   ) " +
        "from PostComment pc " +
        "join pc.post p " +
        "where pc.review = :review", PostCommentDTO.class)
    .setParameter("review", review)
    .getResultList();
});
 
for(PostCommentDTO comment : comments) {
    LOGGER.info("The post title is '{}'", comment.getTitle());
}

參考:http://stackoverflow.com/ques...

第四:若是因爲某些要求,你不能按以上操做進行:那麼請看終極解決方案:

/**
 * 因爲在http請求線程以外,openssessioninviewfilter不起做用,致使線程沒有綁定session,這個類就是爲了解決此問題。
 * 本身管理Hibernate Session的Runnable。
 * @author taojw
 */
public abstract class TxSessionRunnable implements Runnable {
    @Override
    public final void run(){
        SessionFactory sessionFactory = (SessionFactory)SpringContextHolder.getApplicationContext().getBean("sessionFactory");  
        boolean participate = bindHibernateSessionToThread(sessionFactory); 
        try{
            execute();
        }finally{
            closeHibernateSessionFromThread(participate, sessionFactory);
        }
    }
    public void execute(){
        
    }
    public static boolean bindHibernateSessionToThread(SessionFactory sessionFactory) {  
        if (TransactionSynchronizationManager.hasResource(sessionFactory)) {  
            // Do not modify the Session: just set the participate flag.  
            return true;  
        } else {  
            Session session = sessionFactory.openSession();  
            session.setFlushMode(FlushMode.MANUAL);  
            SessionHolder sessionHolder = new SessionHolder(session);  
            TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);  
        }  
        return false;  
    }  
    public static void closeHibernateSessionFromThread(boolean participate, Object sessionFactory) {  
        if (!participate) {  
            SessionHolder sessionHolder = (SessionHolder)TransactionSynchronizationManager.unbindResource(sessionFactory);  
            SessionFactoryUtils.closeSession(sessionHolder.getSession());  
        }  
    }

}

參考
http://stackoverflow.com/ques...
http://blog.csdn.net/zhengwei...
http://stackoverflow.com/ques...

no session問題補充:延遲加載時,Hibernate initialize object in another session ?

能夠不能夠先獲取一個延遲加載的對象,不當即調用Hibernate.initialize,而關閉session,而後在須要的時候,打開另一個session,調用Hibernate.initialize來初始化延遲的對象呢?
答案是否認的。不能夠!
參考:https://stackoverflow.com/que...
Entity objects are pretty much "bound" to original session where they were fetched and queries like this cannot be performed in this way. One solution is to bound entity to new session calling session.update(entity) and than this new session knows about entity. Basically new query is issued to populate entity fields again.
So avoid this if not necessary and try to fetch all needed data in original session
或者你也能夠經過獲取主鍵去再次主動查詢關聯對象

問題2、No row with the given identifier exists

產生此問題的緣由:
有兩張表,table1和table2.產生此問題的緣由就是table1裏作了關聯<one-to-one>或者<many-to-one unique="true">(特殊的多對一映射,實際就是一對一)來關聯table2.當hibernate查找的時候,table2裏的數據沒有與table1相匹配的,這樣就會報No row with the given identifier exists這個錯.
註解配置解決方法:
使用hibernate 註解配置實體類的關聯關係,在many-to-one,one-to-one關聯中,一邊引用自另外一邊的屬性,若是屬性值爲某某的數據在數據庫不存在了,hibernate默認會拋出異常。解決此問題,加上以下註解就能夠了:
@NotFound(action=NotFoundAction.IGNORE),意思是找不到引用的外鍵數據時忽略,NotFound默認是exception

@ManyToOne(fetch = FetchType.LAZY)    
    @JoinColumn(name = "ICT_BASE_ID", referencedColumnName = "ID", unique = false, nullable = false, insertable = false, updatable = false)    
    @NotFound(action=NotFoundAction.IGNORE)    
    public IctBase getIctBase() {    
        return ictBase;    
    }

參考:http://blog.csdn.net/h3960710...
http://www.cnblogs.com/rixian...

問題3、How to autogenerate created or modified timestamp field?

Hibernate4.3以後,咱們能夠直接使用JPA註解:"@CreationTimestamp" and "@UpdateTimestamp"

protected Date insertTime;
    protected Date updateTime;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(updatable = false)  
    @CreationTimestamp
    public Date getInsertTime() {
        return insertTime;
    }
    public void setInsertTime(Date insertTime) {
        this.insertTime = insertTime;
    }
    @UpdateTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    public Date getUpdateTime() {
        return updateTime;
    }
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

若是使用以前版本,那麼你能夠給dao封裝一個save方法,並規定inserttime,updatetime字段名。統一處理便可。

參考:http://stackoverflow.com/ques...
http://stackoverflow.com/ques...

問題4、hibernate的速度問題之fetch_size和batch_size

hibernate的速度性能並不差,固然了這和應用的數據庫有關,在Oracle上,hibernate支持 hibernate.jdbc.fetch_size和 hibernate.jdbc.batch_size,而MySQL卻不支持,而我原來的項目絕大多數都是使用MySQL的,因此以爲速度慢,其實在企業級應用,尤爲是金融系統大型應用上,使用Oracle比較多,相對來講,hibernate會提高系統不少性能的。
hibernate.jdbc.fetch_size 50 //讀
hibernate.jdbc.batch_size 30 //寫
hiberante.cfg.xml(Oracle ,sql server 支持,mysql不支持)

<property name="hibernate.jdbc.fetch_size">50</property>
<property name="hibernate.jdbc.batch_size">30</property>

這兩個選項很是很是很是重要!!!將嚴重影響Hibernate的CRUD性能!
Fetch Size 是設定JDBC的Statement讀取數據的時候每次從數據庫中取出的記錄條數。
例如一次查詢1萬條記錄,對於Oracle的JDBC驅動來講,是不會1次性把1萬條取出來的,而只會取出Fetch Size條數,當紀錄集遍歷完了這些記錄之後,再去數據庫取Fetch Size條數據。
所以大大節省了無謂的內存消耗。固然Fetch Size設的越大,讀數據庫的次數越少,速度越快;Fetch Size越小,讀數據庫的次數越多,速度越慢。 但內存消耗正好相反
建議使用Oracle的必定要將Fetch Size設到50
不過並非全部的數據庫都支持Fetch Size特性,例如MySQL就不支持

Batch Size是設定對數據庫進行批量刪除,批量更新和批量插入的時候的批次大小,有點至關於設置Buffer緩衝區大小的意思。
Batch Size越大,批量操做的向數據庫發送sql的次數越少,速度就越快。!

參考:http://blog.csdn.net/rick_123...

問題5、@UniqueConstraint and @Column(unique = true)的區別

好比

@Table(
   name = "product_serial_group_mask", 
   uniqueConstraints = {@UniqueConstraint(columnNames = {"mask", "group"})}
)

And

@Column(unique = true)
@ManyToOne(optional = false, fetch = FetchType.EAGER)
private ProductSerialMask mask;

@Column(unique = true)
@ManyToOne(optional = false, fetch = FetchType.EAGER)
private Group group;

區別在哪裏?

前者是聯合約束、後者分別是獨立約束。

參考:http://stackoverflow.com/ques...

問題6、HQL如何進行多對多查詢

好比多對多時,Book有個Set<Author>類型屬性,Author有個Set<Book>的屬性。其實你不用管authors是Set類型。查詢照樣寫就行,以下:

select a.firstName, a.lastName from Book b join b.authors a where b.id = :id

參考:http://stackoverflow.com/ques...
http://www.cnblogs.com/kingxi...

問題7、Hibernate中關於多表鏈接查詢hql 和 sql 返回值集合中對象問題

錯誤的查詢語句:

String sql = "select a.* from tb_doc_catalog a where a.cat_code like '"+catCode+"%'";
Session session = this.getSession();
try {
List catNameList = session.createSQLQuery(sql).list();
return catNameList ;
} finally {
releaseSession(session); //釋放session
}

分析:原來是查詢出來的字段並不能自動轉換爲bean對象

解決思路一(採用hql查詢):

String sql = "select a from DocCatalogInfo a where a.catCode like '"+catCode+"%'";
List catNameList =getHibernateTemplate().find(sql);
return catNameList ;

ok,測試一下沒問題。

解決思路二(採用原生sql查詢):

String sql = "select a.* from tb_doc_catalog a where a.cat_code like '"+catCode+"%'";
Session session = this.getSession();
try {
List catNameList = session.createSQLQuery(sql).addEntity(DocCatalogInfo.class).list();
return catNameList ;
} finally {
releaseSession(session); //釋放session
}

ok。

hibernate 中createQuery與createSQLQuery二者區別是:
前者用的hql語句進行查詢,後者能夠用sql語句查詢
前者以hibernate生成的Bean爲對象裝入list返回 ,後者則是以對象數組進行存儲

其實,createSQLQuery有一個addEntity方法能夠直接轉換對象

Query query = session.createSQLQuery(sql).addEntity(XXXXXXX.class);

對於鏈接了多個表的查詢,可能在多個表中出現一樣名字的字段。下面的方法就能夠避免字段名重複的問題:

List cats = sess.createSQLQuery( " select {cat.*} from cats cat " ).addEntity( " cat " , Cat. class ).list();

addEntity()方法將SQL表的別名和實體類聯繫起來,而且肯定查詢結果集的形態。
addJoin()方法能夠被用於載入其餘的實體和集合的關聯.

List cats = sess.createSQLQuery(
" select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id " )
.addEntity( " cat " , Cat. class )
.addJoin( " kitten " , " cat.kittens " )
.list();

原生的SQL查詢可能返回一個簡單的標量值或者一個標量和實體的結合體。

Double max = (Double) sess.createSQLQuery( " select max(cat.weight) as maxWeight from cats cat " )
.addScalar( " maxWeight " , Hibernate.DOUBLE);
.uniqueResult();

命名SQL查詢
@NamedQuery("persons")

List people = sess.getNamedQuery( "persons" ).setString( " namePattern " , namePattern)
.setMaxResults( 50 )
.list();

返回一個Map對象,代碼以下

Query query = session.createSQLQuery("select id,name from Tree t where pid in (select id from Tree) ").setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); //返回一個map,KEY:爲DB中名稱一致(大小寫一致)遍歷list時就能夠
Map map = (Map)list.get[i];

map.get("id");map.get("name");來取值。按你的SQL語句select後的字段名來做爲map的Key,但這個key必須與數據庫中的字段名如出一轍。

參考:http://blog.csdn.net/stonejon...

問題8、hibernate:java.math.BigInteger cannot be cast to java.lang.Long

Query query1 = session.createSQLQuery("select count(*) from user u where u.name like ? ").setParameter(0, "%lis%") ;

Query query2 = session.createQuery("select count(*) from User ") ;

Sysstem.out.println((Long) query1.list().get(0)) ; //wrong
Sysstem.out.println((Long) query2.list().get(0)) ; //right

緣由:原生的SQL語句中count()返回值爲BigInteger,而HQL語句中的count()返回值位Long型的,網上說的主要是兼容JPA而作的。
當使用createSQLQuery時,其返回值爲BigInteger類型;
當使用createQuery時,其返回值位Long型的。
解決:

public long getMusicCount(long id){
        String sql="select count(1) from music_singer_music where mid=?";
        Object obj=dao.getSession().createSQLQuery(sql).setParameter(0, id).uniqueResult();
        if(obj instanceof Long){
            return (Long)obj;
        }else if(obj instanceof BigInteger){
            return ((BigInteger)obj).longValue();
        }
        return 0;
    }

參考:http://blog.csdn.net/u0137625...

Hibernate陷阱之Session清空緩存時機

  1. 清空緩存

    當調用session.evict(customer); 或者session.clear(); 或者session.close()方法時,Session的緩存被清空。
  2. 清理緩存

    Session具備一個緩存,位於緩存中的對象處於持久化狀態,它和數據庫中的相關記錄對應,Session可以在某些時間點,按照緩存中持久化對象的屬性變化來同步更新數據庫,這一過程被稱爲清理緩存。

    在默認狀況下,Session會在下面的時間點清理緩存。

  • 當應用程序調用org.hibernate.Transaction的commit()方法的時候,commit()方法先清理緩存,而後在向數據庫提交事務;

  • 當應用程序調用Session的list()或者iterate()時(【注】get()和load()方法不行),若是緩存中持久化對象的屬性發生了變化,就會先清理緩存,以保證查詢結果能能反映持久化對象的最新狀態;

  • 當應用程序顯式調用Session的flush()方法的時候。

上面第二點解釋了爲何在list()查詢是有個時候會出現update語句。

http://blog.csdn.net/xwz0528/...

hibernate中getCurrentSession()和openSession()區別

1 getCurrentSession建立的session會和綁定到當前線程,而openSession每次建立新的session。
2 getCurrentSession建立的線程會在事務回滾或事物提交後自動關閉,而openSession必須手動關閉
在一個應用程序中,若是DAO 層使用Spring 的hibernate 模板,經過Spring 來控制session 的生命週期,則首選getCurrentSession()。
在 SessionFactory 啓動的時候, Hibernate 會根據配置建立相應的 CurrentSessionContext ,在 getCurrentSession() 被調用的時候,實際被執行的方法是 CurrentSessionContext.currentSession() 。

Hibernate4以後與Spring結合需配置爲

<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop>

在 getCurrentSession() 被調用的時候,實際被執行的方法是 SpringSessionContext.currentSession()

實體對象爲何須要覆蓋equals與hashCode

讓我設想如下情景:

Session s1=sessionFactory.openSession();
s1.beginTransaction();
Object a=s1.get(Item.class,new Long(123));
Object b=s1.get(Item.class,new Long(123));

// a==b爲true,a、b指向相同的內存地址。

s1.getTransaction.commit();
s1.close();

Session s2=sessionFactory.openSession();
s2.beginTransaction();
Object c=s2.get(Item.class,new Long(123));

//a==c返回false,a,c指向不一樣的內存地址

s2.getTransaction.commit();
s2.close();

/*如今a,b,c都處於脫管狀態/
Set set=new HashSet();
set.add(a);
set.add(b);
set.add(c);

/*輸出什麼呢?1?,2?,3?/
System.out.println(set.size())
咱們知道set是經過調用集合元素的equals方法來確保元素惟一性的。而Object的equals方法默認就是經過obj1==obj2來經過內存地址判斷同一性。
那咱們如今知道了把。上述會輸出2。可是咱們知道這兩個元素都是表明數據褲中的同一行。因此這個時候咱們就須要覆蓋equals與hashCode方法了。建議咱們對全部的實體類都覆蓋這兩個方法,由於咱們沒法保證這種狀況不會發生。

覆蓋實體類的equals方法能夠選擇兩種方式:

  • 一、經過判斷業務鍵而非數據庫主鍵的相等性。若是業務鍵是惟一的話,推薦此方式。

  • 二、經過判斷對象的全部屬性的相等性。

相關文章
相關標籤/搜索