最近比较忙,还要复习考研的内容。但突然发现blog的订阅数猛减,于是百忙之中抽空写了一篇关于前几天使用Hibernate时遇到到的问题,希望大家能对我继续的支持。
不要把HQL看的太神,第一眼看到HQL的in语句发现很强大。想当年直接写SQL时,每次都为in后面的参数感到麻烦。后来在网上看了很多文章后发现in的效率很低下,就渐渐避免用in来写SQL,后面参数少的就用=,<>来代替。
网上很多人鼓吹说HQL 很强大,我也就没关心HQL的实现。其实还是有很多问题的。
public void testSelect() { String hql1 = "select invoice.id from Invoice invoice"; String hql2 = " from Invoice invoice where invoice.id in (:mytest)"; try { Query q1 = session.createQuery(hql1); q1.setMaxResults(1000); List test = q1.list(); Query q2 = session.createQuery(hql2); q2.setParameterList("mytest", test); Invoice invoice = (Invoice) q2.list().get(0); Assert.assertEquals(invoice.getStaff(),"70007039"); } catch (HibernateException e) { e.printStackTrace(); Assert.fail(e.getMessage()); } }
此处MaxResult等于2000以下没有什么问题,这里如果设成3000就挂了,这是什么原因呢,其实HQL实现in时最终还是通过拼接SQL,也没做过拆分SQL的操作(这里我高估它了),最终导致SQL过长,数据库不接受。那怎么办呢,我网上寻找了好久,想找到了一个好的解决方案。最终发现只能把查询List中的内容先插入数据库中,然后通过子查询的方式。这样可以保证List内容再长这查询也不会失败。我再拿size为2000的list做了实验发现,这种插入的方法虽然执行了2001条的SQL,竟然效率比q2.setParameterList("mytest", test)这种方式效率更高。解决方案代码如下:
public void testMySelect() { long begin = System.currentTimeMillis(); Transaction tran = null; Query query = null; Test test = null; tran = session.beginTransaction(); for (int i = 0; i < 2; i++) { String hql = "select invoice.id from Invoice invoice"; query = session.createQuery(hql); query.setMaxResults(3000); List result = query.list(); for (Iterator it = result.iterator(); it.hasNext();) { test = new Test(); test.setId((Integer) it.next()); session.save(test); session.flush(); session.clear(); } String hql2 = " from Invoice invoice where invoice.id in (select test.id from Test test)"; query = session.createQuery(hql2); List aaa = query.list(); // // for (Iterator it = result.iterator(); it.hasNext();) { // test = new Test(); // test.setId((Integer) it.next()); // session.delete(test); // } String hql1 = " delete Test"; query = session.createQuery(hql1); query.executeUpdate(); } tran.commit(); long end = System.currentTimeMillis(); System.out.println(end - begin); }
数据库表配置如下
Table invoice(这是我以前项目中的表,由于数据比较多就用它了,呵呵)
<?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> <class name="cn.fql.hibernate.Invoice" table="INVOICE"> <id name="id" type="java.lang.Integer" column="id" unsaved-value="0"> <generator class="identity"/> </id> <property name="staff" type="java.lang.String" column="staff" length="8" not-null="false" /> <property name="activity" type="java.lang.Integer" column="activity" not-null="false"/> </class> </hibernate-mapping>
Table Test(用于缓存数据)
<?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> <class name="cn.fql.hibernate.Test" table="Test"> <id name="id" type="java.lang.Integer" column="id"> <generator class="assigned"/> </id> </class> </hibernate-mapping>
这里再说明下由于这里我只是做下测试也没考虑数据库德设计,就使用了业务主键(呵呵,id不一定是逻辑主键,我做这个实验时把它暂时当成的是业务主键)这是你使用session.delete(test)来删除是没问题的。但如果你用String hql1 = " delete Test";,那你必须在前面的查询后session.flush();session.clear();不然你第二次做测试时就会遇到问题。这是由于主键一样,第一次插入后,这个Test对象处于游离态,你一定要清除掉这个游离态对象不然不能插入。
In虽然效率不是很高,但由于业务的复杂不得不使用,还好客户对那块的效率要求不是很高。