[键入文字]
观察上面的配置文件,不难发现我们对Sale采取了(1)方式,对Skill采取了(3)方式。同样请留意hibernate的sql语句和执行后的表。特别要说明的是:在进行操作时,由于hibernate在进行操作时不能删除前面的相关联的表(主要是和第一种继承关系“共享一张表”时发生冲突,因为在建立的三张表,在未删除sale/skill表时,是不能来删除employee表,因为employee表中的主键被另两张表关联了),所以需要手工删除表或者是直接删除数据库再建数据库。 补充:借助此例我们来看看discriminator-value的默认情况:如果我们在 (4)每个具体类一张完整表: 与第(2)种情况相比,它的主要特点就是为每个具体类建立一张表,表的字段对应子类本身的属性,同样也包括父类的所有属性。 同样只需要修改配置文件如下: 注意:id生成器选择了“hilo”,由于我们为每个具体类都映射了一张表,所以id不能只在每张表中递增,如果只 在每张表中递增,这里的三张表中的id将会出现重复,这样我们在采用多态查询,将会查出多种结果,我们应让id是唯一的,所以采取了hilo的方式来生成id,它能保证id是全局性的递增生成,这样每张表中的id均不会重复。 同样请注意,可能需要删除某些表或者是删库建库才能执行测试类。 执行完成后,需留意hibernate产生的sql语句和表的结构内容。 补充说明:在使用这种方法时,如果父类为抽象类也是可行得,我们可以在 总结:在上面的继续关系中我们多次用到了删库建库,在执行测试类时,如果出现sql不能更新或者sql相关的错 误,则不防尝试此方法。另外在学继承关系时,除了注意配置文件外,更应注意hibernate产生的sql语句以及执行后产生表的情况。通常我们建议表的数目不要超过类的数目。 4.懒加载: 在前面我们已经对懒加载有所提及,现在再借助一个简单的实例(它们均位于lazyLoad项目下)再来重新认识懒加载:先看下面的代码: - 26 - / 42 [键入文字] package com.asm.hibernate.test; public class UserTest2 { public static void main(String[] args) { addUser(); User u = getUser(1); System.out.println(\ } static User getUser(int id) { Session s = null; try { s = HibernateUtil.getSession(); User user = (User) s.load(User.class, id); // 以下的两种方式都可以让懒加载去真正连接数据库。 Hibernate.initialize(user); // System.out.println(user.getName()); System.out.println(\ return user; } finally { if (s != null) s.close(); } } static void addUser() { 省略内容:此方法的作用就是插入一条记录到数据库中,以使我们的查询操作可以进行。 } } 执行后,打印结果如下: load--User:class com.asm.hibernate.domain.User$$EnhancerByCGLIB$$212cbff4 return type:com.asm.hibernate.domain.User@1f6ba0f name=jack 注意到上面第一行打印结果,可以发现返回的是User的一个子类,借此来看看懒加载的实现,懒加载的意思是只有当我们真正要查询的数据时,它才真正去连接数据库,为什么要提出懒加载呢,我们知道数据库的连接是非常耗资源的,有了懒加载可以从一定程度上降低数据库连接资源的消耗。 懒加载本质是借助asm.jar和cglib.jar这两个jar包,来生成User的一个子类,这就是前面提到“使用懒加载时实体类不能是final”的原因,从这里我们知道hibernate用构建实体类的子类来实现一个更强大的操作功能,这样即使数据库无记录,查询返回的结果对象也永远不会为null,即是说“返回的结果对象==null”永远不能成立,但这并不表示一定查询出了结果。其实如果我们以懒加载查询的结果为空,而我们再对这个对象进行操作时,是会报错得。 概述要点:(1)hibernate构造了实体类的子类来查询(2)要使用赖加载,实体类不能声明为final (3)查询结果对象永不为空。 (4)只要连接未关,如果我们想获知此对象的任何属性信息都会引起懒加载去连接数据库,但是如果我们是企图获取它的id时,却不能让hibernate去连接数据库。 补充说明:其实这种懒加载的使用很少,它通常在需要关联两个实体对象时使用,比如我们希望把查询出的User user对象关联到部门中去时,即我们 Employee emp=new Employee(); emp.setUser(user);在这种情况下我们并不关心它的查询内容,只是想关联到部门中去,所以此时使用懒加载是一个节省“数据库连接资源”的好方法。 5.概述关联关系中的懒加载: (1)一对一中的懒加载:必需要同时满足三个条件才能实现懒加载:即在 lazy!=false(即是为true或proxy);constrained=true;fetch=select,但在主表中不会有懒加载,因为主表不能满足constrained=true;其实在主外键关联的“一对一“关联关系中,我们判断主表和从表也是从是否配置 “constrained“来判断:因为constrained的配置只会在从表中出现。从上面的分析中可以得知:查询主表永不会使用懒加载,查询从表可选择懒加载。 下面再结合fethch来分析查询从对象时的懒加载: fetch=join fetch=select - 27 - / 42 [键入文字] lazy=true 查询,所以会一次关联据库。 由于采取的是join连接懒加载有效,实际查询从对象发展时,才去连接数lazy=false 所有的表查出所有数=false意为不使用懒加载。 lazy=proxy 据,这样懒加载失效。 默认设置:使用代理实现懒加载。 提示:fetch的意思是“通过什么方式抓取”,lazy的意思是“什么时候抓取”。“抓取” 意为“获得数据,连接数据库”。 其实在“一对一”中使用懒加载对性能提升不多大的作用。 分析“查询主对象不能使用懒加”:当我们要想获取一个主对象时,仅从查询主表是不能判断出是否有从对象, 比如我们在查询主表获取Person对象时,不能从主表中查出是否有有“身份证号“,这样我们便不能正确设置Person对象的身份证属性,所以hibernate采取了连接查询,这也就是为什么主对象不能使用懒加载的原因。但是查询从对象可使用懒加载,原因如下:当我们通过查询从表获知从对象时,可以在从表的主外键中查询这个身份证号是否有Person对象对应,如果没有,设它的person属性为null,如果有我们放置一个代理,当要真正查询时,便通过这个代理来查询。 (2)其它关联关系使用懒加载的条件:在实配置文件中的“关联关系配置”配置元素中配置时满足以下两个 条件:lazy!=false;fetch=select (3)强调:能够懒加载的对象都是被必定过的代理对象,当相关联的session没有关闭时,访问这些懒加载对象 (代理对象)的属性会初始化这些代理(便getId和getClass除外)对象,当相关的session关闭后,再访问懒加载对象将会出现异常“could not initialize proxy - no Session”。 (4)灵活选择:在查询方法是我们有时候希望使用代理懒加载,但有时候我们可能又要具体查询出数据。我们可以这样做来灵活选择: public Object query(int id,boolean signLazy){ .... if(signLady) Hibernate.initialize(代理对象); 说明:为true,则会初始化代理,连接数据库,且在此方法调用完成后得到的对象是可以进行有关此对象的属性访 问得。为false,则不会去真正连接数据库,只是为了建立起某种关联关系服务 .... } (5)简单的属性也可以使用懒加载,但效果不大,除非是用在大的文本段。 十、缓存 1.模拟缓存并简要说明缓存实现原理 在myhibernate项目下新建一个包com.asm.hibernate.test.cacheTest来说明与缓存有关的问题。首先看下面的一个模拟缓存程序,主要代码如下: package com.asm.hibernate.test.cacheTest; public class CacheSimulate { static Map cache = new HashMap(); public static void main(String[] args) { addUser(); //第一次查询,会去连接数据库查询 User u1 = getUser(1); //第二次查询,直接从Map cache中取 User u2 = getUser(1); //第三次查询,同样从cache中直接取 User u3 = getUser(1); } static User getUser(int id) { String key = User.class.getName() + id; User user = (User) cache.get(key); if (user != null) return user; - 28 - / 42 [键入文字] } user = getUserFromDB(id); cache.put(key, user); return user; } static void addUser() { 省略代码,此方法的作用主要是向数据库添加一条记录,以方便查询操作 } static User getUserFromDB(int id) { 省略代码,作用就是真正去查数据 } 分析:重点来看getUser方法:当我们查询一个数据时,会首先在cache中查找,如果是第一次查询某数据,cache 中没有存这个数据,会去查数据库。但是如果已经查过数据,便会在cache中查找到此数据,然后直接返回。可以从控制台中看到:hibernate只与数据库交互一次。 为什么要提出缓存的概念:在前面已经多次说过与数据库建立连接是非常耗资源,而且相当耗时。为了保证高 效的查询性能,才提出了缓存的概念。缓存的原理:当第一次查询时会从数据库中查,当查出数据后会把数据保存在内存中,以后查询时直接从内存中查。当然,实际的缓存要远比此模拟程序复杂,但整个缓存机制是大同小异得,只是它要考虑到更多的细节。 下面来谈谈缓存机制要解决的三个主要问题: (1)向缓存中放数据:一般是发生在查询数据库时,因为每当我们不能从缓存中得到所需数据,便会去数据库中查找,查找完成后我们自然要把它更新缓存中。 (2)从缓存中取数据:涉及到一个key的设置问题,比如我们在模拟程序中,key的取值来自“id + 类的类型信 息”,这样就能保证key值的唯一性,因为如果仅以id作为key,那么其它的类会有相同的id时,在缓存中就不能区分。 (3)清掉缓存中失效的数据:当有其它的操作更新此数据时,原数据将不再正确,这时我们可以选择更新的方式来重新把新的数据更新到缓存中,也可以直接移除原数据,即调用 remove(key)。 2.Hibernate中的一级Session缓存: package com.asm.hibernate.test.cacheTest; public class HibernateCacheTest { public static void main(String[] args) { addUser(); getUser(1); } static User getUser(int id) { Session s = null; User user = null; try { s = HibernateUtil.getSession(); user = (User) s.get(User.class, id); System.out.println(\ // session缓存,当session未关闭时,再查询直接从缓存中获得数据。 user = (User) s.get(User.class, id); System.out.println(\ // 如果我们清掉缓存,再查询时将会重新连库。 s.evict(user);// 清掉指定的数据 // s.clear();//清掉当前session缓存中的所有内容 user = (User) s.get(User.class, id); System.out.println(\ } finally { if (s != null) - 29 - / 42 [键入文字] } s.close(); } // 当上面的session关闭后,如果想再获取前面查询的数据,必须重新查库。 try { s = HibernateUtil.getSession(); user = (User) s.get(User.class, id); System.out.println(\ } finally { if (s != null) s.close(); } return user; } static void addUser() { User user = new User(); user.setName(\ HibernateUtil.add(user); } 分析:经过上面的测试和相关说明我们可以得知如下结论: (1)session的缓存只在session未关闭前有效,关闭后再查相同的数据会重新连库 (2)我们可以手工清除session中的缓存:evict和clear (3)如果我们清掉session中的缓存,或是第一次查询这个数据,都会引起连库 (4)save,update,savaOrUpdate,load,get,list,iterate,lock等方法都会将对象放在一级缓存中,具体可以在上例的基础上进行测试。 (5)session一级缓存不能控制缓存数量,所以在大批量操作数据时可能造成内存溢出,这时我们可以用evict,clear来清除缓存中的内容 (6)session在web开发应用中,一般只在一个用户请求时进行缓存,随后将会关闭,这个session的存活时间很短,所以它的作用不大,因此提出了二级缓存在概念。 3.二级缓存: 二级缓存通常是第三方来实现,而我们使用时只需要对它进行配置即可。 下面演示使用二级缓存的具体步骤。 >>步骤一,在主配置文件中指明支持使用二级缓存: >>步骤二、配置第三方缓存机制: 由于我们这里选择了OSCacheProvider(它貌似也是hibernate官方开发得缓存机制)来提供缓存,所以还需要把它的缓存配置文件放在src目录下以使配置能被读到,这里即是把hibernate解压下的etc目录中的oscache.properties文件复制到src目录下。 >>步骤三、两种方式指定要缓存的实体类,一种是在主配置文件中配置(注意class是完整的类名): class=\ 另一种是在实体配置文件(映射文件)配置:比如在User.hbm.xml 的class元素下配置如下内容: read-only:如果你的应用程序只需读取一个持久化类的实例,而无需对其修改,那么就可以对其进行只读缓存。 这是最简单,也是实用性最好的方法。 read-write: 如果应用程序需要更新数据,那么使用“读/写缓存”比较合适。 如果应用程序要求“序列化事务” - 30 - / 42