Hibernate入门之HelloWorld篇
http://hi.baidu.com/gefforey520/item/ad420ed7544f6d96270ae71c
1,我的理解
hibernate只是用来对数据库连接处理的一个中间件,是对jdbc的一个轻量级封装。
hibernate是一种ORM框架,就是面向对象与关系型数据的一个协调框架,让你使用面向对象的思想来访问和操作关系型数据库。hibernate编程需要这样的几个步骤,首先在数据库里要生成一个表tbl;其次要在eclipse下编写一个对应于tbl的类,这种类我们称之为POJO类;其三我们需要在这两者之间架起一个桥梁,也就是用abc.hbm.xml这样的配置文件来联系二者;最后还需要一个hibernate.cfg.xml文件来配置如何连接mysql或其他数据库以及相关的abc.hbm.xml的存放位置。
可以试着用一些像MiddleGen,xDoclet等之类的生成工具,不过我还是偏向手工编写,而且用Hibernate-Tools工具也很方便手写。
2,整个文件夹结构
这里作一些说明:
src文件夹下除了hibernate.cfg.xml这个配置文件外,其他都是文件夹,这里分别进行说明:
1,hibernate.cfg.xml:配置Java如何链接mysql数据库和xxx.hbm.xml存放位置的配置文件。
2,config文件夹 :放置各个类与表的对应关系的配置文件,因为这里只有PersonInfo一个类和personinfo一个表相对应,所以这里只有一个PersonInfo.hbm.xml配置文件,这个文件会被hibernate.cfg.xml引用。
3,dao文件夹 :这里放置关于DAO的类,DAO的作用是抽象出对使用OO方式对关系型数据库的操作,当然实际上也不是必须的。
比如后面的测试文件Test就演示了如何不用DAO访问数据库。而PersonInfoDaoTest则演示了使用DAO。
其中两个Generic文件我也不知道源自哪里。后面的PersonInfoDao则是专门对PersonInfo类的操作的封装。
4,hibernateSampleTest文件夹:这里放了两个测试类,PersonInfoDaoTest演示了使用DAO方式访问数据库。Test则演示了不用DAO方式访问数据库。
5,pojo文件夹 :pojo中每个文件都对应数据库一个表。比如这边的PersonInfo类是personinfo表的对应类。
另外需要主要还要引用一大堆jar包。
比如;
hibernate3.jar
antlr-2.7.6.jar
commons-collections-3.1.jar
dom4j-1.6.1.jar
javassist-3.9.0.GA.jar
jta-1.1.jar
mysql-connector-java-5.1.7-bin.jar//使用的是mysql数据库s
slf4j-nop-1.6.1.jar//注意slf4j-nop-1.6.1.jar和slf4j-api-1.6.1.jar需要版本相同,否则出现可能不兼容的情况。
slf4j-api-1.6.1.jar
3,创建数据库
在MySql下创建数据库sample,创建表personinfo,主键为INTEGER型pid,属性列有:varchar型pname,INTEGER型age。其sql文件如下:
CREATE TABLE personinfo( pid INTEGER NOT NULL auto_increment PRIMARY KEY, pname VARCHAR(50) NOT NULL, age INTEGER NOT NULL );
mysql> desc personinfo; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | pid | int(11) | NO | PRI | NULL | auto_increment | | pname | varchar(50) | NO | | NULL | | | age | int(11) | NO | | NULL | | +-------+-------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec)
4,创建POJO类
PersonInfo.java
package pojo; public class PersonInfo { private Integer pid; private String pname; private Integer age; public PersonInfo() { super(); } public Integer getPid() { return pid; } public void setPid(Integer pid) { this.pid = pid; } public String getPname() { return pname; } public void setPname(String pname) { this.pname = pname; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "PersonInfo [pid=" + pid + ", pname=" + pname + ", age=" + age + "]"; } }
5,创建abc.hbm.xml文件来联系二者
PersonInfo.hbm.xml
<?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="pojo"> <class name="PersonInfo" table="personinfo"> <id name="pid" column="pid" type="java.lang.Integer"> <generator class="native"/> </id> <property name="pname" column="pname" type="java.lang.String" /> <property name="age" column="age" type="java.lang.Integer" /> </class> </hibernate-mapping>
6,创建hibernate.cfg.xml来配置数据库连接等信息
需要放在src目录下。
hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory > <property name="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.password">xxx</property> <property name="hibernate.connection.url">jdbc:mysql://10.13.xx.79:3306/sample</property> <property name="hibernate.connection.username">xxx</property> <property name="hibernate.current_session_context_class">thread</property> <property name="hibernate.default_entity_mode">pojo</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.search.autoregister_listeners">false</property> <property name="hibernate.show_sql">true</property> <property name="hibernate.default_catalog">sample</property> <!--<property name="hibernate.hbm2ddl.auto">create</property>--> <mapping resource="config/PersonInfo.hbm.xml"/> </session-factory> </hibernate-configuration>
注意其中的<property name="hibernate.hbm2ddl.auto">create</property>表示,如果数据库里没有表的话会自动创建,如果存在,则覆盖之。
7,创建DAO类
GenericDao.java
package dao; import java.io.Serializable; import java.util.Collection; import java.util.List; public abstract interface GenericDao<T, PK extends Serializable> { // public abstract List<T> getAll(); // public abstract List<T> getAllDistinct(); public abstract T findById(PK paramPK); // public abstract boolean exists(PK paramPK); public abstract T save(T paramT); public abstract void saveAllT(Collection<T> paramCollection); public abstract void saveAll(Collection<? extends T> paramCollectio); public abstract void remove(PK paramPK); public abstract void removeByHql(String hql); public abstract void updateByHql(String hql); public abstract List<T> findByHql(String hql); /** * * @Title: findByVersion * @Description: TODO 搜索版本下所有内容 * @return * @return List<T> * @throws */ // public abstract List<T> findByVersion(String hql); public void saveOrUpdateAll(Collection<T> entities); void removeAll(Collection<T> entities); }
GenericDaoHibernate.java
package dao; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collection; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.criterion.Expression; public class GenericDaoHibernate<T, PK extends Serializable> implements GenericDao<T, PK> { // 是否插入版号ID protected Method versionIDMethod; // 是否插入项目ID protected Method projectIDMethod; protected static Log log = LogFactory.getLog(GenericDaoHibernate.class); // protected final Log log = LogFactory.getLog(super.getClass()); private Class<T> persistentClass; // private HibernateTemplate hibernateTemplate; private final SessionFactory sessionFactory = getSessionFactory(); protected List<T> objects; public GenericDaoHibernate(Class<T> persistentClass) { this.persistentClass = persistentClass; } /* * public HibernateTemplate getHibernateTemplate() { return * this.hibernateTemplate; } */ protected SessionFactory getSessionFactory() { try { return new Configuration().configure().buildSessionFactory(); } catch (Exception e) { e.printStackTrace(); log.error(e.getMessage()); throw new IllegalStateException( "Could not locate SessionFactory in JNDI"); } } public T save(T object) { log.debug("persisting Project instance"); try { sessionFactory.getCurrentSession().beginTransaction(); sessionFactory.getCurrentSession().persist(object); sessionFactory.getCurrentSession().getTransaction().commit(); log.debug("persist successful"); return object; } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); log.error("persist failed", re); throw re; } } public void saveAllT(Collection<T> entities) { this.saveAll(entities); } public List<T> findByHql(String hql) { log.debug("finding Project instance by example"); try { sessionFactory.getCurrentSession().beginTransaction(); List<T> results = (List<T>) sessionFactory.getCurrentSession() .createCriteria(persistentClass).add(Expression.sql(hql)) .list(); log.debug("find by example successful, result size: " + results.size()); sessionFactory.getCurrentSession().getTransaction().commit(); return results; } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); log.error("find by example failed", re); throw re; } } public T findById(PK id) { log.debug("getting Project instance with id: " + id); try { sessionFactory.getCurrentSession().beginTransaction(); T instance = (T) sessionFactory.getCurrentSession().get( persistentClass, id); sessionFactory.getCurrentSession().getTransaction().commit(); if (instance == null) { log.debug("get successful, no instance found"); } else { log.debug("get successful, instance found"); } return instance; } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); log.error("get failed", re); throw re; } } public List<T> findBySql(String sql) { try { sessionFactory.getCurrentSession().beginTransaction(); List<T> results = (List<T>) sessionFactory.getCurrentSession() .createSQLQuery(sql).addEntity("s", persistentClass).list(); sessionFactory.getCurrentSession().getTransaction().commit(); return results; } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); log.error("find by example failed", re); throw re; } } @Override public void remove(PK paramPK) { try { sessionFactory.getCurrentSession().beginTransaction(); T instance = (T) sessionFactory.getCurrentSession().get( persistentClass, paramPK); sessionFactory.getCurrentSession().delete(instance); sessionFactory.getCurrentSession().getTransaction().commit(); } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); log.error("get failed", re); throw re; } } @Override public void removeAll(Collection<T> entities) { log.debug("remove entities"); try { sessionFactory.getCurrentSession().beginTransaction(); for (T t : entities) { // plusVersion(t); sessionFactory.getCurrentSession().delete(t); } sessionFactory.getCurrentSession().getTransaction().commit(); log.debug("remove successful"); } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); throw re; } } @Override public void removeByHql(String hql) { try { sessionFactory.getCurrentSession().beginTransaction(); sessionFactory.getCurrentSession().createQuery(hql).executeUpdate(); sessionFactory.getCurrentSession().getTransaction().commit(); } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); log.error("get failed", re); throw re; } } @Override public void saveOrUpdateAll(Collection<T> entities) { log.debug("updateOrSave Project instance"); try { sessionFactory.getCurrentSession().beginTransaction(); for (T t : entities) { sessionFactory.getCurrentSession().saveOrUpdate(t); } sessionFactory.getCurrentSession().getTransaction().commit(); log.debug("updateOrSave successful"); } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); throw re; } } @Override public void updateByHql(String hql) { try { sessionFactory.getCurrentSession().beginTransaction(); sessionFactory.getCurrentSession().createQuery(hql).executeUpdate(); sessionFactory.getCurrentSession().getTransaction().commit(); } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); log.error("get failed", re); throw re; } } @Override public void saveAll(Collection<? extends T> entities) { log.debug("persisting Project instance"); // try { // 每N条提交一次,以减少内存压力 int n = 10000; int ttl = entities.size(); int runNumber = (int) Math.ceil(Float.valueOf(entities.size()) / Float.valueOf(n)); T[] ts = (T[]) entities.toArray(); for (int r = 0; r < runNumber; r++) { sessionFactory.getCurrentSession().beginTransaction(); try { for (int i = 0; i < n; i++) { if (r * n + i >= ttl) break; sessionFactory.getCurrentSession().persist( (T) (ts[r * n + i])); } sessionFactory.getCurrentSession().getTransaction().commit(); } catch (RuntimeException re) { sessionFactory.getCurrentSession().getTransaction().rollback(); throw re; } } log.debug("persist successful"); } }
PersonInfoDao.java
package dao; import pojo.PersonInfo; public class PersonInfoDao { public static GenericDaoHibernate<PersonInfo, String> personInfoDao = new GenericDaoHibernate<PersonInfo, String>( PersonInfo.class); public static void save(PersonInfo data) { personInfoDao.save(data); } }
8,创建测试类
Test.java
package hibernateSampleTest; import org.hibernate.*; import org.hibernate.cfg.*; public class Test { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub try { SessionFactory sf = new Configuration().configure().buildSessionFactory(); Session session = sf.openSession(); Transaction transaction = session.beginTransaction(); PersonInfo personInfo = new PersonInfo(); personInfo.setPid(9); personInfo.setPname("dandan"); personInfo.setAge(20); session.save(personInfo); session.flush(); transaction.commit(); session.close(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
或者使用JUnit
【注】
hibernate的session.createQuery()方法查不到数据的解决方法
在Hibernate配置文件中加入下面代码:
<property name="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</property>
在Hibernate的配置文件 中,hibernate.query.factory_class属性用来选择查询翻译器。例如:
(1)选择Hibernate3.0的查询翻译 器:
hibernate.query.factory_class= org.hibernate.hql.ast.ASTQueryTranslatorFactory
(2)选择Hibernate2.1的查询翻 译器
hibernate.query.factory_class= org.hibernate.hql.classic.ClassicQueryTranslatorFactory
//不通过DAO直接访问数据 package hibernateSampleTest; import pojo.PersonInfo; import java.util.Iterator; import java.util.List; import junit.framework.Assert; import junit.framework.TestCase; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.Query; import com.aliyun.odps.pl.parser.DEPLParser.printStatement_return; public class Test extends TestCase { Session session = null; protected void setUp() { try { Configuration config = new Configuration().configure(); SessionFactory sessionFactory = config.buildSessionFactory(); session = sessionFactory.openSession(); } catch (HibernateException e) { e.printStackTrace(); } } protected void tearDown() { try { session.close(); } catch (HibernateException e) { e.printStackTrace(); } } /* * //测试insert //下面这段代码相当于我们执行了sql语句:insert into personinfo * values(1,'xiaobao',20); public void testInsert() { Transaction * transaction = null; try { transaction = session.beginTransaction(); * PersonInfo personInfo = new PersonInfo(); personInfo.setAge(20); * personInfo.setPname("dida"); personInfo.setPid(1); * session.save(personInfo); session.flush(); transaction.commit(); * Assert.assertEquals(personInfo.getPid().intValue() > 0, true); } catch * (HibernateException e) { e.printStackTrace(); * Assert.fail(e.getMessage()); if (transaction != null) { try { * transaction.rollback(); } catch (HibernateException ee) { * ee.printStackTrace(); } } } } */ // 测试select /* * hibernate的常用接口, Query和Criteria: * Query是执行数据库查询,Query接口包装了一个HQL语句,HQL语句是面向对象的,它引用类名和属性名,而不是表名和字段名。 * Criteria完全封装了基于字符串形式的查询语句,比Query接口更加面向对象,擅长动态查询。 */ public void testSelect() { String hql = "from PersonInfo'"; // hql注意from后面是对象,而不是表名。使用方法session.createQuery(hql) String sql = "select * from personinfo";// sql注意from后面是表名,而不是对象。使用方法session.createSQLQuery(sql) Transaction transaction = null; try { /* transaction = session.beginTransaction(); Query mySqlQuery = session.createSQLQuery(sql).addEntity(PersonInfo.class); List<PersonInfo> list = mySqlQuery.list(); Iterator iterator = list.iterator(); System.out.println(list.size()); while (iterator.hasNext()) { System.out.println(((PersonInfo) iterator.next()).toString()); } */ // 刚开始一直用hql方法查不到数据,原因是需要在hibernate.cfg.xml文件中加入一行: // <property // name="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</property> transaction = session.beginTransaction(); Query mySqlQuery = session.createQuery(hql); List<PersonInfo> list = mySqlQuery.list(); Iterator iterator = list.iterator(); System.out.println(list.size()); while (iterator.hasNext()) { System.out.println(((PersonInfo) iterator.next()).toString()); } } catch (HibernateException e) { e.printStackTrace(); Assert.fail(e.getMessage()); } } }
PersonInfoDaoTest.java
//使用DAO方式来访问数据 package hibernateSampleTest; import java.util.Iterator; import java.util.List; import org.junit.Test; import dao.PersonInfoDao; import pojo.PersonInfo; public class PersonInfoDaoTest { @Test public void testInsert() { PersonInfo p = new PersonInfo(); p.setPname("jiji"); p.setAge(19); PersonInfoDao.personInfoDao.save(p); } public void testSelect() { String sql = "select * from personinfo"; List<PersonInfo>list = PersonInfoDao.personInfoDao.findBySql(sql); Iterator<PersonInfo> iterator = list.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } } public static void main(String args[]) { System.out.println("start test"); PersonInfoDaoTest a = new PersonInfoDaoTest(); a.testSelect(); System.out.println("end test"); } }
Hibernate原理解析
1.hibernate的常用接口
(1) Configucation :负责加载Hibernate的配置及映射信息,启动Hibernate,根据连接到数据库的信息来创建SessionFactory 对象。
(2) SessionFactory :初始化Hibernate,创建Session对象。他是线程安全的,通常采用单子模式创建此对象。
(3)Session :负责保存·修改·删除·查询·加载对象,这个session不是线程安全的,使用Threadlocal来得到session(相当于创建了一个副本)。
(4)Transaction :事务处理 开启事务的方法 Transaction tx= session.beginTransaction(); 提交事务 tx.commit();
(5) Query和Criteria :Query是执行数据库查询,Query接口包装了一个HQL语句,HQL语句是面向对象的,它引用类名和属性名,而不是表名和字段名。Criteria完全封装了基于字符串形式的查询语句,比Query接口更加面向对象,擅长动态查询。
2.hibernate的工作原理
2.1.Hibernate是如何连接数据库
主要是通过hibernate.cfg.xml配置文件中的配置。在这个文件中定义了数据库进行连接所需要的信息,包括JDBC驱动、用户名、密码、数据库方言等,configuration类借助dom4j的XML解析器解析设置环境,然后使用这些环境属性来生成 SessionFactory。这样这个sessionFactory生成的session就能成功获得数据库的连接。
2.2.Hibernate是如何进行数据库写操作
对数据库的写操作包括保存、更新和删除,当保存一个POJO持久对象时,触发Hibernate的保存事件监听器进行处理。Hibernate通过映射文件获得对象对应数据库表名以及属性所对应的表中的列名,然后通过反射机制持久化对象(实体对象)的各个属性,最终组织成向数据库插入新对象的SQL insert语句。调用了session.save()方法后,这个对象会标识成持久化状态存放在session中,对于Hibernate来说它就是一个持久化了的对象,但这个时候Hibernate还不会真正的执行insert语句,当进行session的刷新同部或事务提交时,Hibernate会把session缓存中的所有SQL语句一起执行,对于更新、删除操作也是采用类似的机制。
然后,提交事务并事务提交成功后,这些写操作就会被永久地保存进数据库中,所以,使用session对数据库操作还依赖于Hibernate事务的处理。如果设置了二级缓存,那么这些操作会被同步到二级缓存中,Hibernate对数据库最终操作也是依赖于底层JDBC对数据库进行。
2.3.Hibernate 如何从数据库中载入对象
当使用session.load()载入对象时,可以设置是否采用延迟加载,如果延迟加载,那么load返回的对象实际是CGLIB或javassist返回的代理类,它的非主键属性都是空的,这对于对象集合属性很有效。 Hibernate以此来节约内存,当真正需要读取对象时,Hibernate会先尝试从session缓存中读取,如果session缓存中数据不存在或者是脏数据并且配置了二级缓存,Hibernate尝试从二级缓存中检索数据,否则Hibernate会根据对象类型,主键等信息组织select语句到数据中读取,再把select结果组织成对象返回。
2.4.Hibernate如何进行数据库查询操作
Hibernate提供SQL HQL Criteria查询方式。HQL是其中运用最广泛的查询方式。用户使用session.createQuery()方法以一条HQL语句为参数创建 Query查询对象后,Hibernate会使用Anltr库把HQL语句解析成JDBC可以识别的SQL语句,如果设置了查询缓存,那么执行 Query.list()时,Hibernate会先对查询缓存进行查询,如果查询缓存不存在,再使用select语句查询数据库。
Hibernate上手指南
http://www.blogjava.net/balajinima/articles/146910.html
一、Why Hibernate?
要说Hibernate,就得先介绍一下Object/Relation Mapper(ORM),中文翻译为对象关系映射。之所以会产生这样的概念是源于目前软件开发中的一些不协调的思想。目前流行的编程模型是OOP(Object Oriented
Programming),面向对象的编程,而目前流行的数据库模型是Relational Database,这两者思考的方式不一样,这必然产生了开发过程中的不协调。ORM框架(也称为持久层框架)的出现就是为了解决这样的问题,屏蔽底层数据库的操作,以面向对象的方式提供给开发者操作数据库中数据的接口。目前流行的ORM框架有Apach
OJB,Hibernate,iBatis等等,当然最完善,最好用的是Hibernate,至少我这样认为。或许你对“持久层”感到迷惑,其实说白了很简单,把数据放到数据库中叫做持久化(内存种的数据当然不是持久的),那么负责这一操作的结构层面就叫做持久层。你以前应该听说过表现层,业务层,数据层,那么持久层是在业务层和数据层之间的一层,或者说持久层是数据层的一部分。
接下来,我想通过一个实际开发中的例子来说明ORM带给我们的好处。先来讲一下我们的需求,数据库中有三张表,一张student,一张course,另外一张course_slection。其中student用来保存学生信息,course用来表示课程信息,course_selection用来表示学生的选课信息。(表的详细结构这里我就省略了,因为这并不重要)现在要求编写一个程序,用来选出指定学号学生所选的课程名字,那么可能会出现以下几种程序编写的方式:
1. 菜鸟级
代码片段1:
public List selectCourses(String studentId) { Connection con = null; Statement sta = null; try { Class.forName("oracle.jdbc.driver.OracleDriver"); con = DriverManager.getConnection( "jdbc:oracle:thin:@10.85.33.199:1521:glee", "test", "test"); String sql = "select * from course_selection"; String sql2 = "select name from course where id='"; sta = con.createStatement(); ResultSet rs = sta.executeQuery(sql); List list = new LinkedList(); while (rs.next()) { ResultSet rs2 = sta.executeQuery(sql2 + rs.getString("course_id") + "'"); if (rs2.next()) { list.add(rs2.getString("name")); } } return list; } catch (Exception e) { e.printStackTrace(); } return null; }
这段程序你一定看的很晕吧,什么乱七八糟的都搞在一起,那么接下来看一段改进过的程序。
2. 改进后的代码
代码片段2:
class DBHelper { public static Connection getConnection() { try { Class.forName(Constants.DB_DRIVER); return DriverManager.getConnection(Constants.DB_URL, Constants.DB_USER, Constants.DB_PWD); } catch (Exception e) { e.printStackTrace(); } return null; } } public List selectCourses(String studentId) { Connection con = null; Statement sta = null; try { con = DBHelper.getConnection(); String sql = "select * from course_selection"; String sql2 = "select name from course where id='"; sta = con.createStatement(); ResultSet rs = sta.executeQuery(sql); List list = new LinkedList(); while (rs.next()) { ResultSet rs2 = sta.executeQuery(sql2 + rs.getString("course_id") + "'"); if (rs2.next()) { list.add(rs2.getString("name")); } } return list; } catch (Exception e) { e.printStackTrace(); } return null; }
这段代码的形式是一种被广泛采用的形式,相对第一段代码来说,应该已经有所进步,分离了数据库连接操作,并把数据库连接信息交给单独的类完成(一般放在配置文件里面),往往在开发中还会引入数据库连接池(Connection Pool)来提高性能,我这里都尽量简化了。但这些并不能从根本上改善程序的结构,在业务代码中仍然混杂了很多数据库操作,结构不清晰。下面来看一段彻底分离数据库操作的代码:
3. DAO模式
代码片段3:
public List selectCourses(String studentId) { StudentDAO sd = new StudentDAO(); Student student = sd.findById(studentId); Set set = student.getCourseSelections(); List courseNames = new LinkedList(); for (Iterator iter = set.iterator(); iter.hasNext();) { CourseSelection element = (CourseSelection) iter.next(); courseNames.add(element.getCourse()。getName()); } return courseNames; }
是不是感觉代码少了很多?或许你对这段代码有点迷惑,没关系,后文会详细解释。我想先解释一下DAO。其实DAO和Hibernate没有必然联系,只不过一般用Hibernate的程序都用DAO模式。DAO的全称是Data Access Object,程序要访问数据库中的数据(包括获取,更新,删除)都通过DAO来访问,实际上DAO才是真正屏蔽了所有数据库操作的东西,这样在业务代码中就可以完全隔离数据层的代码。如果我告诉你,在真正用Hibernate开发的时候,要完成上文提到的功能,需要手写的代码就是“代码片段3”这么多,甚至更少,你是不是有很大的动力去学习Hibernate?那么好吧,让我们开始Hibernate之旅。
二、持久层的组成
这一节的名字应该换成“基于Hibernate的持久层的组成”更合适一点,可是它太长了。既然Hibernate是用来开发持久层,那么我先介绍一下这个持久层中的各个元素。
1. POJO:Plain Old Java Object,你可以把它看作是简单的JavaBean。一般说来,一张数据库表对应一个POJO,也就是对象/关系的一一映射。【我的理解是对应于数据库中一张表的一个类】
2. DAO:对于每一个POJO,一般都有一个DAO与之对应,承担所有关于该POJO的访问控制。实际上也就是控制了对数据库中一张表的访问控制。【我的理解是对POJO这种类的访问操作类】
3. *.hbm.xml文件:【这个文件定义了POJO和数据库中的表是如何映射的】,比如POJO中的字段对应数据库表中的哪个字段等等。一般每个映射都用单独的文件来描述,也就是有一个POJO就有一个*.hbm.xml文件。
4. *.cfg.xml文件:这个文件定义了Hibernate的基本信息,比如数据库驱动,用户名,密码等等连接信息,也包括了所有要用的*.hbm.xml文件,在初始化的时候,Hibernate会读取这个文件来找相应的映射文件完成对象/关系。
我们还是以上文的例子来详细描述一下这里提到的各个元素的内容。
1. Student.java【POJO类示例】:
代码片段4:
public class Student implements java.io.Serializable { private String id; private String name; private Set courseSelections = new HashSet(0); public Student() { } public String getId() { return this.id; } public void setId(String id) { this.id = id; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Set getCourseSelections() { return this.courseSelections; } public void setCourseSelections(Set courseSelections) { this.courseSelections = courseSelections; } }
这个类就是一个POJO,你可以很明显的看出来它就是一个JavaBean。我想解释它的courseSelection字段。很显然,在数据库表student中,没有这个字段。这里的这个字段是因为一个外键引用,course_selection的student_id是一个外键,引用了student表中的id字段。那么在Student类中courseSelection来记录这样的外键关系,也就是说,当我们获取了Student对象以后,就可以直接获取他的选课记录,这样就为上层的调用提供了很大的方便。这里有点模糊没关系,我在介绍映射定义文件(*.hbm.xml)的时候还会提到这个问题。
2. StudentDAO.java【对应的DAO类】
代码片段5:
public class StudentDAO { Session session; public StudentDAO() { Configuration cfg = new Configuration(); cfg.configure("/hibernate.cfg.xml"); SessionFactory sessionFactory = cfg.buildSessionFactory(); session = sessionFactory.openSession(); } public void save(Student transientInstance) { session.save(transientInstance); } public void delete(Student persistentInstance) { session.delete(persistentInstance); } public Student findById(java.lang.String id) { List list = session.createCriteria(Student.class).add( Expression.eq("id", id)).list(); if (list.size() > 0) { return (Student)list.get(0); } return null; } }
这里的构造函数是用来启动Hibernate,并获取session。打开一个session就相当于打开了一个数据库连接,然后我们就可以对这个session进行操作,完成数据库操作,完全不用写SQL语句。我这里Hibernate的启动方式写的很不规范,系统应该只需要完成一次Hibernate启动就可以在不同的DAO中使用,我把它写在构造函数里面纯粹是为了简化演示代码。
你可以看到save和delete方法都很简单直接对对象操作,而findById就有些麻烦,因为这里有一个查询过程在里面。Hibernate里面查询可以用Criteria这个类来完成,我们也常用Hibernate独有的HQL(Hibernate Query Language)来完成查询。当然Hibernate也是支持原生SQL的。关于查询的详细信息请参考其他文章或书籍,我只是演示一个流程,介绍一些概念。
3. Student.hbm.xml【Java的POJO类与数据库的table如何对应】
代码片段6:
<hibernate-mapping> <class name="Student" table="STUDENT"> <id name="id" type="string"> <column name="ID" length="10" /> <generator class="assigned" /> </id> <property name="name" type="string"> <column name="NAME" not-null="true" /> </property> <set name="courseSelections" inverse="true"> <key> <column name="STUDENT_ID" length="10" not-null="true" /> </key> <one-to-many class="CourseSelection" /> </set> </class> </hibernate-mapping>
这个文件定义了Student类和Student表是如何映射的。
class :class元素定义了Sudent类和STUDENT表映射,然后就定义了各个属性是如何映射的。
id :如果一个属性是数据库的key,那么会用id标签来定义。
column :column定义了当前类的属性和数据库中的哪个字段对应,
generator :generator是id特有的。一般来说id是自增的,由于我的数据库是用的Oracle,它没有自增字段,要实现自增必须用Sequence,这超出了本文的范围,所以我就用assigned来简化示例代码。assigned表示id是用户给定的。
set :有一个比较特别的标签是set,它对应着数据库中的外键关系,上文我提到的通过Student对象可以获得所有相关的选课记录就是通过这里的定义实现的。name属性对应了Student类中的字段名,key表示哪个字段是外键,one-to-many表示Student和CourseSelection是一对多关系,这和事实相符。类似的还有many-to-one,many-to-many,不过这些都不常用,我不介绍了。Hibernate根据这个映射定义文件,在实例化一个POJO(比如Student)的时候,会自动的把定义过映射的属性用数据库中的数据填充,set也包括在内。
4. hibernate.cfg.xml
代码片段7:
<hibernate-configuration> <session-factory> <property name="connection.username">test</property> <property name="connection.url"> jdbc:oracle:thin:@10.85.33.199:1521:glee</property> <property name="dialect"> org.hibernate.dialect.Oracle9Dialect</property> <property name="connection.password">test</property> <property name="connection.driver_class"> oracle.jdbc.OracleDriver</property> <mapping resource="Student.hbm.xml"></mapping> <mapping resource="CourseSelection.hbm.xml"></mapping> <mapping resource="Course.hbm.xml"></mapping> </session-factory> </hibernate-configuration>
这个文件我不解释了,自己看吧。结合上文StudentDAO的例子,我想你应该能看明白。
看了这么多,或许你会有点头皮发麻,POJO,DAO,配置文件...好像要写的东西还是很多。值得庆幸的是现在Hibernate已经发展的比较成熟了,有很多工具来帮助我们完成这些工作,比如MiddleGen,Hibernate Synchronizer等等。我使用的开发工具是Eclipse+MyEclipse,我所要做的只是把数据库表建好,然后MyEclipse提供的工具会自动根据数据库表生成POJO,DAO,*.hbm.xml,甚至hibernate.cfg.xml都是自动完成的(前提是MyEclipse知道你的数据库连接信息)。我并不打算介绍如何用IDE来开发Hibernate,你可以参考IDE的帮助文档。
到这里为止,使用Hibernate进行开发的基本组成元素我都介绍好了,强烈建议你马上实践一遍,即使有些不理解,也先依葫芦画瓢一个。对了,别忘了把Hibernate的包down下来放到classpath里面。
三、Session与SessionFactory
Session可以说是Hibernate的核心,Hibernate对外暴露的接口就是Session。所以我这里讲一下有关Session的常用函数和特性。
在讲Session之前,我想先提一下SessionFactory,这个东西不复杂,只要配置好就行了。顾名思义,SessionFactory就是用来创建Session的。SessionFactory是线程安全的,也就是说对于同一个数据库的所有操作共享一个SessionFactory就行了。回头看代码片段5,我们可以看到SessionFactory的常用配置方式。
代码片段8:
Configuration cfg = new Configuration(); cfg.configure("/hibernate.cfg.xml"); SessionFactory sessionFactory = cfg.buildSessionFactory();
我们通过Configuration来读取配置文件,然后就可以创建SessionFactory,这段代码在 所有系统中都大同小异,一般就是xml配置文件的名字不一样,所以也没什么好说的。
当我们有了SessionFactory以后就可以获取Session了。调用SessionFactory.openSession()就会返回一个Session实例,然后我们操作这个Session来访问数据库。值得一提的是Session并不是线程安全的,也就是每一个线程都必须有自己的Session。所以我们一般通过以下方法来获取和关闭Session:
代码片段9:
public static Session currentSession() throws HibernateException { Session session = (Session) threadLocal.get(); if (session == null || !session.isOpen()) { if (sessionFactory == null) { try { cfg.configure(CONFIG_FILE_LOCATION); sessionFactory = cfg.buildSessionFactory(); } catch (Exception e) { e.printStackTrace(); } } session = (sessionFactory != null) ? sessionFactory.openSession(): null; threadLocal.set(session); } return session; } public static void closeSession() throws HibernateException { Session session = (Session) threadLocal.get(); threadLocal.set(null); if (session != null) { session.close(); } }
可以看到,我们通过threadLocal来保存每个线程的session,这样就保证了各个线程之 间的互不干扰,也保证了系统只有一个SessionFactory实例(对于大多数应用来说已经 足够了)。如果你使用MyEclipse进行开发的话,它会自动生成一个 HibernateSessionFactory.java,其中就包含了以上代码。
好了,现在我们已经获得了Session,下面我来介绍以下Session的常用函数,这些函数都有很多重载函数,我只介绍以下大概是干嘛的,不一一解释,详细信息你可以查看Hibernate的API。
1.Session.get(),获取某个类的实例,一般都是通过id来获取比如
Session.get(Student.class, "0361095");
这句话的意思就是获取id(primary key)为“0361095”的Student对象。这里要注 意的是第二个参数必须是Object,也就是说,如果是long类型的1,那么必须转换 成new Long(1)再传入。
2.Session.load(),用法和意义都和get一样,不过它们还是有点区别,我稍后解释。
3.Session.save(),将某个实例保存到数据库中去(往往在数据库中形成一条新的记录)。
4.Session.update(),更新某个实例,这个实例必须和数据库中有对应,否则会报错。
5.Session.delete(),删除某个实例,也就是删除这个实例对应的数据表中的数据。
6.Session.saveOrUpdate(),保存或者更新某个实例,调用这个方法你就不用去关心到 底是save还是update了,它会自己判断,然后调用相应的函数。其实save和update 涉及到实体对象生命周期中的三种状态,这个比较重要,我在后面会单独讲的。
对于get和load的区别,很难讲清楚,这里涉及到Hibernate的缓存机制,是一个非常复杂的话题,我不打算深入讨论这个内容。简单的来讲,Session实现了Hibernate的一 级缓存,SessionFactory实现了Hibernate的二级缓存。load方法会先查找一级缓存再查 找二级缓存,最后再去数据库中找,而get只会查找一级缓存,然后就去数据库中找了。 这只是是get和load的一个区别,另外的区别如果你有兴趣的话自己去google吧。关于Hibernate的缓存机制,如果你只是一般用用Hibernate的话没有必要深入研究,就当它不存在好了,get和load到底用哪个也不用非常讲究,如果你用工具生成DAO的话,
生成的代码用什么就用什么吧。
四、关键概念的理解
在使用Hibernate进行开发的时候有几个概念在我看来是必须理解的,否则在开发的时候会遇到很多问题而摸不着头脑。
1.Lazy loading,懒加载(延迟加载)
这个技术在很多地方被应用,比如Eclipse的插件管理也是用的延迟加载。在Hibernate中,所谓延迟加载就是返回一个POJO但是某些数据(往往是实体类型或者Set类型)并没有被真正的被填充,直到POJO的某个字段真正被引用的时候才从数据库中读取相应的数据来填充POJO中的字段。这样就能有效的避免很多不必要的数据库操作,因为POJO的有些数据我们并不需要,而且数据库操作是很费时间的。在Hibernate2中,默认是非延迟加载的,而在Hibernate3中,默认就是延迟加载了。
如果使用了延迟加载,那么在读取数据的时候有一个问题必须注意,那就是在数据真正被加载之前,Session不能被关闭。你可以回头看一下代码片段5,我在构造函数里面open了session以后就没有关闭这个session,所以我在使用的时候没有什么问题,但这样总占着数据库连接也不好,用好了应该及时关闭给别人用。我上文给的例子中没有关闭Session的代码,要加的话给DAO加一个方法调用Session.close(),然后在代码片段1中的return之前调用这个方法就行了。
Hibernate的延迟加载机制远不是这么简单,但是普通的应用没有必要去深究这些东西,了解这么多就够了。
2. Object lifecycle,对象生命周期
在Hibernate中,对象分为三种状态,Transient(自由状态)、Persistent(持久状态),Detached(游离状态),下面我分别解释一下。
1、自由状态:所谓自由状态就是说这个对象是自由的,与Hibernate无关,比 如:
Student student = new Student();
student.setId("0361095");
这里的student就是一个普通的对象与hibernate无关,称为自由状态。
2、持久状态:所谓持久状态就是指对象和数据库中的数据(持久状态的数据) 有关联,也就是说对象被Hibernate所管理了,比如:
session.save(student);
这样student对象就从自由状态变为持久状态了。持久状态的对象在Session 与数据库中的数据进行同步时(比如commit)会把数据更新到数据库。而 其他状态的则不会。我觉得可以这样来理解持久状态,可以看成Hibernate 也拥有一份对象的引用,那么如果你对持久状态对象的属性进行更改的话, Hibernate看到的对象的状态也更改了,而Hibernate所看到的对象和数据 库中的数据是等价的。也正是这样,Hibernate才实现了Object/Relation
的映射。类似的,load和get方法也一样会获取Persistent状态的对象。
3、游离状态:每次调用Session.close以后,所有跟这个session有关的处于
Persistant的对象就变成了游离状态。也许你要问Detached和Transient有什么区别。其实从我给的例子来说看不出什么区别,因为我这里ID是给定的,而真正开发的时候ID往往是自增的,那么Transient的对象是没有ID 的,当save了以后就有了,显而易见Detached的对象也是有ID,只不过这个对象已经和Hibernate脱离了关系。但是游离状态的对象仍然和数据库 中的记录有一定联系,至少游离状态的对象知道数据库中有条记录的ID为xxx。从这一点上来讲,游离状态是可以自己创造出来的,只要你知道数据库中的主键信息。
在使用Hibernate开发的时候要分清楚这三种状态,否则很容易出错。比如不能去save一个游离状态的对象,不能去update一个自由状态的对象等等。
五、结束语
我要讲的就这么多,这篇文章不是详细介绍Hibernate,我只是总结了自己学习和使用Hibernate的一些感受和经验,希望能给没有用过Hibernate的开发者一个上手的指引。如果你看完了这篇文章仍然满头雾水,无从下手的话,我只能向你表示歉意,浪费你的时间了。不管怎样,我强烈推荐一本书《深入浅出Hibernate》,这本书既能作为学习的教程,也能作为日后的查询用书,相当实用。