框架学习之Hibernate 第十一节 Hibernate知识补充
1.配置文件 hibernate.cfg.xml
参见官方文档
2.映射文件 hbm.xml
参见官方文档
重点:主键生成方式
native:根据使用的数据库来确定id的生成方式
如果是插入操作的话,在插入之前就会对数据库进行一次访问来生成下一个id,然后才插入,也就是插入了之后才知道id
hilo:高低位方式,一部分是数据库生成的,另一部分是程序生成的,可以保证是不会重复的,这种方式和上面的不同之处在于它不是插入了之后才知道id的[我自己也很糊涂]
采用这种方式数据库中会生成一个表 hibernate-unique-key(默认情况),表中有一个字段 next-hi,意思就是下一个高位
如果是native类型获取主键,对mysql来说是自增长方式的,只有在插入数据库后才能拿到主键;而用hilo方式获取主键,这种方式不用马上插入数据也可以拿到主键
foreign:外键引用,通常要和one-to-one标签一起使用
在一对一映射中要指定引用的是哪个表的哪个字段
<id name="id"> <generator class="foreign"> <param name="property">person</param> </generator> </id>
uuid:UUID的生成方式生成id时不用访问一次数据库,它生成的id肯定是不同的,并且是string
如果插入比较多的话,可以考虑这种方式,但是查询效率会降低,因为比较的是string
3.Hibernate的类型
(1)<property name=“name” type=“java.lang.String”/>
type可以是hibernate、java类型或者你自己的类型(需要实现hibernate的一个接口)。
(2)基本类型一般不需要在映射文件(hbm.xml)中说明,只有在一个JAVA类型和多个数据库数据类型相对应时并且你想要的和hibernate缺省映射不一致时,需要在映射文件中指明类型(如:java.util.Date,数据库DATE,TIME,DATATIME,TIMESTAMP,hibernate缺省会把java.util.Date映射成DATATIME型,而如果你想映射成TIME,则你必须在映射文件中指定类型)。
默认的情况下,如果 java.util.Date -> datetime(数据库中)
如果只想保存日期,type设置成date
如果只想保存时间,type设置成time
如果两个都想保存,type设置成timestamp
(3)自定义类型,只要实现了接口就行
文档:
数据类型的对应关系:
内置的 basic mapping types 可以大致地分类为:
integer, long, short, float, double, character, byte, boolean, yes_no, true_false
这些类型都对应 Java 的原始类型或者其封装类,来符合(特定厂商的)SQL 字段类型。boolean, yes_no 和 true_false 都是 Java 中 boolean 或者 java.lang.Boolean 的另外说法。
string
从 java.lang.String 到 VARCHAR(或者 Oracle 的 VARCHAR2)的映射。
date, time, timestamp
从 java.util.Date 和其子类到 SQL 类型 DATE,TIME 和 TIMESTAMP(或等价类型)的映射。
calendar, calendar_date
从 java.util.Calendar 到 SQL 类型 TIMESTAMP 和 DATE(或等价类型)的映射。
big_decimal, big_integer
从 java.math.BigDecimal 和 java.math.BigInteger 到 NUMERIC(或者 Oracle 的 NUMBER类型)的映射。
locale, timezone, currency
从 java.util.Locale,java.util.TimeZone 和 java.util.Currency 到 VARCHAR(或者 Oracle 的VARCHAR2 类型)的映射。Locale 和 Currency 的实例被映射为它们的 ISO 代码。TimeZone 的实例被影射为它的 ID。
class
从 java.lang.Class 到 VARCHAR(或者 Oracle 的 VARCHAR2 类型)的映射。Class 被映射为它的全限定名。
binary
把字节数组(byte arrays)映射为对应的 SQL 二进制类型。
text
把长 Java 字符串映射为 SQL 的 CLOB 或者 TEXT 类型。
serializable
把可序列化的 Java 类型映射到对应的 SQL 二进制类型。你也可以为一个并非默认为基本类型的可序列化 Java 类或者接口指定 Hibernate 类型 serializable。
clob, blob
JDBC 类 java.sql.Clob 和 java.sql.Blob的映射。某些程序可能不适合使用这个类型,因为blob 和 clob 对象可能在一个事务之外是无法重用的。(而且, 驱动程序对这种类型的支持充满着补丁和前后矛盾。)
imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date, imm_serializable, imm_binary
一般来说,映射类型被假定为是可变的 Java 类型,只有对不可变 Java 类型,Hibernate 会采取特定的优化措施,应用程序会把这些对象作为不可变对象处理。比如,你不应该对作为 imm_timestamp 映射的 Date 执行 Date.setTime()。要改变属性的值,并且保存这一改变,应用程序必须对这一属性重新设置一个新的(不一样的)对象。
实体及其集合的唯一标识可以是除了 binary、 blob 和 clob 之外的任何基础类型。
4.Session和SessionFactory
Session是非线程安全的,生命周期较短,代表一个和数据库的连接,在B/S系统中一般不会超过一个请求;
内部维护一级缓存和数据库连接,如果session长时间打开,会长时间占用内存和数据库连接。
SessionFactory是线程安全的,一个数据库对应一个SessionFactory,生命周期长,一般在整个系统生命周期内有效;
SessionFactory保存着和数据库连接的相关信息(user,password,url)和映射信息,以及Hibernate运行时要用到的一些信息。
线程安全 意思就是多个地方进行访问同一个数据时,数据是同步的,不会乱的,因为正在访问的那个数据会被锁死,其他人没有办法访问
还有一种线程安全是 static 的,静态的,不会改变的,以及只读的属性,它们的值是不能够被修改的(final)
5. flush时将一级缓存与数据库同步,这个方法Hibernate自己会在相应的时间调用,最好不要自己调用,因为这个方法耗时耗资源
一般在查询之前或者提交之前执行
测试代码:[注:Department是 native的id生成器,而Employee是hilo的形式]
/** * @Author:胡家威 * @CreateTime:2011-8-15 下午11:18:42 * @Description: */ package com.yinger.main; import java.util.HashSet; import java.util.Set; import org.hibernate.Session; import org.hibernate.Transaction; import com.yinger.domain.Department; import com.yinger.domain.Employee; import com.yinger.util.HibernateUtils; public class One2Many { public static void main(String[] args) { add(); } private static void add() { Department depart = new Department(); depart.setName("depart name"); Employee emp1 = new Employee(); emp1.setName("emp1 name"); Employee emp2 = new Employee(); emp2.setName("emp2 name"); Set<Employee> emps = new HashSet<Employee>(); emps.add(emp1); emps.add(emp2); depart.setEmps(emps); Session s = null; Transaction tr = null; try{ s = HibernateUtils.getSession(); tr = s.beginTransaction(); s.save(depart); s.save(emp1); s.save(emp2); s.flush(); System.out.println("--------------------"); tr.commit(); }catch(Exception e){ if(tr!=null) tr.rollback(); }finally{ if(s!=null) s.close(); } } }
测试结果: 两条Employee的insert语句是在横线之上的
log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. Hibernate: insert into Department (name) values (?) Hibernate: insert into Employee (name, id) values (?, ?) Hibernate: insert into Employee (name, id) values (?, ?) Hibernate: update Employee set depart_id=? where id=? Hibernate: update Employee set depart_id=? where id=? --------------------
注释掉 s.flush() 方法之后,insert语句在横线的下面
log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. Hibernate: insert into Department (name) values (?) -------------------- Hibernate: insert into Employee (name, id) values (?, ?) Hibernate: insert into Employee (name, id) values (?, ?) Hibernate: update Employee set depart_id=? where id=? Hibernate: update Employee set depart_id=? where id=?
前者调用了flush方法,使得一级缓存和数据库进行了同步,这样就可以立即插入Employee对象了
后者则不会,Hibernate默认情况下都会对数据库操作进行优化,一般都会在最后的时刻进行数据库操作,也就是批量的进行数据库的CURD
这样可以提高数据库操作的效率
6.大批量处理
大量操作数据时可能造成内存溢出,解决办法如下:
①清除session中的数据
for(int i=0;i<100000;i++) session.save(obj);
for(int i=0;i<100000;i++){
session.save(obj);
if(i% 50 == 0){
session.flush(); // 一般要在clear之前进行一次flush操作,因为并不知道什么时候修改了数据,所以要在清除缓存之前和数据库进行一次同步
session.clear();
}
}
②用StatelessSession接口:它不和一级缓存、二级缓存交互,也不触发任何事件、监听器、拦截器,通过该接口的操作会立刻发送给数据库,与JDBC的功能一样。
StatelessSession s = sessionFactory.openStatelessSession();该接口的方法与Session类似。
③Query.executeUpdate()执行批量更新,会清除相关联的类二级缓存(sessionFactory.evict(class))
缺点:可能会造成级联和乐观锁定问题,因为二级缓存中的数据已被清空了
测试代码:
public static void main(String[] args) { People p = addPeople(); System.out.println("---------"); excuteUpdateTest(); } public static void excuteUpdateTest() { // hibernate 3.0 之前,批量更新只能是把数据一个个取出来然后更新 Session session = HibernateUtils.getSession(); Transaction tx = session.beginTransaction(); SQLQuery query = session.createSQLQuery("select {p.*} from people p").addEntity("p", People.class); List<People> list = query.list(); for(People people:list) { people.getName().setFirstName("new firstName"); } // hibernate 3.0 之后,有了 excuteUpdate 方法 update Customer set name = :newName Query query2 = session.createQuery("update People set first_name=:newName"); query2.setString("newName", "new name"); query2.executeUpdate(); tx.commit(); }
测试结果:
log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. Hibernate: insert into People (version, first_name, last_name, id) values (?, ?, ?, ?) --------- Hibernate: select p.id as id0_, p.version as version8_0_, p.first_name as first3_8_0_, p.last_name as last4_8_0_ from people p Hibernate: update People set version=?, first_name=?, last_name=? where id=? and version=? Hibernate: update People set first_name=?
数据库中的数据:
id version first_name last_name
1 1 new name lastName
7.拦截器和事件监听
拦截器与事件都是hibernate的扩展机制(类似过滤器),Interceptor接口是老的实现机制,现在改成事件监听机制;
他们都是hibernate的回调接口,hibernate在save,delete,update…等会回调这些类。
测试代码:Hibernate 3.0 之后将拦截器改成了事件监听!当然,拦截器还是有的
[ 这部分内容我测试失败,估计是我的Hibernate的版本的问题,实现的方法的返回值都是不同的,以下摘自百度]
以下摘自:http://hi.baidu.com/sonmeika/blog/item/b83d1c12aa687449f819b8b2.html 传智播客hibernate视频教程(十五):其他问题汇总
// 示例: // 在hibernate.cfg.xml中添加: <hibernate-configuration> <session-factory name="sessionFactory"> ...... <!-- 映射文件存放位置 --> <mapping resource="cn/itcast/hibernate/domain/User.hbm.xml"/> <mapping resource="cn/itcast/hibernate/domain/Department.hbm.xml"/> <mapping resource="cn/itcast/hibernate/domain/Employee.hbm.xml"/> <mapping resource="cn/itcast/hibernate/domain/Person.hbm.xml"/> <mapping resource="cn/itcast/hibernate/domain/IdCard.hbm.xml"/> <mapping resource="cn/itcast/hibernate/domain/Teacher.hbm.xml"/> <mapping resource="cn/itcast/hibernate/domain/Student.hbm.xml"/> <!-- 定义监听器 --> <event type="save"> <listener class="cn.itcast.hibernate.SaveListener"/> </event> </session-factory> </hibernate-configuration> SaveListener.java: package cn.itcast.hibernate; import org.hibernate.HibernateException; import org.hibernate.event.SaveOrUpdateEvent; import org.hibernate.event.SaveOrUpdateEventListener; import cn.itcast.hibernate.domain.User; public class SaveListener implements SaveOrUpdateEventListener { private static final long serialVersionUID = -5774832715365370704L; @Override public void onSaveOrUpdate(SaveOrUpdateEvent event) throws HibernateException { if (event.getObject() instanceof cn.itcast.hibernate.domain.User) { User user = (User) event.getObject(); System.out.println("--"+user.getId()); //如果有id值,就证明已经保存了 System.out.println("--"+user.getName()); } } } 这样在保存user时都会先经过onSaveOrUpdate。例如: User user = new User(); user.setBirthday(new Date()); user.setName("name"); System.out.println("before save"); addUser(user); System.out.println("after save"); 输出如下: before save --0 --name after save 在输出内容中看不到insert语句了,并且库里也没有添加记录。这是因为我们自定义的监听器覆盖了原有的监听器,hibernate认为已经定义了更好的监听器,就不执行默认的了。如果要继续执行默认的,则需要在hibernate.cfg.xml中增加如下: <!-- 定义监听器 --> <event type="save"> <listener class="cn.itcast.hibernate.SaveListener"/> <listener class="org.hibernate.event.def.DefaultSaveEventListener"/><!-- 注意:是DefaultSaveEventListener,而不是DefaultSaveOrUpdateEventListener --> </event> 监听器的调用顺序与此处的顺序有关,先调用SaveListener,后调用DefaultSaveEventListener。 修改后的输出如下: before save --0 // ID是0,说明此时还没有保存 Hibernate: select hibernate_sequence.nextval from dual Hibernate: insert into Persons (name, birthday, id) values (?, ?, ?) after save 调整监听器的顺序如下: <event type="save"> <listener class="org.hibernate.event.def.DefaultSaveEventListener"/><!-- 注意:是DefaultSaveEventListener,而不是DefaultSaveOrUpdateEventListener --> <listener class="cn.itcast.hibernate.SaveListener"/> </event> 输出如下: before save Hibernate: select hibernate_sequence.nextval from dual --1 // ID是1,说明对象已保存 Hibernate: insert into Persons (name, birthday, id) values (?, ?, ?) after save
8. SQL查询 和 命名查询
s.createSQLQuery:建议不要使用,因为兼容性不好,对于数据库的移植会造成影响
s.getNamedQuery:写在映射文件中的HQL,统一的管理,便于以后使用的方便
命名的sql放置在映射文件中,放置的位置不同(class内或者class外),传递的字符串的参数是不同的
测试代码:
在People的映射文件中添加:放在class外面
<query name="getFirstNameById"> <![CDATA[from People where id=:id]]> </query>
测试类中的方法:
package com.yinger.main; import java.util.List; import org.hibernate.Query; import org.hibernate.SQLQuery; import org.hibernate.Session; import org.hibernate.Transaction; import com.yinger.domain.Name; import com.yinger.domain.People; import com.yinger.util.HibernateUtils; public class QueryTest { public static void main(String[] args) { People p = addPeople(); System.out.println("---------"); sqlTest(); System.out.println("---------"); namedSqlTest(p.getId()); } public static void sqlTest() { Session session = HibernateUtils.getSession(); SQLQuery query = session.createSQLQuery("select {p.*} from people p").addEntity("p", People.class); // 注意这里是SQLQuery,不是Query List<People> list = query.list(); for(People people:list) { System.out.println(people.getName().getFirstName()); } } public static void namedSqlTest(int id) { Session session = HibernateUtils.getSession(); Query query = session.getNamedQuery("getFirstNameById"); query.setInteger("id", id); List<People> list = query.list(); for(People people:list) { System.out.println(people.getName().getFirstName()); } } public static People addPeople() { People p = new People(); Name n = new Name(); n.setFirstName("firstName"); n.setLastName("lastName"); p.setName(n); HibernateUtils.add(p); return p; } private static void testTransaction(int id) { Session s1 = HibernateUtils.getSession(); Transaction tx1 = s1.beginTransaction();//一个事务开启了 People p1 = (People)s1.get(People.class, id); Session s2 = HibernateUtils.getSession();; Transaction tx2 = s2.beginTransaction();//又开启了另一个事务,并且操作的是同一条数据 People p2 = (People)s2.get(People.class, id); p2.getName().setFirstName("firstName 2"); p1.getName().setFirstName("firstName 1"); tx2.commit(); tx1.commit(); s1.close(); s2.close(); } }
测试结果:
log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. Hibernate: insert into People (version, first_name, last_name) values (?, ?, ?) --------- Hibernate: select p.id as id0_, p.version as version8_0_, p.first_name as first3_8_0_, p.last_name as last4_8_0_ from people p firstName --------- Hibernate: select people0_.id as id, people0_.version as version8_, people0_.first_name as first3_8_, people0_.last_name as last4_8_ from People people0_ where (people0_.id=?) firstName
9. 不合适使用 Hibernate的场景
①不适合OLAP(On-Line Analytical Processing 联机分析处理),以查询分析数据为主的系统;适合OLTP(on-line transaction processing 联机事务处理)。
联机分析处理 系统总是进行查询统计,没有很好的对象和关系模型,不适合使用 Hibernate
②对于些关系模型设计不合理的老系统,也不能发挥hibernate优势。
③数据量巨大,性能要求苛刻的系统,hibernate也很难达到要求, 批量操作数据的效率也不高。
10. 与 JPA 集成 (annotation方式)
①需要添加的包ejb3-persistence.jar, hibernate-entitymanager.jar, hibernate-annotations.jar, hibernate-commons-annotations.jar, jboss-archive-browsing.jar, javassist.jar
②配置文件%CLASSPATH%/META-INF/persistence.xml
③JAVA代码:
EntityManagerFactory emf = Persistence.createEntityManagerFactory(name); //(Name:在persistence.xml中指定。) EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); Tx.begin(); Em.persist(entity);//remove,merge,find Tx.commit(); Em.close(); Emf.close();
相关的PPT: