Hibernate3实用开发及使用教程

hibernate说实话,博主是今年毕业的,所以真正在项目中没有使用过hibernate。但是对于它我一点都不陌生,自己有突发奇想使用它配合其他技术搭建过很多小型项目和demo。所以今天写一篇有关它的基础使用方法。以及使用的一些技巧。

hibernate我所理解的就是一种操作数据库的技术,把实体类与数据库中的字段配置成映射关系,通过操作实体类的方式完成对数据库操作的持久化。并且可以根据配置识别自动生成对应数据库语言的SQL语句,并自动执行SQL语句。相比JDBC减轻了开发者的重复性工作,并更加符合面对对象的开发思想。

一:搭建hibernate的使用环境

二:对象在hibernate中的三种状态、增(增加完成后可以返回该条数据的主键)删改查、分页查询、总记录查询

三:hibernate中的关系

四:缓存分析

五:事务、延迟加载、乐观锁(version)、C3PO连接池(现在来讲,比较古老了)、两种session的获取与区别

六:总结

一:搭建hibernate的使用环境

1. 要使用hibernate完成对数据库的持久化操作,首先要实现以下配置:

①在eclipse的java Project项目中导入hibernate所必须的jar包;

②在src下新建hibernate.cfg.xml主配置文件----设置数据库连接、配置hibernate具备的属性、导入实体类映射文件等

<?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="connection.driver_class">com.mysql.jdbc.Driver</property>		
		<property name="connection.url">jdbc:mysql://localhost:3306/sh?characterEncoding=UTF-8</property>		
		<property name="connection.username">root</property>		
		<property name="connection.password">root</property>		
		
	<!-- hibernate配置 -->
		<property name="dialect">org.hibernate.dialect.MySQLDialect</property>		
		<property name="current_session_context_class">thread</property>		
		<property name="show_sql">true</property>		
		<property name="hbm2ddl.auto">update</property>
	<!-- 映射文件 -->	
		<mapping resource="hibernate/Hero.hbm.xml"/>	
	</session-factory>
</hibernate-configuration>

此配置中声明了hibernate连接那个数据库,以及对hibernate的设置,

dialect是告诉数据库我连接的是MySQL,请在生成SQL语句或者其他操作时,按照MySQL的语法来执行;

current_session_context_class是hibernate的事务管理方式,即每个线程代表一个事务;

show_sql为true时,是配置了在控制台打印生成的SQL语句;

hbm2ddl.auto这个配置代表自动更新表结构,也就是hibernate可以自动的在数据库中创建表。无需自行创建。

mapping resource指向了实体类与数据库字段做映射的配置文件路径。

③src下新建pojo包,创建实体类Hero.java,设置name和id属性,并给出get、set方法;

④src下新建hibernate文件夹,创建Hero.hbm.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">        
<hibernate-mapping package="pojo">
	<class name="Hero" table="hero">
		<id name="id" column="id">
			<generator class="native"/>
		</id>
		<property name="name" column="name"/>
	</class>
</hibernate-mapping>

package指向的是该实体类所在的包,以src为根目录。

<class>标签即代表一个映射类。在该标签属性中,name属性是对应的package包下的需要进行映射的实体类,table对象数据库中的表名。

在class标签中,<id>必须设置,因为他声明了该表的主键对应实体的那个属性。name是属性名,column代表对应哪个字段,<genarator class="native"/>该配置是说该id的增长方式按照配置的数据库的增长方式来(例如数据库中没有该表,则hibernate会创建这个表,并设置id为主键,设置为自增长)

<property>该标签是配置除主键外其他的属性与字段的映射关系,name是属性,column是字段。

⑤src下新建action包,创建Test.java文件

public class Test {
	public static void main(String[] args) {
		SessionFactory sf = new Configuration().configure().buildSessionFactory();
		Session s = sf.openSession();
		s.beginTransaction();
		Hero hero = new Hero();
		hero.setName("女警");
		s.save(hero);
		s.getTransaction().commit();
		s.close();
		sf.close();
	}
}

此时,右键运行该文件,则数据库中会生成hero表,并插入了一条数据。一套完整的hibernate配置搭建就完成了并能够成功运行。此时就能够感受到,hibernate的面向对象的思想,通过操作对象的方式完成对数据的持久化。似不似很方便了。

二:增删改查

对象在hibernate中的三种状态:瞬时、持久、托管。

  • 当对象被new创建,并设置了属性值,没有跟hibernate发生任何关系,JVM停止运行,该对象就烟消云散。此时为瞬时状态
  • 该对象与hibernate发生了关系,并且有对象的session,在数据库中有对应的记录,且该对象存在session的缓存中。则该对象为持久化状态。
  • 该对象在数据库中有对应的一条记录,但是session被关闭了,该对象不存在session的缓存中了。对象不能跟数据库进行通讯,它恢复成了一个普通的对象了。此时为托管状态。例如通过session的get方法获取到id为1的hero对象,然后session关闭了,hero对象虽然有个数据库查询出来的值,但它此时仅仅是一个hero对象,修改它与数据库没有任何关系了。

对于session对象来讲,他就是代表一次程序与数据库的对象,当它开启后,相当于设置了映射关系的对象就可以与数据库进行通讯,通过修改对象完成对数据库的操作。此时对象就是处于持久化的状态。当session关闭后,通讯切断。程序恢复正常。

通过hibernate完成数据库的操作过程中,大多可遵循以下顺序:

创建sessionFactory, 通过sessionFactory打卡一个session对象,该session对象开启一个事务(相对于开启一个线程)

执行数据库操作逻辑,session对象提交事务,关闭session,关闭sessionFactory。

1. 以增加为例,如下:

public class Test {
	public static void main(String[] args) {
        //创建sessionFactory
		SessionFactory sf = new Configuration().configure().buildSessionFactory();
		Session s = sf.openSession();//通过sessionfactory打开一个session对象
		s.beginTransaction();//session对象开启一个事务
		Hero hero = new Hero();
		hero.setName("女警");
		s.save(hero);//执行数据库操作
		s.getTransaction().commit();//session对象提交事务
		s.close();//关闭session
		sf.close();//关闭sessionfactory
	}
}

在此过程中,当session.save方法执行之后,其实可以观察到数据已经传递到数据库那里去了,相当于在程序与数据库之间,有一个中间层,当执行save方法之后,该数据被放到了中间层,只有在session把该事物提交之后,才真正的锤落定音,持久化到了数据库中。之前我们在hibernate.cfg.xml配置文件声明了一个事务一个线程,因为事务具有线程不安全性,所以我们在使用hibernate时,要做到一个数据库操作创建一个session,使用完后关闭掉。

使用save保存一条数据可以通过int id = (Integer)s.save(hero)这种方式获取到save返回来的这条数据的主键。

2. 删除即调用session的delete(hero)即可完成

3. 修改调用update(hero)即可完成,这里通过get(实体类.class,id值)的方式获取id为1的对象,然后修改该对象的属性,执行update完成更新,如下:

public class Test {
	public static void main(String[] args) {
		SessionFactory sf = new Configuration().configure().buildSessionFactory();
		Session s = sf.openSession();
		s.beginTransaction();
		Hero hero = new Hero();
		hero = (Hero)s.get(Hero.class, 1);
		hero.setName="女警改1";
		s.update(hero);
		s.getTransaction().commit();
		s.close();
		sf.close();
	}
}

4. 查询就有点多样化了。

①首先可以通过get()或者 load()方法获取,这两种方法的区别会在下面进行介绍。

②通过hql获取,hibernate的专门用于查询的语句,不同于sql,比如hql在查询时,使用类名而不是表明,select Hero where id=1

public class Test {
    public static void main(String[] args) {
        SessionFactory sf = new Configuration().configure().buildSessionFactory();
        Session s = sf.openSession();
        s.beginTransaction();
        String name = "女";
        Query q =s.createQuery("from Hero p where p.name like ?");
        q.setString(0, "%"+name+"%");
        List<Hero> ps= q.list();
        for (Hero p : ps) {
            System.out.println(p.getName());
        }
        s.getTransaction().commit();
        s.close();
        sf.close();
    }
}

即完成了对hero表中的数据进行模糊查询,并通过q.list(),返回查询到的数据集合。 

③SQL,同样使用SQL进行模糊查询如下:

        String name = "女";         
        String sql = "select * from hero  where name like '%"+name+"%'";         
        Query q= s.createSQLQuery(sql);
        List<Object[]> list= q.list();
        for (Object[] os : list) {
            for (Object filed: os) {
                System.out.print(filed+"\t");
            }
            System.out.println();
        }

使用SQL查询list返回的是一个object类型的集合。 

④Criteria进行查询,这种查询方式是完全面对对象的方式进行查询:

         String name = "女";          
        Criteria c= s.createCriteria(Hero.class);
        c.add(Restrictions.like("name", "%"+name+"%"));
        List<Hero> ps = c.list();
        for (Hero p : ps) {
            System.out.println(p.getName());
        }

有关更多Criteria的使用方法,可以查询hibernate的官方文档或者论坛使用手册。

5. 对于分页查询和总记录查询:

可以事情hql查询,调用q.uniqueResult()方法获取总数。

        String name = "女";         
        Query q =s.createQuery("select count(*) from Hero  where name like ?");
        q.setString(0, "%"+name+"%");
        long total= (Long) q.uniqueResult();
        System.out.println(total);

hibernate的分页查询,hibernate使用Criteria来进行分页查询,使用十分的简单。

c.setFirstResult(2); 表示从第3条数据开始
c.setMaxResults(5); 表示一共查询5条数据

        String name = "女";       
        Criteria c= s.createCriteria(Hero.class);
        c.add(Restrictions.like("name", "%"+name+"%"));
        c.setFirstResult(2); //表示从第三条数据开始,相当于limit的第一个参数
        c.setMaxResults(5);  //显示5条数据,相当于limit的第二个参数

三:hibernate中的关系

多对一、一对多、多对多关系。

1. 多对一:

多个Product对应一个Category ,大致理解就是,在product类中增加Category属性,在保存该product时,设置setCategory属性。此时通过配置映射文件中的多对一关系,会在product表中自动添加cid(cid的值为该Category对象的主键)作为该表的外键。

创建Product、Category类,并配置映射文件。在Product.hbm.xml文件中配置增加多对一关系。代表Product类中的category属性对应的是Category这个映射类,并对应product数据库中的cid。

<hibernate-mapping package="pojo">
    <class name="Product" table="product">
        <id name="id" column="id">
            <generator class="native">
            </generator>
        </id> 
        <property name="name" />
        <property name="price" />
        <many-to-one name="category" class="Category" column="cid" />
    </class>    
</hibernate-mapping>

将这两个映射文件添加到hibernate.cfg.xml中,在test类中,此时更新p时,p的cid即为c对象的主键id。

        Category c =new Category();
        c.setName("c1");
        s.save(c); 
        Product p = (Product) s.get(Product.class, 8);
        p.setCategory(c);
        s.update(p);

2. 一对多

一个Category对应多个Product ,大概意思就是一个分类中有多个产品,在Category实体中增加属性set<Product> products代表这个Category对象中的product集合。在Category映射文件中配置了一对多关系后,查询一条Category对象的值,通过获取products属性,就可以获取到product表中cid的值为该Category对象主键的所有product记录。

配置文件如下:通过<set>标签进行设置,因为我们的products属性是set类型的。name为该属性的名。lazy为是否延迟加载,有关延迟加载后面会有介绍。key代表这个表对应的外键是cid,<one-to-many>为配置一对多关系,class为一对多的多是那个类。

<hibernate-mapping package="pojo">
    <class name="Category" table="category_">
        <id name="id" column="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name" />
        <set name="products" lazy="false">
            <key column="cid" not-null="false" />
            <one-to-many class="Product" />
        </set>                 
    </class>   
</hibernate-mapping>

在test.java 中,查询id为1的Category对象记录,并获取products属性,将该属性遍历输出,即可得到product表中所有cid为1的product记录。

        Category c = (Category) s.get(Category.class, 1);
        Set<Product> ps = c.getProducts();
        for (Product p : ps) {
            System.out.println(p.getName());
        }

3. 多对多

①一个用户User可以购买多个Product,一个product可以被多个User用户购买,则user和product为多对多关系。

②创建user类,user类中添加set<Product> products。product中添加set<User> users属性。

③在user.hbm.xml中:设置多对多关系,也就是相当于给products这个属性设置多对多关系。并告诉数据库,user与product的对应关系保存在user_product这张表中。

key代表这个对象在user_product表中的主键,也就是说获取一条user对象时,然后查看他买了哪些product。需要去user_product表中寻找uid为user的主键的对应的所有pid记录,即该用户对应的多个product是哪些。

many-to-many中设置了class,也就是这个products集合属性对应的哪个类,column的意思是在user_product表中创建字段pid,当插入一个user记录时,获取到user中所有product对象,把对象的Product类中映射对应的主键,插入到这个pid中。一个uid可以对应多个pid。在数据库中形成一对多关系

<hibernate-mapping package="pojo">
    <class name="User" table="user_">
        <id name="id" column="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name" /> 
        <set name="products" table="user_product" lazy="false">
            <key column="uid" />
            <many-to-many column="pid" class="Product" />
        </set>               
    </class>     
</hibernate-mapping>

代表user的id为1的对象对应了id为2,3,4,5的四个product对象。

④product.hbm.xml也配置多对多关系,<set>中,如果是查询一个product对象时,当获取users属性时,则会寻找pid为该对象id的所有uid的值,并根据这些uid寻找class中对应的映射文件,寻找所有id为这些uid的记录,查询出来放在users属性中。

当插入一条product对象时,会将users中所有的user对象的主键插入到该表的uid字段中,pid为这一个product对象的主键,就会产生这样的多条记录。一个pid对象多个uid

<hibernate-mapping package="pojo">
    <class name="Product" table="product_">
        <id name="id" column="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name" />
        <property name="price" />
        <many-to-one name="category" class="Category" column="cid" />         
        <set name="users" table="user_product" lazy="false">
            <key column="pid" />
            <many-to-many column="uid" class="User" />
        </set>                        
    </class>     
</hibernate-mapping>

四:缓存分析

hibernate分为二级缓存和一级缓存。

简单点讲,一级缓存中缓存是存在session中的,会随着session的关闭而清除。例如session开启,在获取一条id为1的user之后,hibernate会去数据库进行物理查询,并将查询结果返回,生成缓存放在session中,在改事务再次查询id为1的user时,则不会进行数据库物理查询,而是直接从缓存中获取。并且该缓存只在当前session对象中有效,在其他session对象中同样无法获取。当session关闭后,缓存对应消除,再次查询需要进行物理查询数据库。

二级缓存是存在sessionFactory中的,不会随着session的关闭而清除。获取id为1的user对象时,session会进行物理数据库查询,此时返回查询结果,并将缓存存在sessionFactory中,该缓存可以直接在其他session对象中使用,就算当前session关闭后同样有效,只要sessionfactory不关闭即可。只会查询一次数据库。

1. 如何配置二级缓存

在hibernate.cfg.xml中声明开启二级缓存,这里使用的是第三方 EhCache提供的二级缓存,因为hibernate本身不提供二级缓存。

 <property name="hibernate.cache.use_second_level_cache">true</property>
 <property name="hibernate.cache.provider_class">
    org.hibernate.cache.EhCacheProvider
 </property>

早src目录下,创建一个ehcache.xml用于EHCache的缓存配置

<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />
</ehcache>

对要使用二级缓存的实体类映射文件添加二级缓存配置:

<cache usage="read-only" />

<class name="Category" table="category_">
        <cache usage="read-only" />  二级缓存配置
        <id name="id" column="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name" />
        <set name="products" lazy="true">
            <key column="cid" not-null="false" />
            <one-to-many class="Product" />
        </set>
                 
    </class>

此时,hibernate就已经开启了二级缓存。

二级缓存的开启会大大减少连接数据库进行物理查询的时间,会对程序的整体性能有很大的提高。

五:事务、延迟加载、乐观锁(version)、C3PO连接池(现在来讲,比较古老了)、两种session的方式与区别

1. 事务:对hibernate的任何操作,都要放在事务中进行,并且在事务提交时,要么都成功,要么都失败。

也就是说在一个事务中,进行两个操作,如果A操作和B操作都能正常执行,则事务提交成功,两个都生效了。

但如果A正常执行,B执行失败,则事务提交时,AB都不会生效。

一个事务的开始是由:s.beginTransaction();开始,由s.getTransaction().commit();结束。

并且MySQL的表类型是INNODB才支持事务。

2. 延迟加载

  • 通过get方法和load方法获取对象有什么不同呢?

通过get方法获取对象时,hibernate会直接去数据库进行查询,获取记录返回结果。

通过load方法获取时,hibernate并不会直接去查询数据库,而是返回一个代理对象,该对象所有属性均为初始值,当程序调用对象的属性时,例如调用user.getName()时,hibernate才会真正的查询数据库,返回该条记录。

也就是说,如果使用load方法查询时,hibernate就认定数据库中一定存在这条记录。所以先使用代理对象,如果数据库中没有该条记录,则会报错。get如果没有该条记录,不会报错,会返回null。

  • 关系的延迟加载

在设置了一对多等关系的实体中,如果设置了延迟加载,即lazy="true",俗称懒加载。则在获取该实体对象时,例如User实体类中有一个set<Product> products属性,在获取时,hibernate并不会查询该user对应的所有product集合值,而是返回一个代理对象products,当user对象真正调用这个属性中的值时,如user.getProducts才会去进行数据库查询。

3. 乐观锁(version)

如果有这样的情景,两个session同时获取一个id为1的product,数据库中价格为1000。s1修改价格值为+1000,s2修改name值为+1500。s1sava并提交事务,此时product的价格为2500,s2sava并提交事务,此时有覆盖了s1修改的内容成为了2000,。如何避免这种情况的发生,hibernate提供了乐观锁机制,也就是给这个记录添加一个版本号,用来控制同时修改的情况。

添加乐观锁:在实体的映射文件中,添加<version>标签,并在Product类中添加version属性。

 <class name="Product" table="product_">
        <id name="id" column="id">
            <generator class="native">
            </generator>
        </id>
        <!--version元素必须紧挨着id后面  -->
        <version name="version" column="ver" type="int"></version>
        <property name="name" />
        <property name="price" />
    </class>

此时在运行上述情境中的代码就会报错,提示本对象已经被修改过了,无法进行修改。原理分析:

id为1的product的对象初始version为1,s1和s2同时获取该对象,version均为1。s1修改价格+1000,执行save,并提交事务,此时id为1的product的version会被修改为2。s2修改价格+2000,执行sava,并提交事务,提交事务是hibernate检测到id为1的这个对象的version不等于1了,已经是2了物是人非了。所以会报错,版本不一致,无法修改。则s2就修改失败了。

4. C3PO连接池(现在来讲,比较古老了)

使用连接池技术主要是用来节省数据库连接时间的,可以让程序建立多条数据库连接。

使用C3PO连接池只需要两步即可,添加C3PO的jar包,在hibernate.cfg.xml配置中设置C3PO连接池。即可

        <property name="hibernate.connection.provider_class"> 
            org.hibernate.connection.C3P0ConnectionProvider 
        </property> 
        <property name="hibernate.c3p0.max_size">20</property> 
        <property name="hibernate.c3p0.min_size">5</property> 
        <property name="hibernate.c3p0.timeout">50000</property> 
        <property name="hibernate.c3p0.max_statements">100</property> 
        <property name="hibernate.c3p0.idle_test_period">3000</property> 
        <!-- 当连接池耗尽并接到获得连接的请求,则新增加连接的数量 -->
        <property name="hibernate.c3p0.acquire_increment">2</property> 
        <!-- 是否验证,检查连接 -->
        <property name="hibernate.c3p0.validate">false</property>   

5. 两种session的方式与区别

hibernate提供了两种获取session的方式,一种是sf.openSession;一种是sf.sf.getCurrentSession(); 

两种session中,区别在于

  • 对于事务的提交:

openSession只有在增加,删除,修改的时候需要开启提交事务,查询时不需要的 

getCurrentSession是所有操作都必须放在事务中进行,并且提交事务后,session就自动关闭,不能够再进行关闭 

  • 对于获取

openSession每次都会获取一个新的session

getCurrentSession如果在同一个线程中,每次getCurrentSession获取的都是同一个session;在不同的线程中,每次getCurrentSession会获取不同的session。

六:总结

hibernate的使用其实很简单,只要理解了它想表达的那种面对对象的思维通过对象操作数据库,就能基本掌握它的意思。至于用法,它就像一个工具,用法和技巧,不都是积累出来的么,每次掌握一个用法,久而久之对它就会很熟悉了。此篇文章是我自己的学习笔记,也是用来方便自己日后查阅的。分享出来有需要的朋友也可以来查阅,我会尽可能的写的详细明了。

猜你喜欢

转载自blog.csdn.net/qq_41908550/article/details/81081121