Hibernate

Hibernate

Hibernate是一个开源的对象关系映射(ORM)框架。对JDBC进行了非常轻量级的对象封装。 

将对象和数据库表建立映射关系,Hibernate框架使用在数据持久化层(dao)。 

ORM:对象关系映射(英语:Object Relational Mapping)

采用映射元数据(配置文件)来描述对象-关系的映射细节。ORM框架通过配置文件将实体对象和数据库表对应起来。

Hibernate配置文件

Hibernate从其配置文件中读取和数据库连接有关的信息。Hibernate配置文件有两种形式,XML格式或者java属性文件(properties)格式。

properties

hibernate.dialect:指定数据库使用的sql方言。可以根据数据库的不同生成不同的方言,底层是通过调用一个一个类实现的。
hibernate.connection.driver_class:指定数据库的驱动程序
hibernate.connection.url:指定连接数据库的url
hibernate.connection.username:指定连接数据库的用户名
hibernate.connection.password:指定连接数据库的密码
hibernate.show_sql:如果为true,可以在控制台打印sql语句
hbm2ddl.auto:生成表结构的策略配置,配置这个可以通过映射文件和实体类自动生成表结构
有四个值:
update(最常用的取值):如果当前数据库不存在对应的数据表,那么自动创建数据表
如果存在对应的数据表,并且表结构和实体类属性一致,那么不做任何修改
如果存在对应的数据表,但是表结构和实体类属性不一致,那么会新创建与实体类属性对应的列,其他列不变 

create(很少使用):无论是否存在对应的数据表,每次启动Hibernate都会重新创建对应的数据表,以前的数据会丢失

create-drop(极少使用):无论是否存在对应的数据表,每次启动Hibernate都会重新创建对应的数据表,每次运行结束删除数据表

validate(很少使用):只校验表结构是否和我们的实体类属性相同,不同就抛异常 

xml

<?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">123456</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate</property>

        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="hbm2ddl.auto">update</property>
        <property name="hibernate.connection.autocommit">true</property> 

        //引入映射文件
        <mapping resource="com/cad/domain/User.hbm.xml"/>

    </session-factory>
</hibernate-configuration>

Hibernate中持久化类编写规范

    -必须提供无参数的默认构造方法。因为程序运行时,Hibernate会运用java的反射机制,创建实体类的实例。 

    -所有属性必须提供public访问控制符的set get方法 

    -属性应尽量使用基本数据类型的包装类型(如Integer)
            基本数据类型无法表达null值,所有基本数据类型的默认值都不是null,这样就有很大的缺陷。
            例如有一个score属性,表示学生分数,如果为0,那么是表示该学生未参加考试还是说该学生成绩为0呢?
            这时候如果用包装类型,就可以使用null来表示空值,学生未参加考试等等。

    -不要用final修饰实体(将无法生成代理对象进行优化)

详解对象-关系映射文件

Hiernate采用XML格式的文件来指定对象和关系数据之间的映射。Hibernate通过这个文件来生成各种sql语句。
命名规则为 实体类名.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">

 <!--根元素,有一常用属性为package,当指定了package时,后面的类名字就可以简短,例如 package="com.cad.domain",后面class标签的name只用写User即可--> 
 <hibernate-mapping>

    <!--class标签指定类和表的对应关系,name为类名,table为表名
        class标签的属性 dynamic-insert属性,默认是false,当我们执行插入语句时,会动态生成sql语句,如果我们只为某个字段赋值,其他字段为null,但是生成的sql语句还是包含其他字段,例如user有两个属性,我们只为name赋值,生成的sql语句还是 insert into user(name,password)values (?,?),而当我们将该值设置为true时,生成的sql语句会仅包含不为null的字段,生成的sql语句就是insert into user(name) values (?) 

        class标签的属性 dynamic-update属性,默认是false,当我们执行更新语句时,会动态生成sql语句,如果我们只为某个字段更新,其他字段不变,生成的sql语句还是包含其他字段。而当我们将该值设置为true时,生成的sql语句仅包含需要更新的字段
        使用上面的两个属性可以提高运行性能,但是Hibernate动态生成sql语句需要的性能很小,所以可以省略-->
    <class name="com.cad.domain.User" table="user"> 

        <!--id标签用来设定持久化类中的OID和表的主键的映射,name为持久化类中的属性,column是数据表中的主键列名
            id标签的属性:length 指定列的数据长度
            id标签的属性:unsaved-value 指定当主键为某个值时,当做null来处理
            id标签的属性:access 也可用在<property>标签上 默认值为property,即通过相应的get set方法来访问持久化类的属性,当值为field时,表明使用反射机制直接访问类的属性,不推荐使用,破坏封装性-->
        <id name="id" column="id">
            <!--
                generator标签用来设定主键生成策略,hibernate内置的几种主键生成策略
                1.increment 适用于代理主键。由Hibernate自动以递增的方式生成主键,每次增量为1 ,会执行两个sql语句,先从表中查找出最大的id,然后加一,插入当前数据
                2.identity  适用于代理主键。由底层数据库生成主键,依赖数据库的主键自增功能
                3.sequence  适用于代理主键。由底层数据库的序列来生成主键,前提是数据库支持序列。(mysql不支持,oracle支持)
                4.hilo      适用于代理主键。Hibernate根据hilo算法来自己生成主键。
                5.native    适用于代理主键。根据底层数据库对自动生成主键的支持能力选择 identity|sequence|hilo
                6.uuid      适用于代理主键。采用UUID算法生成主键。
                7.assigned  适用于自然主键。由我们自己指定主键值。例如指定身份证号为主键值

            -->
            <generator class="native"></generator>
        </id>

        <!--
            property标签属性 name指定持久化类的属性名称
            column 与类属性映射的字段名,如果没有设置,默认用类属性名作为字段名
            not-null  指定属性的约束是否为非空,默认false
            unique   指定属性的约束是否唯一
            type     指定Hibernate映射类型。例如java类型为string,数据库类型为text,那么应该把Hibernate类型设置为Text。有一张对应的表可以查看。如果没有指定映射类型,Hibernate会使用反射机制识别属性的java类型,然后自动使用与之对应的Hibernate映射类型
        -->
        <property name="name" column="name"></property>
        <property name="password" column="password"></property>
    </class>
 </hibernate-mapping>   

Hibernate API详解

Configuration类

Configuration类:用来加载默认文件路径下的配置文件(hibernate.properties)。
                 调用configure()方法会加载默认文件路径下的xml格式的配置文件(hibernate.cfg.xml)推荐使用。

                 如果配置文件在不默认文件路径下或者配置文件名不符合默认规则
                     可以使用 
                     new Configuration().configure(file)  加载指定文件
                     new Configuration().configure(path)  加载指定路径下的文件 

                如果使用properties格式的配置文件,可以使用addClass(实体类名.class)方法可以加载映射文件。

SessionFactory对象

SessionFactory对象: 
                SessionFactory代表数据库存储源。根据Hibernate配置文件创建对应的数据库存储源。
                SessionFactory对象创建后,和Configuration对象再无关联。修改Configuration包含的配置文件信息,不会对SessionFactory有任何影响。   
                  获取SessionFactory对象:new Configuration().configure().buildSessionFactory();
                对象的缓存很大,就称为重量级对象。SessionFactory存放了Hibernate配置信息,映射元数据信息等。是重量级对象。

Session对象

Session对象:   
                代表程序和数据库的会话。Session提供了操作数据库的各种方法。是轻量级对象。 

                   获取Session对象 
                   factory.openSession(): 获取新的Session实例。
                   factory.getCurrentSession():采用该方法创建的Session会取出当前线程中的Session,底层使用ThreadLocal进行存取 

                    save()方法:把Java对象保存到数据库中。
                                    Transaction ts=session.beginTransaction();
                                    User u=new User();
                                    u.setName("赵六");
                                    u.setPassword("123456");
                                    //将对象保存到数据库
                                    session.save(u); 
                                    ts.commit();

                    update()方法:更新数据库的方法 
                                    Transaction ts=session.beginTransaction();
                                    //先查出要修改的对象,根据主键值   
                                    User user=session.get(User.class, 1);
                                    user.setName("jery");
                                    //将对象更新到数据库,根据OID
                                    session.update(user); 
                                    ts.commit();

                    delete()方法:删除方法
                                    底层根据OID进行删除。有两种方式
                                    (1)
                                        Transaction ts=session.beginTransaction();
                                        User user=session.get(User.class, 1); 
                                        //删除指定对象
                                        session.delete(user);   
                                        ts.commit();
                                    (2)
                                        Transaction ts=session.beginTransaction();
                                        User user=new User();
                                        user.setId(2);
                                        session.delete(user);
                                        ts.commit();     

                    load()或get()方法:从数据库查找指定对象 

                                  session.get(实体类名.class,OID);或session.load(实体类名.class,OID);

                    load()和get()的区别
                                  我们使用get查询时发现控制台会立马打出查询语句。
                                  使用load查询时控制台不会打印查询语句。
                                  get方法被调用时立刻发送sql语句到数据库进行查询。
                                  load方法被调用时并没有查询数据库,当我们需要使用查询的对象时,才去查询,所以当我们打印对象时,才会在控制台打印sql语句。

                      get()的原理 
                                程序调用get方法,Hibernate发送sql语句到数据库
                                数据库返回结果,Hibernate将结果封装成对象,返回对象到程序。

                      load()的原理
                                程序调用load方法,Hibernate使用代理技术,创建一个代理对象,属性只有ID值。
                                然后返回代理对象给程序,我们使用对象时,代理对象调用Hibernate查询数据库,初始化其他属性。 

            load方法,返回一个代理对象,获取其属性时,会查询数据库,每次访问属性都会查询数据库么?
            答:不是。代理对象中有一个标识是否被初始化的boolean类型变量,记录是否被初始化。

查询所有对象的方法

            使用HQL语言(后面会详细介绍),HQL语言是面向对象的
            Query query=session.createQuery("from User");


            第二种方式
            Criteria c=session.createCriteria(User.class);
            List<User> l=c.list();

            第三种方式,使用原生sql语句进行查询
            SQLQuery query=session.createSQLQuery("select * from user");
            List l=query.list();

Transaction对象

        封装了事务的操作。我们做增删改查等操作时,必须开启事务.
            因为session是线程不安全的,这样主要是为了线程安全。保证数据的正确性。
            开启事务: Transaction ts=session.beginTransaction();
            提交事务:ts.commit();
            回滚事务:ts.rollback();
            当通过getCurrentSession获取当前线程绑定的Session时,事务关闭时,会自动把Session关闭并删除。

Query对象

        封装HQL语句的对象。

        返回一个对象的方法 query.uniqueResult();

        分页相关
        query.setFirstResult(index):从第几个取
        query.setMaxResults(count):指定取几行记录

Hibernate一级缓存

Hibernate中也存在缓存,同样是为了提高效率。Hibernate的缓存包括Session的缓存和SessionFactory的缓存。Session的缓存是内置的,不能被卸载,也被称为Hibertnate的一级缓存。

SessionFactory有一个内置缓存和外置缓存。SessionFactory的外置缓存是一个可配置的缓存插件。默认情况下,Hibernate不会启用这个缓存插件。被称为Hibernate的二级缓存。

缓存的范围

    事务范围:缓存只能被当前事务访问。一级缓存是Session的缓存,Session对象生命周期通常对应一个事务,因此是事务范围的缓存。 

    进程范围:缓存被进程内的所有事务共享。二级缓存是可配置的缓存插件,由SessionFactory管理,SessionFactory生命周期和应用程序的进程对应,因此是进程范围的缓存。

    集群范围:在集群环境中,缓存被同一个机器或者多个机器上的多个进程共享。 

Session缓存

当Session通过save方法持久化一个对象时,该对象被加入到Session缓存中。
当Session通过get方法获取一个持久化对象时,Session会先判断Session缓存中是否存在这个对象,如果存在,就不需要再从数据库中查找。

Transaction ts=session.beginTransaction();
        //测试session缓存的存在
        User user1= (User) session.get(User.class,1);
        User user2= (User) session.get(User.class,1);
        User user3= (User) session.get(User.class,1);
        System.out.println(user2==user3);

脏检查及清理缓存的机制

//测试session缓存的存在
User user1= (User) session.get(User.class,1);
user1.setName("我是更新后的数据");

image-20210208093923837

Session是怎样进行脏检查的呢?

当一个对象被加入到Sesion缓存中时,Session会为该对象复制一份快照。当Session清理缓存时,会比较当前对象的属性和快照来判断是否发生变化,如果发生变化,就会根据最新属性来执行相关的更新操作。

什么时候会清理缓存呢?

默认情况下,在调用commit()方法时会先清理缓存。再向数据库提交事务。 

-当执行查询操作时,如果缓存中的持久化对象属性已经发生了改变,就会先清理缓存,同步数据,保证查询到的是正确的结果。 

-当应用程序显式调用Session的flush()方法时  

-Session清理缓存的例外情况,如果对象使用的是native生成策略生成OID,那么调用Session的save()方法来保存该对象时,会立刻执行向数据库的插入语句。

如果不希望Session在以上默认的时间点清理缓存,可以通过Session的setFlushMode()方法来设定清理缓存的时间点。
FlushMode类定义了三种清理模式。

各种查询方法commit()方法lush()方法
FlushMode.AUTO(默认模式)清理清理清理
FlushMode.COMMIT不清理清理清理
FlushMode.NEVER不清理不清理清理

Java对象在Hibernate持久化层的状态

-临时状态:刚用new语句创建对象,还没有被持久化,并且不处于Session缓存中。处于临时状态的java对象被称为临时对象。

-持久化状态:已经被持久化,并且加入到Session的缓存中。处于持久化状态的java对象被成为持久化对象。

-游离状态:已经被持久化,但不再处于Session的缓存中。处于游离状态的java对象被成为游离对象。

        //对象状态转换的过程
        Transaction ts=session.beginTransaction();
        User user =new User();                  //临时状态
         user.setName("tom");
         user.setPassword("123456");

        session.save(user);                     //转变为持久化状态
        ts.commit();                            //持久化状态 

        session.close();                        //转变为游离状态
        sessionfactory.close();
        System.out.println(user.getName());     //游离状态 

Session接口的其他API

--Session的save()和persist()方法
        两个方法都是用来保存对象,能把临时状态转变为持久化状态。 

    两个方法的区别在于:
    save方法持久化对象时,会返回持久化对象的OID。所以程序执行save时会立刻执行insert语句,来返回数据库生成的OID。 

    persist方法持久化对象时,不会保证立即为持久化对象的OID赋值,不会立即生成insert语句,而是有可能在Session清理缓存时才为OID赋值。


--Session的clear()方法
    清空一级缓存 

--Session的update方法
                    update()方法可以将游离对象转变为持久化对象。用来执行修改操作。
                    update()方法完成以下操作
                    -把游离对象加入到当前缓存中,变为持久化对象
                    -然后计划执行update语句

                    只要通过update使游离对象转变为持久化对象,即使没有修改任何属性,在清理缓存时还是会执行update语句。
                    如果希望Session仅当修改了属性时才执行update语句,可以在映射文件中的<class>元素中设置select-before-update="true",默认为false
                    这样当Session清理缓存时,会先发送一条查询语句,然后判断缓存中的对象和记录是否一致,不一致才执行update语句。

                    当update()方法将游离对象转变为持久化对象时,如果Session缓存中已经存在相同的OID持久化对象,那么会抛出异常。
                    例如:
                        Transaction ts=session.beginTransaction();
                        User user1=session.get(User.class, 5);
                        session.evict(user1);
                        User user2=session.get(User.class, 5);
                        session.update(user1);
                        ts.commit();

                        因为Session的缓存是一个Map结构,OID为key,对象为value。
                        当执行session的update方法时,由于缓存中已经存在了OID为5的持久化对象,因此会抛出异常。 

-Session的saveOrUpdate()方法 

            Session的saveOrUpdate()方法同时包含了save()和update()方法的功能
            如果传入的参数是临时对象(OID为null),就调用save方法。
            如果传入的参数是游离对象(OID不为null),就执行update方法。

一对多关联关系

//一
<set name="orders">
    <!--<key>元素设定所关联的持久化类对应的表的外键-->
    <!--<one-to-many>元素设定关联的持久化类-->
    <key column="cid"/>
    <one-to-many class="com.cad.domain.Order"/> 
</set>
 Hibernate根据映射文件获得以下信息
            -<set>元素表明Customer类中的orders属性为java.util.Set集合
            -<one-to-many>元素表明orders集合中存放的是一组order对象
            -<key>元素表明orders表通过外键cid关联customer表
                
//多
<many-to-one name="customer" column="cid" class="com.cad.domain.Customer"></many-to-one>

< set >元素的inverse属性

inverse所描述的是对象之间关联关系的维护方式。 inverse属性指定由哪方来维护关联关系。
inverse默认为false,即关联关系由自己控制,若为true,则反转,关联关系由对方控制.
Inverse属性的作用是:是否将对集合对象的修改反映到数据库中。 

在映射一对多的双向关联关系中,应该在"一"方把inverse属性设为true,由对方来维护主键关联关系.

所以上述例子中,inverse默认是false.即Customer维护关联关系,所以Customer会执行两条更新语句来更新Order的cid.
但是我们Order在插入的时候已经插入cid,所以这样会影响性能,我们只需要将Customer的<set>元素的inverse属性改为true即可。 

我们的删除案例中也是,Customer执行删除时,会先去把Order的主键约束解除,然后删除。
我们只需要将Customer的inverse设置为true,然后由对方维护关联关系,我们再进行删除时,就会出现异常,因为有主键约束,我们Customer不再维护关联关系。 

级联操纵

在实际应用中,对象和对象之间是相互关联的。例如我们的一对多关联关系。
在关系-对象映射文件中,用于映射持久化类之间关联关系的元素,如 < set>,< many-to-one>,< one-to-many>,都有一个cascade属性,用来指定如何操纵与当前对象关联的其他对象。

    我们先看下面的例子,我们创建一个Customer,再创建两个Order,然后关联
    我们只保存Customer,会抛出org.hibernate.TransientObjectException异常,这是为什么呢?

    这是因为我们的Customer的inverse为false,关联关系由Customer维护。我们保存Customer时,
    会维护Customer中orders中的所有Order的主键,但是Order是临时对象,并没有转变为持久状态,这时候就会抛出异常。

            @Test
            public void test() {
                //读取配置文件
                Configuration conf=new Configuration().configure();

                //根据配置创建factory
                SessionFactory sessionfactory=conf.buildSessionFactory();
                session = sessionfactory.openSession(); 
                 Transaction ts=session.beginTransaction();
                 Customer c=new Customer();
                 c.setName("jack");
                 Order o1=new Order();
                 o1.setName("苹果");
                 Order o2=new Order();
                 o2.setName("香蕉");
                 c.getOrders().add(o1);
                 c.getOrders().add(o2);
                 o1.setCustomer(c);
                 o2.setCustomer(c);

                 session.save(c);

                ts.commit();
                session.close();
                sessionfactory.close();
            }

当Hibernate持久化一个临时对象时,并不会自动持久化所关联的其他临时对象,所以会抛出异常。
如果我们希望Hibernate持久化对象时自动持久化所关联的其他对象,那么就需要指定cascade属性

级联保存和更新

当我们持久化对象时自动持久化所关联的其他对象。 
把cascade属性设置为save-update ,这时候我们再执行上面的代码就会自动帮我们保存Customer关联的Order对象。
当cascade属性为save-update时,表明保存或更新当前对象时,会级联保存或更新与它关联的对象。

级联删除

如果我们的cascade属性为delete时,我们删除当前对象,会自动删除与之关联的对象。
慎用这个delete属性。
例如:我们的Order配置了这个属性,Customer也配置了这个属性,我们删除订单时,因为是级联删除
所以会查找Customer,删除Customer,但Customer也配置了级联删除,所以会查找所有关联的订单,最后会删除该客户的所有订单和该客户。

孤儿删除

如果我们的对象和关联的对象解除关系后,希望自动删除不再关联的对象。
需要将cascade设置为delete-orphan.

例如 ,我们设置cascade="delete-orphan"
Transaction ts=session.beginTransaction();
Customer c=session.get(Customer.class, 7);
Order order=(Order) c.getOrders().iterator().next();
c.getOrders().remove(order);
order.setCustomer(null);
ts.commit(); 

我们解除Customer和Order的关系,Hibernate就会自动删除Order。  


当cascade的值为all时,是save-update和delete的整合。
当cascade的值为all-dalete-orphan时,是all和delete-orphan的整合。

多对多

实体类

public class Student {
    private Integer id;
    private String name;

    public Student() {
    }

    //用一个集合包含该学生所选的课程对象
    private Set<Course> courses=new HashSet<Course>();
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Set<Course> getCourses() {
        return courses;
    }
    public void setCourses(Set<Course> courses) {
        this.courses = courses;
    }
}

public class Course {
    private Integer id;
    private String name;
    //用一个集合包含所有选择该课程的学生

    public Course() {
    }

    private Set<Student> students= new HashSet<>();
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Set<Student> getStudents() {
        return students;
    }
    public void setStudents(Set<Student> students) {
        this.students = students;
    }
}

hibernate.cfg.xml

<?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.cj.jdbc.Driver</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">123456</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate?characterEncoding=utf8&amp;useSSL=true&amp;serverTimezone=UTC</property>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        //这里要注意,生成表的策略要为create
        <property name="hbm2ddl.auto">create</property>
        <property name="hibernate.connection.autocommit">true</property>
        <mapping resource="User.hbm.xml"></mapping>
        <mapping resource="Student.hbm.xml"></mapping>
        <mapping resource="Course.hbm.xml"></mapping>

    </session-factory>
</hibernate-configuration>

Course.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 >
    <class name="com.cyb.hibernate.entity.Course" table="course">
        <id name="id" column="id">
            <generator class="native"></generator>
        </id>
        <property name="name" column="name"></property>
        <!--table属性用来指定生成的中间表的表名称  inverse指定关联关系由Student维护-->
        <set name="students" table="student_course" inverse="true">
            <key column="cid"></key>
            <!--<many-to-many>元素中的column属性指定本表通过中间表中的sid外键关联到Student对象-->
            <many-to-many class="com.cyb.hibernate.entity.Student" column="sid"></many-to-many>
        </set>
    </class>
</hibernate-mapping>

Student.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 >
    <class name="com.cyb.hibernate.entity.Student" table="student">
        <id name="id" column="id">
            <generator class="native"></generator>
        </id>
        <property name="name" column="name"></property>
        <set name="courses" table="student_course" cascade="save-update">
            <key column="sid"></key>
            <many-to-many class="com.cyb.hibernate.entity.Course" column="cid"></many-to-many>
        </set>
    </class>
</hibernate-mapping>

测试一下

public class M2MDemo {
    public static void main(String[] args) {
        //读取配置文件
        Configuration conf=new Configuration().configure();

        //根据配置创建factory
        SessionFactory sessionfactory=conf.buildSessionFactory();;
        Session session = sessionfactory.openSession();
        Transaction ts=session.beginTransaction();
        //创建两个Student
        Student s1=new Student();
        s1.setName("tom");
        Student s2=new Student();
        s2.setName("jack");

        //创建三个Course
        Course c1=new Course();
        c1.setName("语文");
        Course c2=new Course();
        c2.setName("数学");
        Course c3=new Course();
        c3.setName("英语");

        //因为设置了关联关系由Student维护,所以不需要课程再来关联Student
        s1.getCourses().add(c1);
        s1.getCourses().add(c2);
        s1.getCourses().add(c3);

        s2.getCourses().add(c1);
        s2.getCourses().add(c2);
        s2.getCourses().add(c3);

        //由于设置了级联保存,所以只保存Student即可
        session.save(s1);
        session.save(s2);

        ts.commit();
        session.close();
        sessionfactory.close();
    }
}

HQL检索

使用面向对象的HQL查询语言。Hibernate提供了Query接口,它是专门的HQL查询接口,能执行各种复杂的HQL语句。

HQL是面向对象的查询语言,和SQL查询语言有些类似,Hibernate提供的各种检索方式中,HQL是使用最广的一种检索方式。HQL封装了JDBC的细节

HQL和SQL本质是不一样的: 
    -HQL查询语言面向对象,Hibernate负责解析HQL语句,然后根据映射文件,把HQL语句翻译成SQL语句。HQL查询语句中主体是类和类的属性。 
    -SQL查询语言是和关系数据库绑定在一起的,SQL查询语句中主体是数据库表和表的字段。

HQL查询的步骤
-通过session的createQuery(HQL语句)方法创建一个Query对象
-调用Query的list()方法执行查询语句。该方法返回List集合

/**查找所有对象**/ 
//HQL语句是面向对象的,from 类名 
                        List<Customer> customerlist=session.createQuery("from Customer").list();
/**HQL语句使用别名**/
List< Customer > customerlist=session.createQuery(“from Customer as c”).list();

as关键字用来设置别名,as也可以省略。 from Customer c即可。
/**HQL语句选择查询**/
    选择查询是指仅查找某些属性。
        这时list()返回的集合中包括的是一个一个数组,每个数组中是一条记录
List <Object[]> customerlist=session.createQuery("select id,name from Customer").list(); 
        for(Object[] obj:customerlist){
            System.out.println(Arrays.toString(obj));
        } 
/**HQL语句投影查询
基于选择查询的基础上,把查询到的属性封装到对象中,该对象的其他属性为null。
        该对象必须有对应的构造方法。*/
  List <Customer> customerlist=session.createQuery("select new Customer(id,name) from Customer").list();
        for(Customer c:customerlist){
            System.out.println(c.toString());
        }

        Customer类中必须有如下构造方法     
        public Customer(Integer id, String name) {
            this.id = id;
            this.name = name;
        }
/**HQL对查询结果排序
HQL语言使用order by关键字对结果进行排序。
    asc为升序,desc为降序。*/
List <Customer> customerlist=session.createQuery("select new Customer(id,name) from Customer order by id desc").list(); 
/**HQL分页查询
setFirstResult(int index):设定从第几个对象开始检索,起始值为0。
        -setMaxResult(int count):设定一次检索的对象数目。默认情况下检索所有的对象。*/

            Query query=session.createQuery("select id,name from Customer");
            //从第一个对象开始
            query.setFirstResult(0);
            //查找两个对象
            query.setMaxResults(2);
            List result=query.list();
/**HQL语句中绑定参数
对于实际应用中,经常需要用户输入一些查询条件,我们返回符合查询条件的数据。
我们可以使用from Customer where name='"+name+"';来实现
但是这种方式是非常不安全的,会受到SQL注入等非法攻击。
Hibernate中使用参数绑定来避免以上问题。有两种方式 */
 (1)使用?占位符绑定
                        占位符的索引从0开始。
                        Query query=session.createQuery("from Customer where id=?");
                        query.setInteger(0, 7);

                        Customer c=(Customer) query.uniqueResult();
                        System.out.println(c.toString());

                        Query提供了各种绑定数据类型的参数的方法setXxx(),如果参数为字符串类型,就调用setString(),如果参数为整数类型,就调用setInteger()等等。
                        这些setXxx()方法第一个参数代表占位符的索引,第二个代表参数值。

        (2) 使用参数名字绑定
                        在HQL中使用命名参数,命名参数以":"开头。
                        Query query=session.createQuery("from Customer where id=:a");
                        query.setInteger("a", 7);

                        Customer c=(Customer) query.uniqueResult();
                        System.out.println(c.toString()); 

                        这些setXxx()方法第一个参数是命名参数的名字,第二个参数是值。
/**HQL查询单个对象  uniqueResult()方法:返回单个对象  */    
                            Query query=session.createQuery("from Customer where id=1");
                Customer c=(Customer) query.uniqueResult();                                 

**HQL聚合函数和分组查询 **

        (1)HQL调用聚合函数
                -查询customer表所有记录数目
                            Query query=session.createQuery("select count(*) from Customer"); 
                            //该语句返回long类型
                            Long c=(Long) query.uniqueResult();
                            System.out.println(c.intValue());

                -查询最大ID
                            Query query=session.createQuery("select Max(id) from Customer");
                            Integer c=(Integer) query.uniqueResult();
                            System.out.println(c.intValue());


        (2)分组查询
                group by子句用来分组查询。

                -根据id分组,统计相同id的数目
                            Query query=session.createQuery("select id,count(*) from Customer group by id");
                            List<Object[]> list=query.list();
                            for(Object[] obj:list){
                                System.out.println(Arrays.toString(obj));
                            }

                -having子句用于为分组查询加上条件
                            Query query=session.createQuery("select id,count(*) from Customer group by id having id>8");
                            List<Object[]> list=query.list();
                            for(Object[] obj:list){
                                System.out.println(Arrays.toString(obj));
                            }

HQL连接查询

        -交叉连接:返回被连接的两个表所有数据行的笛卡儿积
        如果A表有5行记录,B表有7行记录,返回的结果就有35行记录
        显然,会产生很多没有意义的数据。

                Query query=session.createQuery("from Customer,Order");
                List<Object[]> list=query.list();
                for(Object[] obj:list){
                    System.out.println(Arrays.toString(obj));
                }

        -隐式内连接
            在交叉连接基础上,通过条件来过滤一些无意义的数据,达到内连接的效果。
                Query query=session.createQuery("from Customer c,Order o where o.customer=c");
                List<Object[]> list=query.list();
                for(Object[] obj:list){
                    System.out.println(Arrays.toString(obj));
                }

        -显式内连接
            使用inner join关键字表示内连接。inner可以省略,只使用join。
            调用list()方法返回的集合中存放的是每个元素对应的记录,每个元素都是数组类型。

                Query query=session.createQuery("from Customer c inner join c.orders"); 
                List<Object[]> list=query.list();
                for(Object[] obj:list){
                    System.out.println(Arrays.toString(obj));
                }


        -迫切内连接
            使用inner join fetch关键字表示迫切内连接。
            调用list()方法返回的集合中存放的是Customer对象的引用,每个Customer对象的Orders集合都被初始化,存放所有关联的Order对象。

                Query query=session.createQuery("from Customer c inner join  c.orders");
                List <Object> list=query.list();
                for(Object obj:list){
                    System.out.println(obj);
                } 

        -左外连接
            在连接查询中,连接左端的表中的所有的行全部显示,并且能在右端的表中找到匹配的行,如果右端表中没能找到左端匹配的行,则对应NULL.
            使用left join关键字表示左外连接。
            返回的list集合中存放的是多个对象数组。   
            Query query=session.createQuery("from Customer c left join  c.orders");
            List<Object[]> list=query.list();
            for(Object[] obj:list){
                System.out.println(Arrays.toString(obj));
            }

        -迫切左外连接
            使用left join fetch关键字表示迫切左外连接。
            list()方法返回的集合中存放的是Customer对象的引用,每个Customer的Orders集合都被初始化,存放关联的Order对象。
                Query query=session.createQuery("from Customer c left join fetch c.orders");
                List<Object> list=query.list();
                for(Object obj:list){
                    System.out.println(obj);
                }

        -右外连接
            和左外连接一样,连接右端表中的行全部显示,连接左端找到匹配的行,如果未能找到匹配的行,则用NULL代替
            使用right join关键字表示右外连接。
            Query query=session.createQuery("from Customer c right join  c.orders");
            List<Object[]> list=query.list();
            for(Object[] obj:list){
                System.out.println(Arrays.toString(obj));
            }

        -迫切右外连接
            Query query=session.createQuery("from Customer c right join fetch  c.orders");
            List<Object> list=query.list();
            for(Object obj:list){
                System.out.println(obj);
            }

在映射文件中定义命名查询语句

前面的例子中,HQL查询语句都写在程序代码中。Hibernate允许在映射文件中定义字符串形式的查询语句。

            <class name="com.cad.domain.Customer" table="customer" >
                <id name="id" column="id">
                    <generator class="native"></generator>
                </id>
                <property name="name" column="name"></property>
                <set name="orders" batch-size="3" fetch="subselect">
                    <key column="cid"/>
                    <one-to-many class="com.cad.domain.Order" />    
                </set>
                <!--定义局部命名查询语句-->
                <query name="bcd"><![CDATA[from Order]]></query>
            </class>
            <!--定义全局命名查询语句-->
            <query name="abc"><![CDATA[from Customer]]></query>

            //调用全局的命名查询语句
            Query query=session.getNamedQuery("abc");
            //调用局部的命名查询语句
            Query query=session.getNamedQuery("com.cad.domain.Customer.bcd");
            List<Object> list=query.list();
            for(Object obj:list){
                System.out.println(obj);
            }

QBC检索方式

使用QBC(Query By Criteria)API检索对象。封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口。

QBC检索方式的步骤

  1. 调用Session的createCriteria()方法创建一个Criteria对象
  2. 设定查询条件
  3. 调用Criteria接口的list()方法执行查询语句。返回List集合。

(1) 查询所有对象

            //传递类名.class即可
            Criteria criteria=session.createCriteria(Customer.class);
            List<Customer> l=criteria.list();
             for(Customer c:l){
                 System.out.println(c);
             }

(2)QBC对查询结果进行排序

        QBC使用org.hibernate.criterion.order类对查询结果排序。
        asc为升序,desc为降序。 

         Criteria criteria=session.createCriteria(Customer.class);
         criteria.addOrder(org.hibernate.criterion.Order.desc("id"));
         List<Customer> l=criteria.list();
         for(Customer c:l){
             System.out.println(c);
         }

(3)QBC分页查询

    QBC的分页查询和HQL类似。Criteria接口也提供了方法。
            -setFirstResult(int index):设定从第几个对象开始检索,起始值为0。
            -setMaxResult(int count):设定一次检索的对象数目。默认情况下检索所有的对象。

                 Criteria criteria=session.createCriteria(Customer.class);
                 criteria.setFirstResult(0);
                 criteria.setMaxResults(2);
                 List<Customer> l=criteria.list();
                 for(Customer c:l){
                     System.out.println(c);
                 }1234567891011

**(4)QBC设定查询条件 **

    必须创建一个Criterion对象来设定查询条件。Restrictions类提供了创建Criterion的方法。
            -Restrictions.eq()  等于
            -Restrictions.ne() 不等于
            -Restrictions.gt()  大于
              ..........等等很多方法 

        Criteria criteria=session.createCriteria(Customer.class);
        //查询id为7的Customer
         Criterion c1=Restrictions.eq("id", 7);
         criteria.add(c1);
         Customer c=(Customer) criteria.uniqueResult();
         System.out.println(c);

**(5)QBC检索单个对象 **

        uniqueResult():返回单个对象1

本地SQL检索方式**

    可以使用原生的SQL语句来进行查询。
    本地SQL检索也是使用Query接口,通过Session的createSQLQuery(SQL语句)方法来创建Query。

        例子:
            Query query =session.createSQLQuery("select * from customer");
             List<Object[]> list=query.list();
             for(Object[] o:list){
                 System.out.println(Arrays.toString(o));
             } 

            默认情况下,SQLQuery返回的list集合中存放的是关系数据。每个元素都是Object[]数组。

        -addEntity()方法能把查询结果中每一行的数据封装成对象

                Query query =session.createSQLQuery("select * from customer").addEntity(Customer.class);
                 List<Customer> list=query.list(); 
                 for(Customer o:list){
                     System.out.println(o.toString());
                 }

Hibernate处理事务并发问题

在Hibernate中设置事务的隔离级别。
<property name="hibernate.connection.isolation">2</property>

隔离级别代号。
1:Read Uncommitted
2: Read Committed
4: Repeatable Read 
8: Serializable

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
    随着ICT技术不断深化和传统场景的结合,自动化、智能化相融合已经成为ICT技术的下一代浪潮,以工业机器人、服务机器人、自动驾驶等的新一代技术浪潮目前已经方兴未艾,成为又一波热点。而作为次级操作系统的ROS则提供了一系列的调试、仿真工具,能支持激光导航、视觉导航等算法,是切入机器人和自动驾驶技术的重要基石。    机器人是一个复合型的领域,涉及软件、算法、硬件、结构等诸多方向,是一个理论性和实践性并重的行业,考虑到其复杂性和讲解的循序渐进,整个系列课程将分为三个系列即入门系列、中级系列、高级系列。   在入门系列中,我们将介绍ROS的基础知识(机器人基础知识、ROS开发环境等)、ROS配置管理(系统架构、参数管理、Launch启动、编译配置以及如何基于源代码开发等)、ROS系统调试(代码调试、可视化调试、消息回放、单元测试等)。   在中级系列中,我们将围绕机器人仿真涉及的URDF机器人模型、Gazebo仿真环境、坐标变换、运动控制等展开介绍,并随着课程的深入,将会深度使用RVIZ、Gazebo等仿真和调试工具。   在高级系列中,我们将着眼于人工智能框架及算法,分享机器人开发涉及的运动控制、SLAM、语音交互、计算机视觉等。逐层递进,为大家一层层剥开机器人的神秘面纱。    本系列课程的特色在于:    1、从基础知识、编译管理、通信机制、系统调试等4个方面循序渐进、逐步深化,知识覆盖全面,便于深度认知;    2、从基本理论、源码解读、工程示例等领域开展ROS系统入门知识的深度介绍和分析,源于工程实践,利于快速上手;   3、基于全新的环境Ubuntu 20.04、ROS Noetic、Gazebo 11、GMapping、Cartographer、tensorFlow 2.*、OpenCV 4.*等讲解,紧跟时代前沿。   整个系列的课程将会逐步开发并上线,三个系列是一个逐步深入、环环相扣的课程内容,感兴趣的同学可以开始学习啦。  

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值