Hibernate
1. O/R Mapping
a. Object Relational Mapping对象关系映射
b. 常见O/R Mapping框架: hibernate toplink jdo ibatis
c. JPA java persistence API
2. 相关JAR包
slf4j-api: simple Logging Facade for Java API 日志的Facde API
slf4j-nop: slf4j no-operation slf4j自己的实现, 也可以使用l4j的实现,但需额外加入slf4j-log4j 的转换JAR包
antlr: Auto Tool for Language Recognition 可以构建语言识别器,解析HQL语言
commons-collections: Apache的扩展集合类
javassist: 操作字节码,与cglib相关
jta: 定义JTA规范的JAR包,当Hibernate使用JTA时需要
ejb3-persistence: 实体类中的注解都是在这个JAR包中定义的.
3. 配置文件
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <!--ORACLE为 oracle.jdbc.driver.OracleDriver--> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <!--ORACLE为 jdbc:oracle:thin:@localhost:1521:username--> <property name="connection.url">jdbc:mysql://localhost/hibernate</property> <property name="connection.username">root</property> <property name="connection.password">root</property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL "方言" --> <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property> <!-- Enable Hibernate's automatic session context management --> <property name="current_session_context_class">thread</property> <!-- Disable the second-level cache --> <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property> <!-- 是否在控制台显示SQL语句 --> <property name="show_sql">true</property> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">create</property> <mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/> </session-factory> </hibernate-configuration>
4. 实体映射的Anotation:
package com.fortest.model; import java.util.Calendar; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; @Entity //实体 @Table(name="std_table") //重命名表 public class Student { private int id; private String name; private int age; private boolean isMale; private Calendar birthday; private Group group; private boolean hasGirlFriend; @Id //主键 @GeneratedValue //自增长 public int getId() { return id; } @Column(name="stu_name") //自定义字段名 public String getName() { return name; } @Basic //默认 public int getAge() { return age; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public boolean isMale() { return isMale; } public void setMale(boolean isMale) { this.isMale = isMale; } @Temporal(TemporalType.DATE) //只记日期 public Calendar getBirthday() { return birthday; } public void setBirthday(Calendar birthday) { this.birthday = birthday; } @Column(name="std_group") //group是sql保留字,必须改名 @Enumerated(value=EnumType.STRING) //枚举类型,value=可以省略 public Group getGroup() { return group; } public void setGroup(Group group) { this.group = group; } @Transient //忽略 public boolean isHasGirlFriend() { return hasGirlFriend; } public void setHasGirlFriend(boolean hasGirlFriend) { this.hasGirlFriend = hasGirlFriend; } }
package com.fortest.model; public enum Group { GroupA,GroupB,GroupC }
5. 联合主键
a.将主键封装成类,并实现Serializable, 重写equals hashCode方法
b. 将主键类注解为@Embeddable,并将实体类对应的get方法注解为@Id(不常用). 或:
将直接将实体类对应的get方法注解为@EmbeddedId. 或
将实体类对应的get方法注解为@IdClass(value=TablePk),或略写为@IdClass(TablePK),并将实体中所有属于主键的属性都注解为@Id
6. sessionFactory
package com.fortest.dao; import java.util.Calendar; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistryBuilder; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import com.fortest.model.Group; import com.fortest.model.Student; public class StudentDaoTest { private static SessionFactory sf = null; @BeforeClass public static void inti(){ Configuration cfg = new Configuration(); cfg.configure(); //cfg.configure("hibConfig.xml"); 不带参数的方法自动加载"hibernate.cfg.xml",重载的configure(String filename)可以加载指定配置文件 ServiceRegistry sr = new ServiceRegistryBuilder().applySettings(cfg.getProperties()).buildServiceRegistry(); sf = cfg.buildSessionFactory(sr); } @Test public void testSave(){ Student student = new Student(); student.setAge(22); student.setName("wang"); student.setMale(true); student.setBirthday(Calendar.getInstance()); student.setGroup(Group.GroupB); //获得session的两种方式: //1. 从上下文中找到session,如果没有,创建一个新的 /* * getCurrentSession()获取策略通过配置配置文件的hibernate.current_session_context_class属性指定: * 取值有 jta/thread/managed/custom * */ Session session = sf.getCurrentSession(); //2. 每次都创建一个新的session //Session session = sf.openSession(); //事务开始 session.beginTransaction(); session.save(student); Student std = (Student)session.get(Student.class, new Integer(1)); //事务结束 session.getTransaction().commit(); assertThat(std.getName(),equalTo("wang")); System.out.println(std.getName()); //使用sessioinFactory.getCurrentSession()时,当执行commit后,会自动close session,故不能写session.close(). 使用openSession时则须写 //session.close(); } @AfterClass public static void close(){ sf.close(); } }
7.JTA
a. 概念:java transation API ,java事务API. JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据
b. 两种事务: 依赖于数据库本身(单数据库) 和 JTA事务(分布式 多数据库)
8. 数据持久化的三种状态:
转换关系:
RAM | cache | database | |
Transient | ● | ○ | ○ |
Persistent | ● id | ● id | ● id |
Detached | ● id | ○ | ● id |
9. CRUD
a. get与load的区别:
i.不存在对应记录时表现不一样.
ii.load返回的是代理对象,等到真正用到对象的内容时才发出SQL语句.
iii.get直接从数据库加载,不会延时.
iv.都会首先查找缓存,如果没有,才会查找数据库.所以多次get或load同一对象时,只会发出一条sql语句.使用clean()方法可以使其重发SQL.
b. update只要保证对象的id不为空,且在数据库中有对应的ID时,就能生效.
c. 如何实现update只更新有改动的字段,使用hql
d.clean 清除session缓存
e.flush 正常情况下,只有在session.getTransation.commit()时才会与数据库同步, flush方法强制每次当对象set时,都会去同步.
10. 一对一关联
a.在get方法前加@OneToOne注解.在数据库表中默认生成的外键字段名为XX_id,若需改变,则加入注解@JoinColumn(name="xxId")
b.若为双向关联,则需在一方(getHusband)将OneToOne注解改为@(mappedBy="wife"),此意义在于对于husband和wife类来说,可以相互引用,而在数据库中的表现与一对一单向外键关联没有区别
11. 多对一关联
a. 在多的类的少的类的引用的GET方法前加@ManyToOne:
@Entity @Table(name="User_Table") public class User { private int id; private String name; private Group group; @Id @GeneratedValue public int getId() { return id; } public String getName() { return name; } @ManyToOne public Group getGroup() { return group; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setGroup(Group group) { this.group = group; } }
b. 双向关联时,注意写mappiedBy
12.一对多关联
a.在一的一方是以Set类型存储多达引用(Set是不重复的容器)
private Set<User> users = new HashSet<User>();
List (与Set差不多 多个@OrderBy)
Map @Mapkey
b.引用的GET方法中加注解@OneToMany
c.若不在一的一方加@JoinColumn(name="groupId") hibernate会生成中间表,用于存两者关系,将其当做多对多的一种特殊情况
d.双向关联时,注意写mappiedBy
13. 多对多关联
a. 加中间表
b. 要改变默认生成中间表的名字及字段名
@ManyToMany @JoinTable(name="t_s", joinColumns={@JoinColumn(name="teacher_id")}, //本model的ID inverseJoinColumns={@JoinColumn(name="student_id")} //对于model的ID )
c. 多对多双向关联注意写mappiedBy
14. 关联关系的CRUD
a.增. 要使hibernate自动存储关联关系的表,就必须在根类加Cascade
b.CascadeType的取值:
ALL Cascade all operations所有情况
MERGE Cascade merge operation合并(merge=save+update)
PERSIST Cascade persist operation存储 persist()
REFRESH Cascade refresh operation刷新
REMOVE Cascade remove operation删除
c.
@OneToMany(mappedBy="group", cascade={CascadeType.ALL}, //控制增删改(即CUD) fetch=FetchType.EAGER //控制查询(即R) EAGER值代表取出关联 LAZY值为不取关联 //多的一方fetch取值默认为LAZY 一的一方默认为EAGER )d. Cascade只会影响增删改,不会影响读取,读取由fetch指定
e. 双向不要两边设置Eager(会有多余的査询语句发出),对多方设置fetch的时候要谨慎,结合具体应用,一般用Lazy不用eager,特殊情况(多方数量不多的时候可以考虑,提高效率的时候可以考虑)
15. 继承关系映射的三种方式
a. 单表.JPA的写法:
@Entity @DiscriminatorValue("student") public class Student extends Person{}//不要再子类中写ID属性
@Entity @DiscriminatorValue("teacher") public class Teacher extends Person{}//不要再子类中写ID属性
@Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="discriminator",discriminatorType=DiscriminatorType.STRING) //指定区分字段类型 @DiscriminatorValue("person") public class Person{}
b.每个类一张表: 用table的ID生成策略
c.联合,JPA写法(子类不要写ID属性,子类表的id字段不要写自动生成)
@Entity @Inheritance(strategy=InheritanceType.JOINED) public class Person{}
16. 树状结构的关系映射
a.实体:
import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; @Entity public class Tree { private int id; private String name; private Tree parent; private Set <Tree> children = new HashSet <Tree> (); @Id @GeneratedValue public int getId() { return id; } public String getName() { return name; } @ManyToOne @JoinColumn(name="parent_id") public Tree getParent() { return parent; } @OneToMany(cascade=CascadeType.ALL, mappedBy="parent",fetch=FetchType.EAGER) public Set<Tree> getChildren() { return children; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setParent(Tree parent) { this.parent = parent; } public void setChildren(Set<Tree> children) { this.children = children; } public Tree(String name) { super(); this.name = name; } @Override public String toString() { // TODO Auto-generated method stub return this.name; } public Tree() { //要成功使用HIBERNATE读取到数据,就必须保证有不带参数的构造方法 super(); } }
b. 生成的SQL语句:
create table Tree ( id integer not null auto_increment, name varchar(255), parent_id integer, primary key (id) )
c.保存:
public void TreeSaveTest(){ Tree t = new Tree("t"); Tree t1 = new Tree("t1"); Tree t2 = new Tree("t2"); Tree t11 = new Tree("t11"); Tree t12 = new Tree("t12"); Tree t21 = new Tree("t21"); t.getChildren().add(t1); t.getChildren().add(t2); t1.getChildren().add(t11); t1.getChildren().add(t12); t2.getChildren().add(t21); t1.setParent(t); t2.setParent(t); t11.setParent(t1); t12.setParent(t1); t21.setParent(t2); Session s = sf.getCurrentSession(); s.beginTransaction(); s.save(t); s.getTransaction().commit(); }
d.读取,并用递归打印: (必须保证Tree类有不带参数的构造方法)
public void TreeLoadTest(){ TreeSaveTest(); Session s = sf.openSession(); s.beginTransaction(); Tree tt = (Tree)s.load(Tree.class, 1); s.getTransaction().commit(); printTree(tt); s.close(); } public void printTree(Tree t){ System.out.println(t); for(Tree tt : t.getChildren()){ printTree(tt); } }
17. HQL实例
a.结构:
b.类代码:
import javax.persistence.*; @Entity public class Category { private int id; private String name; @Id @GeneratedValue public int getId() { return id; } public String getName() { return name; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } }
import java.util.Date; import javax.persistence.*; @Entity public class Topic { private int id; private String title; private Category category; private Date creatDate; @Id @GeneratedValue public int getId() { return id; } public String getTitle() { return title; } @ManyToOne(fetch=FetchType.LAZY) @JoinColumn(name="category_id") public Category getCategory() { return category; } public Date getCreatDate() { return creatDate; } public void setId(int id) { this.id = id; } public void setTitle(String title) { this.title = title; } public void setCategory(Category category) { this.category = category; } public void setCreatDate(Date creatDate) { this.creatDate = creatDate; } }
import javax.persistence.*; @Entity public class Msg { private int id; private String content; private Topic topic; @Id @GeneratedValue public int getId() { return id; } public String getContent() { return content; } @ManyToOne(fetch=FetchType.LAZY) @JoinColumn(name="topic_id") public Topic getTopic() { return topic; } public void setId(int id) { this.id = id; } public void setContent(String content) { this.content = content; } public void setTopic(Topic topic) { this.topic = topic; } }
c. 持久化
public void outputTest(){ Session se = sf.getCurrentSession(); se.beginTransaction(); for(int i=1;i<11;i++){ Category c = new Category(); c.setName("category"+i); se.save(c); } for(int i=1;i<11;i++){ Category c = new Category(); c.setId(1); Topic t = new Topic(); t.setTitle("title"+i); t.setCreatDate(new Date()); t.setCategory(c); se.save(t); } for(int i=1;i<11;i++){ Topic t = new Topic(); t.setId(1); Msg message = new Msg(); message.setContent("message"+i); message.setTopic(t); se.save(message); } se.getTransaction().commit(); }
操作结果
d.用查询获得model类的List集(查询对象)
public void testHQL01(){ Session se = sf.getCurrentSession(); se.beginTransaction(); Query q = se.createQuery("from Category"); List<Category> categorys = (List<Category>) q.list(); for(Category c : categorys){ System.out.println(c.getName()); } se.getTransaction().commit(); }
e. 查询对象
public void testHQL02(){ Session s = sf.getCurrentSession(); s.beginTransaction(); Query q = s.createQuery("from Category c where c.name > 'category5'"); //Query q = s.createQuery("from Category c order by c.name desc"); //倒序排列 //Query q = s.createQuery("select distinct c from Category c"); //选择不重复的对象(主键) /*Query q = s.createQuery("from Category c where c.id>:min and c.id<:max"); q.setParameter("min",2); //还可以写作链式编程模式 q.setParameter("max",8); */ //使用占位符 /*Query q = s.createQuery("from Category c"); q.setMaxResults(4); q.setFirstResult(2); */ //分页 List<Category> c = (List<Category>)q.list(); for(Category ca : c){ System.out.println(ca.getName()); } s.getTransaction().commit(); }
f. 查询字段
public void testHQL03(){ Session s = sf.getCurrentSession(); s.beginTransaction(); Query q = s.createQuery("select c.id, c.name from Category c "); //Query q = s.createQuery("select c.name, t.name from Title t join t.category c");//Join List<Object[]> o = (List<Object[]>) q.list(); for(Object[] ob : o){ System.out.println(ob[0]+"-"+ob[1]); } s.getTransaction(); }
q.list返回的结果是一个由Object对象数组组成的List:
g. 使用临时对象接收
先编写临时接收类:
package com.hibernate.test; public class MsgInfo { private int id; private String title; private String content; private String category; public MsgInfo(int id, String title, String content, String category) { super(); this.id = id; this.title = title; this.content = content; this.category = category; } public int getId() { return id; } public String getTitle() { return title; } public String getContent() { return content; } public String getCategory() { return category; } public void setId(int id) { this.id = id; } public void setTitle(String title) { this.title = title; } public void setContent(String content) { this.content = content; } public void setCategory(String category) { this.category = category; } }
接收
public void tempObjectTest(){ Session s = sf.getCurrentSession(); s.beginTransaction(); Query q = s.createQuery("select new com.hibernate.test.MsgInfo(m.id, m.topic.title, m.content, m.topic.category.name) from Msg m"); //必须保证接收类(MsgInfo)有对应的构造方法 for(Object o : q.list()){ MsgInfo mi = (MsgInfo)o; System.out.println(mi.getId()); System.out.println(mi.getCategory()); System.out.println(mi.getTitle()); System.out.println(mi.getContent()); } s.getTransaction().commit(); }
h.取数据库日期时间
public void getCurrentTime(){ Session s = sf.openSession(); s.beginTransaction(); Query q = s.createQuery("select current_date,current_time,current_timestamp, t.id from Topic t"); System.out.println(q.list().size()); for(Object o : q.list()){ Object[] arr = (Object[])o; System.out.println(arr[0]+" "+arr[1]+" "+arr[2]); } s.getTransaction().commit(); s.close(); }
i.QBC语言和QBE语言
18. 注意session.clear()的运用
当运用hibernate在一个大集合中进行遍历,处理,需及时使用clear()方法清除缓存
19. 1+N问题
a.问题描述:一个oneToMany的对象,由于默认的fetch为eager,在欲获取此对象的list时,会自动同时发出SQL去除所有关联对象.如欲取出Topic的集合,而实际会把关联的Category全部select出来.
b.解决方案:
i.设置fetch=fetchType.LAZY,
造成的结果是不会去取对应的Category
ii.在HQL语句后加上join fetch: "from Topic t left join fetch t.category c",
造成的结果是同样会去取Category,但会通过join多张表,只生成一句SQL语句
20. Query.list() 与Query.iterator()的区别(一般只用list):
a.用List 接收会直接取出所有数据,而Iterator只会先取得id,当需要其他数据时再取
b.iterator会用缓存
21. 缓存
a. 一级缓存 session级别的缓存
b. 二级缓存 sessionFactory级别的缓存,可以跨session存在
c. 使用二级缓存
a) hibernate.cfg.xml
<property name="cache.use_second_level_cache">true</property><!-- 使用二级缓存 --> <property name="cache.provider_class">org.hibernate.cache.EhcacheProvider</property> <!-- 使用哪一种二级缓存的实现 -->
b) 引入ehcache.jar commons-logging.jar
c) 具体缓存参数在ehcache.xml中配置
d) 在经常要读取的实体类上加@Cache注解
@Entity @Cache(usage=CacheConcurrencyStrategy.READ_WRITE) public class Category{}
d. 二级缓存使用场合
a) 经常被访问
b) 不经常改动
c) 数量有限
d) 如权限 组织机构
e.查询缓存(三级缓存)
a) 查询缓存依赖于二级缓存,要使用查询缓存,必须先打开二级缓存
b) 查询缓存可以实现多次相同的查询,只会发出一条SQL语句
c) 查询缓存是跨session的
f.使用查询缓存
a) hibernate.cfg.xml
<property name=cache.use_query_cache">true</property>
b) 调用Query.setCacheable(true)
session.createQuery("from Topic t").setCacheable(true).list();
g.缓存算法
a)什么是缓存算法:缓存满后,新增的对象如何替换缓存中的对象
b) 三种缓存算法
i. LRU: least recently used 最近很少被使用
ii.LFU: least frequently used 最近使用频率很低,命中率低
iii.FIFO: first in first out 先进先出
c) 对于eccache的二级缓存实现,配置缓存算法的方法为,在其配置文件ehcache.xml中加入memoryStoreEvictionPolicy="LRU"
22. 事务的特性
ACID atomic原子性 Consistency一致性 Itegrity独立性 Durablility持久性
23. 事务并发出现的三种问题
a) 脏读: 读了另外一个事务没有提交的数据
b) 不可重复读: 对同一个数据前后读取值不一样
c) 幻读: 插入删除新数据对读取的影响
24.数据库处理三种问题的方案--数据库事务隔离机制
a)几种隔离级别
i.2read-uncommitted 可以读到未提交的数据 级别最低,效率最高 会出现 脏读 不可重复读 幻读
ii.4read-committed 只能读已提交的数据 最常用 不会出现脏读,但会出现不可重复读 幻读
iii.8repeatable-read 可重复读,读时加锁 事务执行中其他事务无法执行修改或插入操作
iv.serial 序列化读,级别最高,效率最低 不会出现此三项并发问题
b)数据库都有默认的隔离级别,mysql默认的隔离级别是repeatable-read,查看方式是
SELECT @@tx_isolation;
c)要自定义隔离级别,在hibernate配置文件中加人hibernate.connection.isolation=2,会通过JDBC设置数据库的隔离级别
25.乐观锁与悲观锁
a)为什么要用乐观锁和悲观锁:一般为提高效率,会将数据库的事务隔离级别设为read-committed,但这种情况下,可能会产生不可重复读和幻读的情况,此时在hibernate中,可以另外通过锁来解决
b)悲观锁:在提交前 将数据加锁锁定.
实现方式:session.load(Sccount.class, 12, LockMode.UPGRADE);
c)乐观锁:加入version字段,前后对比判断中间是否有其他session改变过
实现方式:在表对应的实体中新增属性int version; 在get方法上加注解@Version
当出现不可重复读或幻读时,会抛出org.hibernate.StaleObjctStateException异常