1、Hibernate是一个轻量级的ORM框架,它核心就是通过一个配置文件(ORM元数据)把O对象和R关系表之间映射起来。
——除了Hibernate之外,还有MyBatis(之前叫iBatis),前者是全自动的,配置好映射后可以脱离sql语句,后者是半自动的,配置好映射后还需要写sql语句(但实际开发中尽管使用Hibernate后仍然会用到一些sql语句)。
——Hibernate是基于JDBC的一套数据持久化框架。使用的是Java的反射机制、性能较好。
2、Hibernate入门。
——创建一个Java Project。新建一个lib文件夹,导入jar包。先去hibernate-distribution-3.6.10.Final
导入hibernate3.jar
,再去hibernate-distribution-3.6.10.Final\lib\required
里面导入所有必需的jar包,再去hibernate-distribution-3.6.10.Final\lib\jpa
导入里面的jar包。当然,别忘了导入连接mysql驱动的jar包。最后把这些jar包Add Bulid Path一下。
——然后去创建数据库和表。
——写一个实体类。
——写两个配置文件,一个配置文件是用来配置连接数据库的。另一个配置文件是我们Hibernate的核心用来声明什么类和什么数据表对应的映射关系。
——我们先来配置我们的核心映射的xml文件,这个文件命名有讲究,是实体类名.hbm.xml
,我们这里叫做User.hbm.xml
。其中generator
是主键生成策略,这里的native
是默认由数据库维护,即自增主键。
注意:
- 这里的第一行对xml文件的声明需要自己手写。
- 约束文件在导入的jar包下有一个叫做
hibernate-mapping-3.0.dtd
的文件是专门用来管理这个映射约束的,我们把约束拷贝过来即可。
- 里面剩下的主要意思就是,我们把我们的实体类和表对应起来,并且把里面的主键和各个属性也对应起来。
——剩下来的配置就是连接数据库的文件,这个在hibernate-distribution-3.6.10.Final\project\etc
里有一个叫hibernate.cfg.xml
的文件,我们直接拷贝过来放在src下面,再修改成如下的样子。
<?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>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/h01</property>
<!-- 操作数据库时会在控制台打印sql语句 -->
<property name="show_sql">true</property>
<!-- 会将sql语句换行输出,也就是格式化了 -->
<property name="format_sql">true</property>
<!-- 是否自动生成表结构,即在数据库中删除了表,这条语句也能自动创建出一个表 -->
<property name="hbm2ddl.auto">update</property>
<!-- 自动提交事务 -->
<property name="hibernate.connection.autocommit">true</property>
<!-- 最主要的,把我们刚刚配置的那个映射文件加载进来 ,只要com开始的路径-->
<mapping resource="com/hello/domain/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
——然后新建一个类开始测试使用,主要是用Configuration获取文件的配置,然后用配置获取SessionFactory,再用SessionFactory获取Session,最后用Session来操作存取实体类的数据。
package com.hello.domain;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.junit.Test;
public class HelloWorld {
@Test
public void test01(){
//获取src下的配置文件,因为我们的映射配置文件名字是规范的,所以直接获取就能拿到。其他带参数的可以通过文件名和路径拿到配置文件
Configuration cfg=new Configuration().configure();
//根据配置文件里的信息获取SessionFactory ,这个SessionFactory 相当于连接池,里面的Session相当于一个个Connection
SessionFactory sessionFactory=cfg.buildSessionFactory();
//openSession是获取全新的session对象,getCurrentSession是获得当前的session对象,所以处理事务的时候用getCurrentSession,但使用getCurrentSession需要在hibernate.cfg.xml中添加配置<property name="hibernate.current_session_context_class">thread</property>
Session session=sessionFactory.openSession();
//操作
User u=new User();
u.setUsername("eric");
u.setPassword("1234");
session.save(u);
//关闭
session.close();
sessionFactory.close();
}
}
——我们运行之后,查询数据库,发现已有数据。整个流程总结就是:先准备数据库、再导入jar包(数据库驱动、主hibernate3包、lib下必需的包、jpa里的一个包)、再新建一个与表对应的实体类、再配置2个文件(一个是核心的映射文件,第二个是连接数据库和加载映射文件的配置信息)、最后利用获取配置文件获得最终的Session来操作实体类数据。
——最后,我们说可以在控制台看到sql语句,如下。打印出日志信息的就是我们导入的slf4j-api-1.6.1.jar
,其他的jar包,javassist-3.12.0.GA.jar
字节码增强的,commons-collections-3.1.jar
是集合增强的,dom4j-1.6.1.jar
是解析xml的,hibernate-jpa-2.0-api-1.0.1.Final.jar
是jpa规范的,jta-1.1.jar
是事务处理的,antlr-2.7.6.jar
解析HQL的,hibernate3.jar
是核心jar包。
——知识点:Ctrl+1,自动生成变量。
3、在上面的代码注释里我们说过了一些配置文件的意思,下面详解一下Session对象。我们之前用Session操作了插入数据,下面操作修改。修改之前需要先取出数据,取出数据get的第1个参数是类,第二个参数是主键的值,也就是id的值。
@Test
public void test01(){
//获取配置文件,因为我们的映射配置文件名字是规范的,所以直接获取就能拿到
Configuration cfg=new Configuration().configure();
//获取sessionFactory
SessionFactory sessionFactory=cfg.buildSessionFactory();
//获取session
Session session=sessionFactory.openSession();
//打开事务
Transaction ts=session.beginTransaction();
//操作
User u=new User();
u.setUsername("eric");
u.setPassword("1234");
session.save(u);
//改:需要先查询再修改
User u1=(User) session.get(User.class, 1);
u1.setUsername("andy");
session.update(u1);
//提交事务
ts.commit();
//关闭
session.close();
sessionFactory.close();
}
——删除操作。它们本质上都是根据主键ID来操作的,也就是说下面我们不取出对应的user,直接创建一个新的User,只要给这个新User对象赋值了id,再把新的User对象传给delete,它就找到id对应的数据把它删除掉。
//删除:需要先查询再删除
User u1=(User) session.get(User.class, 1);
session.delete(u1);
——查询操作。get和load的区别:
- 程序调用get的时候Hibernate直接去数据库查询了,查询到的结果在Hibernate中被封装到实体类中如User,然后把这个对象返回给用户使用。
- 程序调用load时并没有直接查询数据库,而是在Hibernate里面使用了代理技术,创建了一个代理对象,这个代理对象属性只有ID有值,它会把这个ID属性有值的代理对象返回给程序(用户),当我们使用这个代理对象时,这个代理对象会去调用Hibernate去查询数据库,并初始化里面除了ID之外的属性。所以,如果使用load查询的话,需要在Session关闭之前使用查询,否则Session关闭后,再查询的话就取不到数据了。可以把load的设计看做是延迟加载的一个体现。
——HQL就是Hibernate Query Language。用于查询多条结果的。
//1.createQuery里面传入的就是HQL语句;2.面向对象的查询语言,所以使用的不是表名,而是类名
Query createQuery = session.createQuery("from User");
//获取查询语句里面的结果
List<User> users = createQuery.list();
//打印到控制台
System.out.println(users);
当然,它也可以一个结果,只是取值的时候语句和操作不一样,用uniqueResult
,变成如下:
Query createQuery = session.createQuery("from User where username='andy'");
User u = (User) createQuery.uniqueResult();
分页操作也就是使用limit方法,是这样操作的:
Query createQuery = session.createQuery("from User");
**createQuery.setFirstResult(0);
createQuery.setMaxResults(2)**;
List<User> users = createQuery.list();
System.out.println(users);
——createCriteria创建一个Critieria来获得数据。用的较少,它和createQuery的区别在于参数是完全没有SQL语句的影子,我们之前的createQuery至少还有一半是SQL语句的影子。
Criteria createCriteria = session.createCriteria(User.class);
List<User> users = createCriteria.list();
System.out.println(users);
既然createCriteria没有SQL语句,那么如果我们要想加一些查询条件怎么办?有单独的函数add
,当返回结果是1条的时候可以用list装结果,也可以用单独的一个实体类对象封装结果:
Criteria createCriteria = session.createCriteria(User.class);
createCriteria.add(Restrictions.eq("id", 2));
//List<User> users = createCriteria.list();
User u=(User) createCriteria.uniqueResult();
System.out.println(u);
// 模糊查询
createCriteria.add(Restrictions.like("username", "%er%"));
// id>2,还有le≤等
createCriteria.add(Restrictions.gt("id", 2));
——使用原声的SQl语句查询出来的不知道是封装到哪个实体类,所以先要告诉它封装到什么实体类里面去。
SQLQuery createSQLQuery = session.createSQLQuery("select * from t_user");
createSQLQuery.addEntity(User.class);
List<User> users = createSQLQuery.list();
System.out.println(users);
注意:
- 采用load查询返回的是一个代理对象,然后需要的时候再去查询,但是它并不是每次都去数据库查询,它只查询一次,查询后把数据库里的数据赋值给对象中的属性,它里面有一个Initialized属性,会记录是否初始化过属性,如果没有就去数据库查询,如果有,那就直接取值。
- load返回的是一个代理,但这个代理和我们之前说的动态代理不一样。动态代理是基于接口实现的,而Hibernate这里的代理是通过
javassist-3.12.0.GA.jar
产生的代理,这个代理和被代理对象之间是继承关系,它不需要接口。
4、Transaction。事务操作,就是打开、提交和回滚。没什么好说的,其中需要注意的是我们可以用getTransaction()
获得打开的事务,但是很少用。
——另一个需要注意的知识点是,如果我们提交了一个事务,那么会自动关闭与当前线程关联的session并关闭这个session。所以我们通过两个获取当前session就会得到不同的session。下面代码会返回true,但是需要在hibernate.cfg.xml中配置使用获得当前session的代码,否则会报错错误:org.hibernate.HibernateException: No CurrentSessionContext configured!
:
<property name="hibernate.current_session_context_class">thread</property>
@Test
public void test02(){
Configuration cfg=new Configuration().configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session s1=sessionFactory.getCurrentSession();
Session s2=sessionFactory.getCurrentSession();
System.out.println(s1==s2);
s1.close();
s2.close();
sessionFactory.close();
}
如果代码变成如下,就是不同的当前session了,它在提交事务的同时把之前的那个session也一并关闭了,结果是false。
@Test
public void test02(){
Configuration cfg=new Configuration().configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session s1=sessionFactory.getCurrentSession();
s1.beginTransaction().commit();
Session s2=sessionFactory.getCurrentSession();
System.out.println(s1==s2);
s1.close();
s2.close();
sessionFactory.close();
}
5、我们之前说了那么多Hibernate的操作,其实可以封装成一个工具类:
package com.hello.utils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static SessionFactory sf;
static{
Configuration cfg=new Configuration().configure();
sf=cfg.buildSessionFactory();
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
//当虚拟机关闭的时候条用这个方法,关闭sf资源
sf.close();
}
}));
}
public static Session getSession(){
return sf.openSession();
}
public static Session getCurrentSession(){
return sf.getCurrentSession();
}
}
6、hibernate.cfg.xml配置文件详解。我们写的是:
<property name="hbm2ddl.auto">update</property>
其他,它有4个值,我们可以在hibernate-distribution-3.6.10.Final\project\etc
里面的hibernate.properties
里面:
## auto schema export
#极少用。每次启动Hibernate时创建表结构,关闭Hibernate时删除表结构
#hibernate.hbm2ddl.auto create-drop
#每次都创建表结构(数据会丢失)
#hibernate.hbm2ddl.auto create
#如果不存在表,那么创建;如果存在的话判断结构是否一致,一致就不变,不一致的话保留原先的列,增加新的列
#hibernate.hbm2ddl.auto update
#不创建不维护,只校验表结构,如果不一致就报错
#hibernate.hbm2ddl.auto validate
7、数据库方言配置,同样在hibernate.properties
里面有如下的信息可供我们选择,我们一般选择第一种。
## MySQL
#Key Value
#hibernate.dialect org.hibernate.dialect.MySQLDialect
#hibernate.dialect org.hibernate.dialect.MySQLInnoDBDialect
#hibernate.dialect org.hibernate.dialect.MySQLMyISAMDialect
在hibernate.cfg.xml
里配置:
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
8、小知识点。
——在JavaBean中即我们的User中,尽量使用基本数据类型的包装类,因为表示的意义较多,比如null表示不存在,0表示0值,而如果采用基本数据类型的话,null和0都是0。
9、另一个配置文件详解,User.hbm.xml,对象-表关系映射文件。
——package属性。原先class标签里面的name是com.hello.domain.User全路径,现在设置了package后,只需要写User即可。
<hibernate-mapping package="com.hello.domain">
<class name="User" table="t_user">
……
</class>
</hibernate-mapping>
——动态插入和更新。可以适当提高效率,原因在于默认情况下如果我们插入一个对象的时候只给部分属性赋值的话它还是会调出所有属性来,但是我们把动态插入和更新设置true后,它只会调用涉及到的属性。插入和更新是一样的。
<class name="User" table="t_user" dynamic-insert="true" dynamic-update="true">
——length是指定字段的显示长度;unsaved-value指定当值为多少时Hibernate把它当做null,这个很少用,因为我们建议使用包装类,它天然就区分了null和0值,没有必要通过unsaved-value="0"
来告诉Hibernate当遇到0值的时候其实是null;access极少用,当取值为field的时候,它会直接操作字段,而不通过set/get。
——主键生成策略。我们一般选择native,然后如果是MySQL的话会自动选择identity,如果是Oracle的话会自动选择sequence。
——精度和位数。有时候我们的值是double类型的,要控制精度,其中scale是总位数,precision是小数点后的位数,也就是最多能存9.99
。
<property name="salary" column="salary" scale="3" precision="2"></property>
property的其他属性:
——type属性。表示该属性的类型。