hibernate复习第(三)天
今日要点:
1、继承关系映射
一个表对应一个映射树(subclass)
一个子类一个表,子类只有父类没有的字段(joined-subclass)
鉴别器和内连接结合使用(subclass join)
一个类对应一个表(union-subclass)
2、懒加载
3、缓存
4、事务以及事务有关
1、j继承映射
1、一张表映射一个继承树。
Employee.hbm.xml的主要代码:
<class name="Employee" discriminator-value="0"> <!-- 鉴别属性值为0表示为普通雇员 --> <id name="id" > <generator class="native"/> </id> <discriminator column="type" type="int"/><!-- 鉴别器,缺省类型是字符串类型 ,鉴别器的位置放置在property前面,不写值的话表示属性值为类名的字符串形式--> <property name="name"/> <many-to-one name="depart" column="depart_id" cascade="save-update"/> <subclass name="Skiller" discriminator-value="1"> <!-- sub:替代品 ,表示子类 --> <property name="skill"/> </subclass> <subclass name="Sales" discriminator-value="2"> <property name="sell"/> </subclass> </class>
效率比较高
增加子类就会修改表结构。增加字段
特有字段都不能加上非空约束,应该可以为空,关系型来说不是很好的设计
package cn.itcast.domain; import java.util.Set; public class Department { private int id; private String name; private Set<Employee> employees; public Set<Employee> getEmployees() { return employees; } public void setEmployees(Set<Employee> employees) { this.employees = employees; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Department() { super(); } public Department(int id, String name) { super(); this.id = id; this.name = name; } }
package cn.itcast.domain; public class Employee { private int id; private String name; private Department depart; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Department getDepart() { return depart; } public void setDepart(Department depart) { this.depart = depart; } public Employee() { super(); } public Employee(int id, String name, Department depart) { super(); this.id = id; this.name = name; this.depart = depart; } @Override public String toString() { return "Employee [id=" + id + ", name=" + name + ", depart=" + depart + "]"; } }
package cn.itcast.domain; public class Sales extends Employee{//销售 private int sell; public void setSell(int sell) { this.sell = sell; } public int getSell() { return sell; } }
package cn.itcast.domain; public class Skiller extends Employee{//技术 private String skill; public void setSkill(String skill) { this.skill = skill; } public String getSkill() { return skill; } }
<?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 package="cn.itcast.domain"> <class name="Department"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <set name="employees" inverse="true"> <key column="depart_id" /> <one-to-many class="Employee" /> </set> </class> </hibernate-mapping>
2、每一个子类一张表,通过外键关联
其他地方不用改变,只需要改变下employee.hbm.xml的代码(之后也是):
<class name="Employee"> <!-- 鉴别属性值为0表示为普通雇员 --> <id name="id" > <generator class="native"/> </id> <property name="name"/> <many-to-one name="depart" column="depart_id" cascade="save-update"/> <joined-subclass name="Skiller" table="skiller"><!-- join表示数据库表中使用连接与子类关联 --> <key column="emp_id"/><!-- 连接使用的外键列 --> <property name="skill"/> </joined-subclass> <joined-subclass name="Sales" table="sales"> <key column="emp_id"/> <property name="sell"/> </joined-subclass> </class>
表结构合理,子类和主类差距比较的的时候使用。
多态方式查询的话关联表过多效率低。不使用多态查询的话就比较优秀
3、鉴别器和内连接相结合
适用情况:雇员中销售的字段比较多但是技术的字段比较少
<class name="Employee" discriminator-value="0"> <!-- 鉴别属性值为0表示为普通雇员,注意:鉴别器的值如果不给的话,默认认为是类名的全名称 --> <id name="id" > <generator class="native"/> </id> <discriminator column="type" type="int"/> <!-- 鉴别器,缺省类型是字符串类型 ,鉴别器的位置放置在property前面--> <property name="name"/> <many-to-one name="depart" column="depart_id" cascade="save-update"/> <subclass name="Skiller" discriminator-value="1"> <property name="skill"/> </subclass> <subclass name="Sales" discriminator-value="2"> <join table="sales"> <key column="emp_id"/> <property name="sell"/> </join> </subclass> </class>
4、每一个类对应一个表,且每一个表中都具有完整信息,如果父类不是抽象的,也会有父类表
<?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 package="cn.itcast.domain"> <class name="Employee"><!-- discriminator-value="0" --> <!-- 鉴别属性值为0表示为普通雇员,注意:鉴别器的值如果不给的话,默认认为是类名的全名称 --> <id name="id" > <!-- <generator class="native"/>--> <!-- mysql,选取nativehibernate帮你选择的主键生成器是identity --> <generator class="hilo"/> </id> <!-- <discriminator column="type" type="int"/> --> <!-- 鉴别器,缺省类型是字符串类型 ,鉴别器的位置放置在property前面--> <property name="name"/> <many-to-one name="depart" column="depart_id" cascade="save-update"/> <!-- <subclass name="Skiller" discriminator-value="1"> <property name="skill"/> </subclass> <subclass name="Sales" discriminator-value="2"> <property name="sell"/> </subclass> --><!-- sub:替代品 ,表示子类 --> <!-- <joined-subclass name="Skiller" table="skiller"> <key column="emp_id"/> <property name="skill"/> </joined-subclass> <joined-subclass name="Sales" table="sales"> <key column="emp_id"/> <property name="sell"/> </joined-subclass> --> <!-- join表示数据库表中使用连接与子类关联 --><!-- 连接使用的外键列 --> <!-- <subclass name="Skiller" discriminator-value="1"> <property name="skill"/> </subclass> <subclass name="Sales" discriminator-value="2"> <join table="sales"> <key column="emp_id"/> <property name="sell"/> </join> </subclass> --> <union-subclass name="Skiller" table="skiller"> <!-- union表示结合的意思 --> <property name="skill"/> </union-subclass> <union-subclass name="Sales" table="sales"> <!-- union表示结合的意思 --> <property name="sell"/> </union-subclass> </class> </hibernate-mapping>
注意:父类和子类表的id是不能重复的,所以我们的主键生成策略不能再时native或者identity这种自增长了。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
懒加载:
懒加载就是返回的其实是一个代理对象,该代理对象是你的domain对象的一个子类。在你使用到该对象的时候,hibernate才进行数据库的查询
懒加载的对象在session关闭之后调用该属性会报错。no Session,无法访问数据库,解决方法:
1、在session未关闭之前就调用一下该对象的属性,使得hibernate访问数据库,将代理对象中的对应值填充好。
2、使用Hibernate.instialize(Object)方法实例化懒加载的对象,这样他就会查询数据库将该对象持久化。
懒加载使用的是:asm.jar和cglib.jar实现的,那两个jar能够动态修改装入内存的字节码
load方式的懒加载:
package cn.itcast.domain; import java.util.Date; public class User { private int id; private Name name; private Date birth; public int getId() { return id; } public void setId(int id) { this.id = id; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } } package cn.itcast.domain; public class Name { private String firstName; private String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } <?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 package="cn.itcast.domain"> <class name="User"> <id name="id"> <generator class="native"/> </id> <property name="birth"/> <component name="name" class="Name"> <property name="firstName" column="first_name"/> <property name="lastName" column="last_name"/> </component> </class> </hibernate-mapping>
这样使用会抛出异常:
package cn.itcast.test; import java.util.Date; import org.hibernate.Session; import org.hibernate.Transaction; import cn.itcast.dao.HibernateUtil; import cn.itcast.domain.Name; import cn.itcast.domain.User; public class Demo2 { public static void main(String[] args) { add(); User user=query(1); System.out.println(user.getName()); } static void add(){ Session s=null; Transaction tx=null; try{ s=HibernateUtil.getSession(); User user=new User(); Name name=new Name(); name.setFirstName("zhang"); name.setLastName("san"); user.setName(name); user.setBirth(new Date()); tx=s.beginTransaction(); s.save(user); tx.commit(); }finally{ if(s!=null) s.close(); } } static User query(int id){ Session s=null; try{ s=HibernateUtil.getSession(); User user=(User) s.load(User.class, id); return user; }finally{ if(s!=null) s.close(); } } }
Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:57) at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:111) at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:150) at cn.itcast.domain.User$$EnhancerByCGLIB$$aedc35f5.getName(<generated>) at cn.itcast.test.Demo2.main(Demo2.java:17)
解决方式:
static User query(int id){ Session s=null; try{ s=HibernateUtil.getSession(); User user=(User) s.load(User.class, id); //user.getName(); 在session关闭前简单使用下该对象,就会立刻加载过来了。不建议这样使用,因为调用该对象的语句看起来没有意义可能被误删 Hibernate.initialize(user); //建议使用这种方式 return user; }finally{ if(s!=null) s.close(); } }
one-to-one(元素)懒加载
必须同时满足下面三个条件:
(主表不能有constrained=trur,所以主表没有懒加载)
1、lazy属性不等于false
2、constraint=true。有外键关联
3、fetch=select 抓取方式为select
例子:
package cn.itcast.domain; public class Person { private int id; private String name; private IdCard idcard; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public IdCard getIdcard() { return idcard; } public void setIdcard(IdCard idcard) { this.idcard = idcard; } } package cn.itcast.domain; import java.util.Date; public class IdCard { private int id; private Person person; private Date startUseDate; public int getId() { return id; } public void setId(int id) { this.id = id; } public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } public Date getStartUseDate() { return startUseDate; } public void setStartUseDate(Date startUseDate) { this.startUseDate = startUseDate; } } <?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 package="cn.itcast.domain"> <class name="Person"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <one-to-one name="idcard"/> </class> </hibernate-mapping> <?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 package="cn.itcast.domain"> <class name="IdCard"> <id name="id"> <generator class="foreign"> <param name="property">person</param> </generator> </id> <property name="startUseDate" column="start_use_date"/> <one-to-one name="person" constrained="true" cascade="save-update" fetch="select"/> </class> </hibernate-mapping> package cn.itcast.test; import java.util.Date; import org.hibernate.Session; import org.hibernate.Transaction; import cn.itcast.dao.HibernateUtil; import cn.itcast.domain.IdCard; import cn.itcast.domain.Person; public class Demo3 { public static void main(String[] args) { Person p=add(); IdCard idc=(IdCard) query(p.getId()); } static Person add(){ Session s=null; Transaction tx=null; try{ Person p=new Person(); p.setName("zhangSan"); IdCard idcard=new IdCard(); idcard.setStartUseDate(new Date()); // p.setIdcard(idcard); idcard.setPerson(p); s=HibernateUtil.getSession(); tx=s.beginTransaction(); // s.save(p); s.save(idcard); tx.commit(); return p; }finally{ if(s!=null) s.close(); } } static Object query(int id){ Session s=null; try{ s=HibernateUtil.getSession(); // Person obj=(Person) s.get(Person.class, id);//查询主表直接将所有属性查出,使用left join。无法懒加载 Object obj=s.get(IdCard.class, id);//查询附表进行了懒加载 System.out.println(obj); /* * fetch如果设置成为了join,则抓取附表对象的时候进行的是连接查询,就使得懒加载失去了效果 */ return obj; }finally{ if(s!=null) s.close(); } } }
多对一、一对多、多对多进行懒加载的要求:
lazy属性不能为false,
fetch为select
能够进行懒加载的对象都是被改写过的代理对象,当相关联的session没有关闭时,访问这些懒加载(代理)对象的属性(getId和getClass除外),hibernate会初始化这些代理,相关联的session关闭之后,再次访问懒加载对象将会出现异常。
hibernate中对于空集合和为null的集合是不进行区分的
---------------------------------------------------------------------------------------------------------------------------------------------------
缓存。
我们知道从数据库中拿一条数据出来是成本高的操作,我们一般有这样的需求:比如读一个用户信息,前一个人读一个A的用户信息,读完后下一个人又读取A的用户信息,这个时候我们希望采取一些优化措施。我们希望第一个人从数据库中读取,第二个人可以使用第一个人的一些成果。
缓存的简单模拟:
package cn.itcast.test; import java.util.HashMap; import java.util.Map; import cn.itcast.domain.User; public class CacheMoNiDemo { private Map<String,Object> cache=new HashMap<String,Object>(); public void update(User user){ updateDB(user); String key=User.class.getName()+user.getId(); cache.remove(key);//我这里采取的操作是删除该数据的缓存 } public User getUser(int id){ String key=User.class.getName()+id; User user=(User) cache.get(key); if(user!=null){ return user; } user=(User) getFromDB(); cache.put(key, user); return user; } public Object getFromDB(){ //空的,简单模拟 return null; } public void updateDB(Object obj){ } }
缓存当然不是我写的那么简单啦,可能遇到内存溢出,缓存超时,多台机器信息的交流等等。
hibernate中的缓存
一级缓存,Session级共享
存在于session的缓存。有点类似于我们前面模拟的缓存,不过存入缓存和删除缓存的操作比我们的更加人性。
例子:
package cn.itcast.test; import java.util.Date; import org.hibernate.Session; import org.hibernate.Transaction; import cn.itcast.dao.HibernateUtil; import cn.itcast.domain.Name; import cn.itcast.domain.User; public class SessionCacheDemo { public static void main(String[] args) { User user=add(); query(user.getId()); } static User add(){ Session s=null; Transaction tx=null; try{ s=HibernateUtil.getSession(); User user=new User(); Name name=new Name(); name.setFirstName("first name"); name.setLastName("last name"); user.setName(name); user.setBirth(new Date()); tx=s.beginTransaction(); s.save(user); tx.commit(); // user=(User) s.get(User.class, user.getId()); 这里没有执行后查询语句,使用了一级缓存 return user; }finally{ if(s!=null) s.close(); } } static void query(int id){ Session s=null; User user=null; try{ s=HibernateUtil.getSession(); user=(User) s.get(User.class, id); System.out.println("--------------------"); user=(User) s.get(User.class, id); /*sql语句的输出可以看到只查询了一遍,使用了缓存,并且说明了缓存的范围是在session中*/ s.evict(user);//清除缓存中的指定对象所在键值对 user=(User) s.get(User.class, id); System.out.println("--------------------"); s.clear();//清除session中的所有缓存 user=(User) s.get(User.class, id); System.out.println("--------------------"); }finally{ if(s!=null) s.close(); } } }
save、update、saveOrUpdate、load、get、list、iterate、lock这些方法都会将对象存入session一级缓存中,一级缓存不能控制缓存的数量,所以请注意大批量操作数据时可能造成内存溢出,可以用evict,clear方法清除缓存中的指定对象和清空缓存。
二级缓存:SessionFactory级共享
我们知道,一个完善的缓存机制是比较复杂的,hibernate提供的session级缓存不能满足我们的所有要求,于是我们需要使用其他缓存。hibernate提供了接口给我们,也就是说,我们可以在hibernate中配置其他的缓存插件,达到我们想要的效果。
配置缓存插件的步骤:
1、hibernate中开启二级缓存。
在hibernate.cfg.xml中配置<property name="cache.use_second_level_cache">true</property>。这个属性在缺省状态下为true,也就是默认hibernate是打开二级缓存的
2、指定二级缓存插件的提供者。这里我使用的是OSCache
<property name="cache.provider_class">org.hibernate.cache.OSCacheProvider</property>
使用该缓存插件的时候注意你有没有导入该缓存包
二级缓存插件确定了,如何进行一节二级缓存的基本配置呢?
我在src下面加入了一个oscache.properties文件,然后在该文件中配置了os缓存的一些信息
如何确定二级缓存会缓存那些内容呢?
加入你需要对user类的对象进行缓存,当然缓存的额是持久化对象。可以在hibernate.cfg.xml中配置:
<class-cache usage="read-only" class="cn.itcast.domain.User"/>
也可以在bean的映射文件中配置,比如:
<class name="User">
<cache usage="read-only"/>
。。。
其中usage属性是表示缓存的级别:
read-only则表示缓存的东西不会发生改变,比如缓存省市这种东西的时候就可以使用这个级别的缓存。如果缓存的对象发生了改变就会报错
read-write表示允许对缓存的对象进行读写操作,而且具有很好的并发性,缓存的内容基本不会出现问题
nonstrict-read-write表示允许对缓存的对象进行读写操作,并发要求不高,可能出现一些错误数据。在你允许一些缓存问题的情况下使用,比如评论。
transactional:级别不太清楚,有点麻烦
查看一些hibernate的状态:
可以在配置文件中打开统计信息,然后借助统计信息,得到你需要的信息。
<property name="hibernate.generate_statistics">true</property>
hibernate进行数据的查询,首先在一级缓存查找,然后如果没有在二级缓存查找,如果还没有再往数据库查找
例子:
<property name="cache.use_second_level_cache">true</property> <property name="cache.provider_class">org.hibernate.cache.OSCacheProvider</property> <property name="hibernate.generate_statistics">true</property> 。。。 <class-cache usage="read-write" class="cn.itcast.domain.User"/> package cn.itcast.test; import java.util.Date; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.stat.Statistics; import cn.itcast.dao.HibernateUtil; import cn.itcast.domain.Name; import cn.itcast.domain.User; public class SecondCacheDemo { public static void main(String[] args) { User user=add(); query(user.getId()); query(user.getId()); Statistics st= HibernateUtil.getSessionFactory().getStatistics(); System.out.println(st); System.out.println(""+st.getSecondLevelCachePutCount());//放入了多少次 System.out.println("miss:"+st.getSecondLevelCacheMissCount());//miss多少次 System.out.println("hit:"+st.getSecondLevelCacheHitCount());//击中多少次 } static User add(){ Session s=null; Transaction tx=null; try{ s=HibernateUtil.getSession(); Name name=new Name(); name.setFirstName("first name"); name.setLastName("last name"); User user=new User(); user.setBirth(new Date()); user.setName(name); tx=s.beginTransaction(); s.save(user); tx.commit(); return user; }finally{ if(s!=null) s.close(); } } static void query(int id){ Session s=null; User user=null; // Transaction tx=null; try{ s=HibernateUtil.getSession(); user=(User) s.get(User.class, id); user=(User) s.get(User.class, id); s.clear(); // user=(User) s.get(User.class, id); // tx=s.beginTransaction(); // user.setBirth(new Date()); // tx.commit(); }finally{ if(s!=null) s.close(); } } }
一些补充:
将信息放入二级缓存的方法:
save、update、saveOrUpdate、list、iterator、get、load、以及Query、Critria都会填充二级缓存,查询数据时session的iterator、get、load可以从缓存中读取数据
注意:session中的save方法不适合native生成方式的主键,也就是说native生成方式的主键使用save方法可能不会存入缓存
Query、Criteria(查询缓存)由于命中率低,所以hibernate缺省是关闭修改hibernate.cfg.xml中的property:
<property name="cache.use_query_cache">true</property>才能打开查询缓存
并且query和Criteria存储结果入二级缓存中需要使用query.setCacheable(true),Criteria.setCacheable(true)结果才能存入二级缓存
SessionFactory中使用evit()或者evitXXX()清除缓存内容
统计信息打开generate_statics用sessionFactory.getStatics()获取统计信息
public static void main(String[] args) { User user=add(); query1(user.getId()); } static void query1(int id){ Session s=null; User user=null; try{ s=HibernateUtil.getSession(); String hql="from User"; Query qy=s.createQuery(hql); qy.setCacheable(true); qy.list(); }finally{ if(s!=null) s.close(); } try{ s=HibernateUtil.getSession(); String hql="from User"; Query qy=s.createQuery(hql); qy.setCacheable(true); qy.list(); }finally{ if(s!=null) s.close(); } System.out.println("will clear User in sessionFactory"); HibernateUtil.getSessionFactory().evict(User.class); try{ s=HibernateUtil.getSession(); String hql="from User"; Query qy=s.createQuery(hql); qy.setCacheable(true); qy.list(); }finally{ if(s!=null) s.close(); } } }
缓存的再补充说明:
分布式缓存和中央缓存
分布式缓存:
我们知道大的系统可能会有很多台机器做处理,这种系统做缓存的话,如果每台机器都做一个缓存,这种缓存叫做分布式缓存。这样的话会出现负载均衡的问题。例子:1个人向第一台机器查询一个记录,然后拿到了,缓存中存入一个数据,返回给客户端。第2个人向第二台机器查询同一个记录,然后拿到了,缓存存入,返回给客户端。然后第三个人从第三台机器上,修改之前查询的数据,那么前面两台机器上的缓存数据就变成了废数据。
我们希望每一台服务器在更新对象信息的时候通知前面的服务器更新缓存,这种操作叫做集群。osCache支持这种操作。
从它的配置文件下弄过来的:
#cache.cluster.properties=UDP(mcast_addr=231.12.21.132;mcast_port=45566;ip_ttl=32;mcast_send_buf_size=150000;mcast_recv_buf_size=80000):PING(timeout=2000;num_initial_members=3):MERGE2(min_interval=5000;max_interval=10000):FD_SOCK:VERIFY_SUSPECT(timeout=1500):pbcast.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800):pbcast.STABLE(desired_avg_gossip=20000):UNICAST(timeout=5000):FRAG(frag_size=8096;down_thread=false;up_thread=false):pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=true) #cache.cluster.multicast.ip=231.12.21.132 (这个我不太懂啊)
分布式缓存的缺点:更新成本高
中央缓存:
特意拿出一个服务器做缓存服务器,当每次获取数据,将缓存存入中央服务器。然后每一次读取数据都在中央服务器进行查询。这样做的特点:更新成本低,但是获取数据成本高,内存可以充分利用。
使用缓存的条件:
读取大于修改
数据量不能超过内存容量
对数据要有独享的控制
可以容忍出现无效数据
-----------------------------------------------------------
事务:
单个数据库(一个SessionFactory对应一个数据库),由JDBC实现
Session session=null;
Transaction tx=null;
try{
session=sessionFactory.openSession();
tx=session.beginTransaction();
//process
tx.commit();
}catch(HibernateException e){
if(tx!=null) tx.rollback(); throw e;
}finally{
if(session!=null) session.close();
}
hibernate本身是没有事务功能的,上面的额事务实质上是使用下面的代码实现的。
connection.setAutoCommit(false);
connection.commit();conn.rollback();
分布式事务:
可以简单理解成为跨数据库的事务,由应用JTA容器实现;使用JTATransaction需要配置hibernate.transaction.factory_calss参数
该参数缺省值是org.hibernate.transaction.JDBCTransactionFactory, 使用JTATransaction时需要将该参数改成org.hibernate.transaction.JTATransaction
,并配置jta.UserTransaction参数JNDI名
javax.transaction.UserTransaction tx=context.lookup("jndiName");
try{
tx.begin();
//多个数据库的session操作
//session1...
//session2...
tx.commit();
}catch(Exception e){
tx.rollback(); throw e;
}
事务的边界:
什么时候打开事务,什么时候提交事务,什么时候回滚
我们知道三层架构。事务边界应该由业务逻辑控制,但是我们如果在业务逻辑控制的话,事务的开关是 属于dao层的东西却在业务逻辑层,不合理。
后面我们可以使用spring容器解决这个问题。
openSessionInView模式:
我们知道sessionFactory有一个方法:getCurrentSession():就是获取当前线程内的session。这个方法默认hibernate是无法使用的。
需要在配置文件中进行配置current_session_context_class属性,单服务器其值为thread:ThreadLocal来管理Session实现多个操作共享一个session,避免反复获取session,并控制事务边界,此时session不能调用close或者commit和rollback方法,因为那些时候session会被关闭掉
多态服务器的时候配置其值为jta:由jta事务管理器来管理事务
threadLocal是一个容器,类似于map容器,键使用当前线程。
open session in view:
优势:在渲染页面的额时候,保持页面打开。可以简单处理事务边界问题和懒加载session被关闭问题。
劣势:扩大了session和transaction的生命周期,导致系统的并发处理能力下降了。
例子:
package cn.itcast.web.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.hibernate.Session; import org.hibernate.Transaction; import cn.itcast.dao.HibernateUtil; public class OpenSessionInViewFilter implements Filter{ public void destroy() { } public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { /* * 请求开始打开事务,请求完成提交事务,请求异常回滚事务,一个session对应一个请求 */ Session session=null; Transaction tx=null; try{ session=HibernateUtil.getThreadSession(); tx=session.beginTransaction(); arg2.doFilter(arg0, arg1); tx.commit(); }catch(Exception e){ if(tx!=null) tx.rollback(); throw new RuntimeException(e.getMessage(),e); }finally{ if(session!=null) HibernateUtil.closeCurrentSession(); } } public void init(FilterConfig arg0) throws ServletException { } }
package cn.itcast.dao; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public final class HibernateUtil { private static SessionFactory sessionFactory; private static ThreadLocal<Session> threadLocal=new ThreadLocal<Session>();; private HibernateUtil(){ } static{ sessionFactory=new Configuration().configure().buildSessionFactory(); } public static SessionFactory getSessionFactory(){ return sessionFactory; } public static Session getSession(){ return sessionFactory.openSession(); } public static Session getThreadSession(){ Session session=sessionFactory.getCurrentSession(); if(session==null){ session=getSession(); threadLocal.set(session); } return session; } public static void closeCurrentSession(){ Session session=getThreadSession(); if(session!=null){ session.close(); threadLocal.set(null); } } } hibernate.cfg.xml中: <property name="current_session_context_class">thread</property> dao中操作: public void addUser(User u){ HibernateUtil.getThreadSession().save(u); }
乐观锁和悲观锁:
悲观锁和乐观锁
问题的引出:
我们在进行数据的修改往往是做两个步骤:1、获取初始数据显示在修改的表单项中 2、修改表单项数据并提高修改。 我们假设AB两个管理员几乎同时准备修改同一个用户信息,那么在显示修改页面得到的用户信息是一样的,A先提交,B提交的数据会将A提交的数据覆盖掉。而且B不会知道还有A也对该数据进行了一次更改。
解决方案:
1、悲观锁定。类似于同步。A读取到了用户信息的时候,给该用户加上锁,B用户无法读取等操作,知道A用户完成提交,释放锁,其他用户才可以针对该信息进行操作。这样做解决了前面的并发问题,但是效率慢,如果A锁定到释放锁的过程过长,不合适。对并发影响较大。
2、乐观锁定。对每一条记录加上一个标识列。当数据进行一次更改,标识列进行一次更改,标识列是唯一的,可以是int自增,也可以是时间戳。AB两个用户都得到了对象X,A先进行提交,验证标识列,通过,修改数据,标识列进行修改。然后B提交数据,验证标识列,原本的标识列发生改变,验证失败,抛出异常。
标识列可以使用version,版本号,也可以使用时间戳,timestamp,version可以指定多种类型,timestamp的类型只能是Date
例子:
package cn.itcast.domain; import java.util.Date; public class User { private int id; private Name name; private Date birth; // private int ver; private Date ver; public int getId() { return id; } public void setId(int id) { this.id = id; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } // public void setVer(int ver) { // this.ver = ver; // } // public int getVer() { // return ver; // } public Date getVer() { return ver; } public void setVer(Date ver) { this.ver = ver; } } package cn.itcast.dao; import org.hibernate.Session; import org.hibernate.Transaction; import cn.itcast.domain.Name; import cn.itcast.domain.User; public class UserDao { public static void main(String[] args) { // HibernateUtil.getSessionFactory().getCurrentSession(); User user=add(); Session s1=null; Session s2=null; try{ s1=HibernateUtil.getSession(); s2=HibernateUtil.getSession(); User u1=(User) s1.get(User.class, user.getId()); u1.getName().setFirstName("firstName2"); User u2=(User) s2.get(User.class, user.getId()); u2.getName().setFirstName("firstName3"); Transaction t1=s1.beginTransaction(); Transaction t2=s2.beginTransaction(); s1.update(u1); s2.update(u2); t1.commit(); try { Thread.sleep(100);//设置为timestamp形式因为两个事务提交时间几乎一样,检测不到,所以睡眠一下 } catch (InterruptedException e) { e.printStackTrace(); } t2.commit(); }finally{ if(s1!=null) s1.close(); if(s2!=null) s2.close(); } } static User add(){ Session s=null; Transaction tx=null; try{ User user=new User(); Name name=new Name(); name.setFirstName("firstName1"); user.setName(name); s=HibernateUtil.getSession(); tx=s.beginTransaction(); s.save(user); tx.commit(); return user; }finally{ if(s!=null) s.close(); } } } <?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 package="cn.itcast.domain"> <class name="User" table="user"> <!-- <cache usage="read-write"/> --> <id name="id"> <generator class="increment"/> </id> <version name="ver"/> <!-- 缺省状态是int类型 --> <!-- <timestamp name="ver"/>--><!-- 类型只能是Date类型 --> <property name="birth"/> <component name="name" class="Name"> <property name="firstName" column="first_name"/> <property name="lastName" column="last_name"/> </component> </class> </hibernate-mapping>