首先奉上
Hibernate3.2 API地址:http://docs.jboss.org/hiberna...
Hibernate4.3 API地址:http://docs.jboss.org/hiberna...
Hibernate 4.3文档:http://hibernate.org/orm/docu...
问题一、No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
情景0:请设置OpenSessionInViewFilter
情景1:在service外使用了dao.getSession()从而导致问题。
解决思路:
其实我的Service层是加了@Transactional注解的,但是由于某些原因,在service外使用了dao.getSession()从而导致该问题。
问题代码如下:(这段代码没有放在被@Transactional注解的Serivce层,从而导致问题)
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 page=storeManager.findPage(c, 1, 100);
解决方法就是在这段代码调用方法上加上@Transactional注解。
参考:http://stackoverflow.com/ques...
当然此处更好的办法是采用DetachedCriteria
情景2:Service[抽象]父类数据库方法没有加@Transactional
假设有以下类:
@Transactional
public class SubClass extends SuperClass {
public void loadDb(){
//数据库操作
}
}
public class SuperClass {
public void savedb() {
//数据库操作
}
}
savedb是父类的方法,loadDb是子类的方法。如果有以下调用:
@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:其他场景终极解决方案:
有可能是在非请求线程(请求线程有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:
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...
1、使用HQL时可以join fetch (推荐);或者你也可以用uniqueResult()方法,该方法会立即查出关联对象。
2、使用criteria时可以FetchMode.JOIN
3、显式调用Hibernate.initialize(obj),Hibernate.initialize(obj.getXXX())
4、配置hibernate.enable_lazy_load_no_trans:(使用时请注意开销)
hibernate.xml:
true
persistence.xml:
解释请参考: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.
5、使用 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 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
或者你也可以通过获取主键去再次主动查询关联对象
问题二、No row with the given identifier exists
产生此问题的原因:
有两张表,table1和table2.产生此问题的原因就是table1里做了关联
注解配置解决方法:
使用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...
问题三、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...
问题四、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不支持)
50
30
这两个选项非常非常非常重要!!!将严重影响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...
问题五、@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...
问题六、HQL如何进行多对多查询
比如多对多时,Book有个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...
问题七、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...
问题八、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清空缓存时机
-
清空缓存
当调用session.evict(customer); 或者session.clear(); 或者session.close()方法时,Session的缓存被清空。
-
清理缓存
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结合需配置为
org.springframework.orm.hibernate4.SpringSessionContext
在 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方法可以选择两种方式:
1、通过判断业务键而非数据库主键的相等性。如果业务键是唯一的话,推荐此方式。
2、通过判断对象的所有属性的相等性。