zoukankan      html  css  js  c++  java
  • Hibernate-day04

    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,都不能处理第一类丢失更新和第二类丢失更新;

    所以,数据库提供了锁机制来防止第一类丢失更新和第二类丢失更新;

  • 相关阅读:
    分布式数据库管理系统
    Java并发(一)Java并发/多线程教程
    nginx重启后,反向代理失败之问题排查记录
    从spring源码汲取营养:模仿spring事件发布机制,解耦业务代码
    Mybatis中多表关联时,怎么利用association优雅写resultMap来映射vo
    曹工杂谈:为什么很少需要改Spring源码,因为扩展点太多了,说说Spring的后置处理器
    fastjson自由:controller上指定active profile,让你想序列化什么字段就序列化什么字段
    就因为加了Lombok的@Accessors(chain = true),bean拷贝工具类不干活了
    宽带爱折腾-将家里光猫转成桥接模式
    修改springfox-swagger源码,使example中时间格式默认为“yyyy-MM-dd HH:mm:ss”
  • 原文地址:https://www.cnblogs.com/Java0120/p/9867503.html
Copyright © 2011-2022 走看看