HIbernate中的HQL查询
Hibernate中的查询方式:
1,使用HQL:使用hibernate提供的面向对象的查询语句;
2,使用SQL:在hibernate中允许使用原生的SQL直接查询;
3,使用Criteria:hibernate提供的完全的面向对象的查询方式;
1,HQL:
HQL的学习方法HQL是面向对象的,但是HQL借鉴了SQL的语法结构,把SQL中关系模型的概念替换成面向对象的概念;
//HQL是模型对象的,但是HQL借鉴了SQL的语法结构,把SQL中的关系模型的概念替换为面向对象的概念 String hql = "SELECT e FROM Employee e WHERE e.name LIKE ? AND e.id BETWEEN ? AND ? "; List<Employee> ems = session.createQuery(hql) .setParameter(0, "%a%") .setParameter(1, 1L) .setParameter(2, 10L).list(); System.out.println(ems);
2,SQL:使用session.createQuery来创建基于SQL的查询,
查询出来的结果是Object[]的集合;
//使用SQL查询到的结果集是Object[] 的列表(list) String sql = "select * from employee where name like ? and id between ? and ?"; List<Object[]> ret = session.createSQLQuery(sql) .setParameter(0, "%a%") .setParameter(1, 1L) .setParameter(2, 10L).list(); for (int i = 0; i < ret.size(); i++) { System.out.println(Arrays.toString(ret.get(i))); }
3,Criteria:完全的面向对象的查询,所有的查询及条件的拼装都是通过Criteria对象的方法完成的(使用较少);
//select * from employee //List<Employee> ret = session.createCriteria(Employee.class).list(); List<Employee> ret = session.createCriteria(Employee.class) .add(Restrictions.like("name", "a",MatchMode.ANYWHERE)) .add(Restrictions.between("id", 1L, 10L)).list(); System.out.println(ret);
选择:
1,HQL:面向对象的查询,查询出来的实体都是持久化的,hibernate为HQL做了很多的查询相关的优化,一般来说,对于简单的查询,我们都可以使用HQL(我们都是学过SQL的人)
2,SQL:对于性能要求较高的查询,我们一般直接使用SQL来完成查询;
3,Criteria:完全面向对象的,学习非常简单,对于某些简单的查询,可以直接使用Criteria,对于稍微复杂一点的查询,Criteria完全没有办法处理
分页查询:
1,分页需要些什么东西?总条数,每一页需要多少条数据,当前是第几页,当前页的数据;
2,查询当前页的数据,对于mysql来说,LIMIT ?,?
3,使用query.setFirstResult()方法来设置从第几条数据开始查询;
4,使用query.setMaxResult()方法来设置查询多少条数据;
5,setFristResult和setMaxResult对于SQL和Criteria的查询都有效;
String hql = "select e from Employee e where e.name like ? and e.id between ? and ?"; List<Employee> ems = session.createQuery(hql).setParameter(0, "%a%").setParameter(1, 1L).setParameter(2, 10L) .setFirstResult((currentPage -1)*pageSize)//setFirstResult==limit的第一个参数,代表从第几条数据开始查询 .setMaxResults(pageSize)//setMaxResults==limit的第二个参数,代表最大查询多少条数据 .list(); System.out.println(ems);
查询总条数:
但是使用这种方式非常的不方便,因为我们知道我们查询出来的结果就只有一行数据;
String hql = "select count(e) from Employee e"; //在count中写e比写e.id要好,因为hibernate可以自动根据映射文件找到Employee的主键列,并使用主键列来替换count的内容 List<Long> count = session.createQuery(hql).list(); System.out.println(count.get(0));
使用query.uniqueResult()方法;
//使用uniqueResult,这个方法可以真正的去执行查询 //注意,这个方法只能用在我们确定结果集只有一行数据的时候,如果查询结果多于一行,则报错 //uniqueResult方法对HQL和Criteria都有效 Long count = (Long) session.createQuery("select count(e) from Employee e").uniqueResult(); System.out.println(count);
查询参数设置:
位置占位符:就是使用?号来代表查询参数,通过setParameter(index,object)来根据?的位置来设置参数的;
1,写HQL的时候很方便;
2,如果参数值不多,还是比较容易识别;
3,如果参数值过多,会造成索引不容易识别;如果调整参数位置,所有的设置参数的位置都要变;如果一个参数在多个条件中使用,必须重复设置;
名称占位符:
1,使用 :参数名称 格式来添加名称占位符;
String hql = "select e from Employee e where e.name like :name and e.id between :low and :hi"; List<Employee> ret = session.createQuery(hql) .setParameter("name", "%a%") .setParameter("low", 1L) .setParameter("hi", 10L).list(); System.out.println(ret);
2,使用setParamter(String name,object)这个方法为名称占位符添加参数;
3,可以为多个参数起相同名字的名称占位符,在设置参数的时候只需要设置一次值,就可以在所有的位置设置好参数;
4,使用名称占位符可以给参数传列表值进去,很容易的完成in等查询;但是使用位置占位不行(只能直接拼在HQL里面);
String hql = "select e from Employee e where e.id in (:ids)"; List<Employee> ret = session.createQuery(hql).setParameterList("ids", new Long[]{1L, 2L,3L}).list(); System.out.println(ret);
3,可以通过setEntity方法直接给HQL设置一个实体对象的参数,hibernate会自动的根据实体的关系,创建好对应的SQL
Department dept = new Department(); dept.setId(1L); //在hibernate中可以直接给查询语句的参数设置一个实体对象(可以使用位置占位符或者名称占位符) String hql = "select e from Employee e where e.dept = ?"; List<Employee> ret = session.createQuery(hql).setEntity(0, dept).list(); System.out.println(ret);
查询结果:
1,查询一个实体对象;
直接查询实体对象返回的是实体对象的列表;注意,这个列表中所有的对象都是持久化对象,所以如果查询的数据量过大,记得分页+及时清空一级缓存;
2,投影查询;
1,查询一个简单属性;
//查询一个简单属性,返回该属性类型的list集合 List<String> ret = session.createQuery("select e.name from Employee e").list(); System.out.println(ret); //如果查询的属性是一个实体对象,返回这个实体对象的列表 //这个列表里的所有对象也都是持久化对象 //使用属性的导航查询(e.dept),此处使用join来连接查询 List<Department> dept = session.createQuery("select e.dept from Employee e").list(); System.out.println(dept); //查询多个简单属性,返回Object[]类型的list集合 List<Object[]> ret = session.createQuery("select e.name,e.salary from Employee e").list(); for (int i = 0; i < ret.size(); i++) { System.out.println(Arrays.toString(ret.get(i))); } //查询多个简单属性并且其中包含实体对象属性,返回Object[]类型的list集 //查询出来的实体对象都是持久化的对象 List<Object[]> ret = session.createQuery("select e.name,e.salary,e.dept from Employee e").list(); for (int i = 0; i < ret.size(); i++) { System.out.println(Arrays.toString(ret.get(i))); }
hibernate查询结果的封装:
员工的id,员工的工资,员工的姓名,员工对应部门的编号和部门名称,员工所在的城市
//返回一个Object[]类型的list集合(1) String hql = "select e.id,e.name,e.salary,e.dept.name,e.dept.address.city,e.dept.sn from Employee e"; List<Object[]> ret = session.createQuery(hql).list(); for (Object[] os : ret) { System.out.println(Arrays.toString(os)); } //使用new list把每一行数据包装成list对象(2) String hql = "select new list(e.id,e.name,e.salary,e.dept.name,e.dept.address.city,e.dept.sn) from Employee e"; List<List<Object>> ret = session.createQuery(hql).list(); for (List<Object> os : ret){ System.out.println(os); } //使用new map 把每一行数据包装成map对象(3) //1.默认情况下,把查询的属性的位置作为map的key //2.可以给查询的属性添加别名,别名作为map的key,查询结果作为map的value String hql = "select new Map(e.id as eid,e.name as ename,e.salary as esalary,e.dept.name as dname,e.dept.address.city as dcity,e.dept.sn as dsn) from Employee e"; List<Map<String, Object>> ret = session.createQuery(hql).list(); for (Map<String, Object> os : ret){ System.out.println(os); } //直接通过new VO对象来把结果包装成一个VO对象(4) //注意,VO对象需要一个构造方法,这个构造方法参数的顺序必须和查询的顺序匹配 String hql = "select new EmployeeVO(e.id,e.name,e.salary,e.dept.name,e.dept.address.city,e.dept.sn) from Employee e"; List<EmployeeVO> ret = session.createQuery(hql).list(); for (EmployeeVO os : ret){ System.out.println(os); }
NamedQuery查询:
在hibernate中,执行查询需要先将HQL先翻译成SQL,再执行SQL。如果HQL比较复杂翻译的效率是比较低的。如果一条HQL重复执行,会重复翻译。效率低下。
如果在代码不同的地方重复使用到了相同的HQL,需要在不同的地方反复重写HQL;
hibernate提供了NamedQuery方式,来稍微提高静态HQL语句的执行效率。和对HQL的统一管理
NamedQuery使用:
在实体映射文件中添加:
<!--为HQL起名为findCustomersByName,该HQL在hibernate启动的时候就会翻译成SQL -->
<query name="findCustomersByName">
<![CDATA[from Customer c where c.name like :name]]>
</query>
查询的时候使用:
//通过getNamedQuery,得到的就是已经翻译为SQL的query对象,只需要设置参数查询就行了
NamedQuery的使用限制:NamedQuery里面只能配置静态的HQL。
二级缓存概念:
1,生命周期为整个应用的缓存(二级缓存是sessionFactory上的缓存,能提供整个应用中所有的session使用。)
2,所有的get,load方法,总是先查一级缓存,再查二级缓存,如果都没有,在去数据库里面查询。
3,不是所有的对象都适合放到二级缓存中。(读>>>写)
4,二级缓存有一些性能的指标
1),命中率(总的从二级缓存中取得的数量/总的取的数量)
2),最大对象数量;
3),最大空闲时间;
5,二级缓存实际上就是一个缓存,所以,hibernate并没有实现自己的二级缓存框架,而是用的开源的。
对象缓存策略:
1),usage="read-only" :放到二级缓存里面的对象是只读(性能最高)
2),usage="read-write":允许读写(对并发支持较好)
3),usage="nonstrict-read-write":允许读写,但是在并发事务情况下会产生脏数据
4),usage="transactional" :允许读写,并且支持全事务(只能在ApplicationServer环境下有用)
ehcache的配置:
<defaultCache>:默认的cache,相当于公用cache;
<cache>:自定义的cache;
共同的配置:
1,maxElementsInMemory:该缓存池放在内存中最大的缓存对象个数;
2,eternal:是否永久有效,如果设置为true,内存中对象永不过期;
3,timeToIdleSeconds:缓存对象最大空闲时间,单位:秒;
4,timeToLiveSeconds:缓存对象最大生存时间,单位:秒;
5,overflowToDisk:当内存中对象超过最大值,是否临时保存到磁盘;
6,maxElementsOnDisk:能保存到磁盘上最大对象数量;
7,diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
8,memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。
默认策略是LRU(最近最少使用),可以设置为FIFO(先进先出)或是LFU(较少使用)
在默认的情况下,不同类型的对象都是放在defaultCache中的;
自定义二级缓存(把某一个对象放到某一个特定的二级缓存区域)
1,在hibernate.cfg.xml文件中的class-cache添加region; (添加二级缓存区域)
<class-cache usage="nonstrict-read-write" class="com._520it.hibernate.day4.query.Employee" region="EMPLOYEE"/>
2,在hibernate.cfg.xml文件中的hibernate.propertie属性上添加region_prefix (添加二级缓存前缀)
<property name="cache.region_prefix">hibernate</property>
3,在ehcache.xml中配置一个cache,名字为region_prefix.region (为自己的对象配置一个缓存区域 :前缀.缓存区域)
<cache name="hibernate.EMPLOYEE"
maxElementsInMemory="10000"
eternal="true"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
二级缓存的操作
//得到二级缓存对象
Cache cache=sf.getCache();
//剔除一个实例
cache.evictEntity(User.class, 1L);
//剔除某种类型的所有实例
cache.evictEntityRegion(User.class);
//剔除所有二级缓存实例
cache.evictEntityRegions();
查询缓存:(使用非常少,因为可能带来非常大的负面性能影响)
1,默认情况下,hibernate没有打开查询缓存;
2,使用查询缓存:
1),打开查询缓存:
2),在查询的时候,使用Query对象的.setCacheable(true)方法;
3,查询缓存使用的条件:
1,两条查询的HQL和查询参数必须完全一致;
Hibernate中的事务管理
使用Hibernate的锁机制主要是用来避免第一类丢失更新和第二类丢失更新;
Hibernate使用悲观锁其实就是使用数据库锁:
如果数据库不支持设置的锁机制,hibernate会使用该数据库提供的合适的锁机制来完成,而不会报错。
1,使用session.load(class,id,LockOptions);加悲观锁,相当于发送SELECT ... FOR UPDATE
2,使用session.get(class,id,LockOptions);加悲观锁,相当于发送SELECT ... FOR UPDATE
3,使用session.buildLockRequest(LockOptions).lock(entity);加悲观锁,相当于发送SELECT id FROM ... FOR UPDATE
4,使用query.setLockOptions(LockOptions);加悲观锁,相当于发送SELECT... FOR UPDATE
使用一个额外的版本控制字段来防止第二类丢失更新(乐观锁机制);
1,给表添加一个额外的数字类型字段version;
2,在insert一个对象的时候初始化version值为0;
3,在select的时候,查询出对象的版本号;
4,在update的时候,
1),更新版本号,version = version+1;
2),在update的where条件中带上当前更新对象的版本号 where .. and version = ?
3),如果update返回影响条数>0,说明更新成功;
4),如果update返回影响条数=0,说明更新的对象已经被其他事务更新或者删除,抛出异常,回滚当前事务;
在hibernate中使用乐观锁,推荐使用version方式;
1,给对象添加一个int version字段,最好设置属性为private;
2,在mapping文件中添加<version>元素即可;
事务并发5类问题(如果数据库没有做任何并发处理的情况下):
第一类丢失更新:两个事务更新相同数据,如果一个事务提交,另一个事务回滚,第一个事务的更新会被回滚
脏读:第二个事务查询到第一个事务未提交的更新数据,第二个事务根据该数据执行,但第一个事务回滚,第二个事务操作脏数据
虚读:一个事务查询到了另一个事务已经提交的新数据,导致多次查询数据不一致
不可重复读:一个事务查询到另一个事务已经修改的数据,导致多次查询数据不一致
第二类丢失更新:多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事务提交会覆盖前面所有事务对数据的改变
一般情况,数据库都会处理一些事务并发的问题,数据库提供了不同的事务隔离级别来处理不同的事务并发问题,事务隔离级别定义如下:
READ_UNCOMMITED:允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读(相当于没有做任何事务隔离)
READ_COMMITTED:允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生(ORACLE默认级别)
REPEATABLE_READ:对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。(MYSQL默认级别)
SERIALIZABLE:完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。(ORACLE支持)
所以,数据库的隔离级别除了SERIALIZABLE,都不能处理第一类丢失更新和第二类丢失更新;
所以,数据库提供了锁机制来防止第一类丢失更新和第二类丢失更新;