美文网首页
Hibernate入门1-基本用法

Hibernate入门1-基本用法

作者: sunblog | 来源:发表于2018-05-09 01:30 被阅读0次

    Hibernate 快速入门1 - 基本用法

    Hibernate简介

    ORM:对象关系映射(ORM, Object Relational Mapping)。用于实现面向对象编程语言里不同类型系统的数据之间的转换。

    Hibernate: Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行。

    一句话:让程序员以面向对象的方式访问数据库。

    注意:本文假设你对JDBC和Java反射有基本的了解,熟悉基本MySQL操作。

    0 下载

    // hibernate我使用的是hibernate-release-5.2.16.Final

    hibernate下载:http://hibernate.org/orm/releases/

    mysql驱动:https://dev.mysql.com/downloads/connector/j/

    common-logging: http://commons.apache.org/proper/commons-logging/

    解压hibernate,将/path-to-hibernate/lib/required、mysql驱动所在目录和common-logging 所在目录添加到classpath。

    在Intellij Idea中,可以在Edit -> Project Structure -> Libraries中配置。

    1.1 简单例子

    先考虑下,传统访问数据库的方式是:编写SQL,获取数据,赋值给对象。Hibernate要做的就是这一步。

    先把数据库配置好:

    @ mysql -u root -p
    @ CREATE USER hibernate IDENTIFIED BY 'hibernate123';
    @ CREATE DATABASE hibernatedb;
    @ USE hibernatedb;
    @ GRANT ALL ON hibernatedb.* TO hibernate;
    

    上面在MySQL中为本文建立了一个用户: hibernate ,密码是: hibernate123 ,数据库是: hibernatedb

    首先来看一个例子:

    // src/com/example/test/Student.java
    package com.example.test;
    
    import javax.persistence.*;
    
    @Entity
    @Table(name = "student_info")
    public class Student {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
    
         @Column(name = "name")
        private String name;
    
        private int age;
        
        @Transient
        private boolean olderThan18;
    
        @Override
        public String toString() {
            return "id: " + id + ", name: " + name + ", age: " + age;
        }
    
        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 int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    
    // src/com/example/test/HibernateTest.java
    package com.example.test;
    
    // user: hibernate, pass: hibernate123, db:hibernatedb
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.Transaction;
    import org.hibernate.boot.registry.StandardServiceRegistry;
    import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
    import org.hibernate.cfg.Configuration;
    
    public class HibernateTest {
        public static Integer addStudent() {
            Configuration conf = new Configuration().configure();
            StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
            try (SessionFactory sessionFactory = conf.buildSessionFactory(registry);
                 Session session = sessionFactory.openSession()) {
                Transaction transaction = session.beginTransaction();
    
                Student student = new Student();
    
                student.setAge(18);
                student.setName("robert");
    
                Integer id = (Integer) session.save(student);
    
                transaction.commit();
    
                return id;
            }
        }
    
        public static void searchStudent(Integer id) {
            if (id != null) {
                Configuration conf = new Configuration().configure();
                StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
                try (SessionFactory sessionFactory = conf.buildSessionFactory(registry);
                     Session session = sessionFactory.openSession()) {
                    Transaction transaction = session.beginTransaction();
    
                    Student student = session.get(Student.class, id);
    
                    if (student != null) {
                        System.out.println(student);
                    }
    
                    transaction.commit();
                }
            }
        }
    
        public static void main(String[] args) {
            Integer id = addStudent();
            searchStudent(id);
        }
    }
    
    

    src/hibernate.cfg.xml

    <?xml version='1.0' encoding='utf-8'?>
    <!--
      ~ Hibernate, Relational Persistence for Idiomatic Java
      ~
      ~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
      ~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
      -->
    <!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 -->
            <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
            <!--后面根据tomcat,修改成根据JDNI来访问。参考java ee 企业应用实战 p393-->
            <property name="connection.url">jdbc:mysql://localhost:3306/hibernatedb</property>
            <property name="connection.username">hibernate</property>
            <property name="connection.password">hibernate123</property>
            <property name="hibernate123"/>
            <!-- maximum number of connections -->
            <property name="hibernate.c3p0.max_size">20</property>
            <property name="hibernate.c3p0.min_size">1</property>
            <property name="hibernate.c3p0.max_statements">30</property>
            <property name="hibernate.c3p0.idle_test_period">3000</property>
            <property name="hibernate.c3p0.acquire_increment">2</property>
            <property name="hibernate.c3p0.valid">true</property>
    
            <!-- SQL dialect -->
            <!--caution. it's not MySQLInnoDBDialect, but MySQL5InnoDBDialect !!-->
            <property name="dialect">org.hibernate.dialect.MySQL55Dialect</property>
            <property name="hbm2ddl.auto">update</property>
    
            <!-- Echo all executed SQL to stdout -->
            <property name="show_sql">true</property>
    
            <property name="hibernate.format_sql">true</property>
    
            <mapping class="com.example.test.Student"/>
        </session-factory>
    
    </hibernate-configuration>
    

    下面运行程序,可以看到console有部分输出为:

    Hibernate: 
        select
            student0_.id as id1_0_0_,
            student0_.age as age2_0_0_,
            student0_.name as name3_0_0_ 
        from
            student_info student0_ 
        where
            student0_.id=?
    id: 1, name: robert, age: 18
    

    前面是Hibernate生成的SQL语句。id: 1, name: robert, age: 18就是我们新建到数据库中数据。

    mysql> show tables;
    +-----------------------+
    | Tables_in_hibernatedb |
    +-----------------------+
    | student_info          |
    +-----------------------+
    7 rows in set (0.01 sec)
    
    mysql> select * from student_info;
    +----+-----+--------+
    | id | age | name   |
    +----+-----+--------+
    |  1 |  18 | robert |
    +----+-----+--------+
    1 row in set (0.00 sec)
    

    这表明数据库中已经有一个名为student_info的表,表中有我们插入的元组。

    1.2 基本使用

    下面来讲解一下hibernate的基本使用。

    1.2.1 类的注解

    拿前面例子来说明。

    @Entity: 标识这个类想要被映射到数据库中。

    @Table: 指定表的名字。如果省略@Table,表的名字则是这个类的类名(the unqualified class name)。这里表的名字是student_info。如果上面省略@Table,表名是Student,并不是com.example.test.Student。

    @Id:标识这个字段用作主键(primary key)。可以把多个字段都用@Id标识,对应属性的组合构成了主键。多个@Id不是JPA(Java Persistence API)所支持的。JPA自己查。这里就只用一个字段。

    @GeneratedValue: 表示主键生成的策略。比如自增还是生成一个UUID(这里是Integer不能用UUID)。strategy = GenerationType.IDENTITY 生成策略是由数据库自动生成。例子中是自增。其他策略(比如GenerationType.AUTO等)请参考文档。

    @Column: 设置属性在数据库表中的名字。如果省略,就是属性全部小写的名字。

    @Transient: 表示该属性不应该被实例化。

    最重要的:Student是一个bean。它为所有需要让外部访问的成员变量都提供了setter和getter。除了static和@transient标记的属性外,其他属性所有不管有没有annotation,都会被持久化。

    请查看官方文档,看这些注解还可以设置哪些属性。

    也就是如下:

    @Entity
    public class Student {
        private String hometown;    // will be persistent too.
    };
    

    1.2.2 配置文件

    除了要在源代码中用注解指出哪些class需要被映射外,我们还要配置文件。Hibernate的配置文件是hibernate.cfg.xml。基本的数据库配置就像上面举例的一样,数据库的用户名,密码,db名字。hibernate.c3p0.这些属性是数据库连接*的一些基本信息,看名字基本能看懂。不懂的查阅文档。

    为了让hibernate能根据不同的数据库进行优化,我们还要设置数据库的种类,我用的是MySQL5,所以设置方言为MySQL5. <property name="dialect>org.hibernate.dialect.MySQL55Dialect</property> 其他的数据库怎么设置请查阅文档。

    <property name="hbm2ddl.auto">update</property>是让hibernate根据需要更新数据库(包含了创建)。hbm2ddl.atuo还可以有其他值,请查阅温度。注意,这个值最好仅在开发测试的时候使用,正式环境中请勿使用这个property。

    配置最下面<mapping/>指定,哪些类需要被映射。这个容易被忽略

    1.2.3 SessionFactory

    有了被注解的类,有了配置文件,我们还要进行一系列的操作。

    1. 加载配置
    2. 启动
    3. 调用SessionFactory得到一个Session
    4. 开始事务
    5. 程序逻辑处理
    6. 结束事务
    7. 关闭Session和SessionFactory

    分别如下:

    Configuration conf = new Configuration().configure();   // 默认加载src/hibernate.cfg.xml
    StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();    // 启动   
    try (SessionFactory sessionFactory = conf.buildSessionFactory(registry);
         Session session = sessionFactory.openSession()) {  // 打开得到session
        Transaction transaction = session.beginTransaction();   // 开始事务
    
        Student student = session.get(Student.class, id);   // 逻辑处理
    
        if (student != null) {
            System.out.println(student);
        }
    
        transaction.commit();   // 结束事务
    }   // 关闭Session和SessionFactory
    

    注意:要正确关闭Session和SessionFactory,不然程序退出不了。

    我们对数据库的CRUD操作都是先打开一个事务,然后通过Session来进行。即使只查询,不修改数据库,事务也是必不可少的。具体原因请看这里https://stackoverflow.com/questions/13539213/why-do-i-need-transaction-in-hibernate-for-read-only-operation

    1.2.4 对象的状态

    对象在Hibernate中有如下几个状态:

    • transient: never persistent, not associated with any Session
    • persistent: associated with a unique Session
    • detached: previously persistent, not associated with any Session

    暂时分别翻译为:瞬态,持久态,脱管态。

    瞬态的意思是,这个对象的被new以后,从来没有被持久化到数据库中。

    持久态的意思是,这个对象和一个session是相关联的。比如刚从数据库中取出来:调用session.get()

    脱离态的意思是,这个对象被持久化过,但现在没有和一个session相关联。比如对持久态的对象调用:obj.evict,或者session.close()。

    如下:

    hibernate_stateshibernate_states

    1.2.4 基本函数

    public interface Session {
    Serializable save(Object object);
    void saveOrUpdate(Object object);
    void persist(Object object);
    <T> T load(Class<T> theClass, Serializable id);
    <T> T get(Class<T> entityType, Serializable id);
    void update(Object object);
    void delete(Object object);
    void evict(Object object);
    // ... other methods
    }
    

    保存:save、saveOrUpdate和persist返回的是生成的主键(the generated identifier)。它们主要是把瞬态的对象持久化。其中save会返回生成的主键。在我这个版本的hibernate,对于处于持久态的对象,不管对象是否有更新,再次调用save后,似乎数据库都是一样的。

    查询: load和get。区别是如果数据库中,没有id对应的记录,那么load会抛出异常,get会返回null。

    更新:update只能作用于处于持久态和脱离态的对象上。对于脱离态的对象,这个操作还会把对象变为持久态。

    删除:delete。很简单,只能删除处于持久态的对象。

    evict: 把对象从持久态转化为脱离态。

    还有一些其他函数,请参考官方文档。

    1.3 映射集合属性

    如果一个属性有多个地址,也就是Student的属性为一个集合的时候。怎么办呢?

    1.3.1 映射 list, set, array

    首先,为了避免数据冗余,这些bean属性的集合,肯定要保存在另外一个表中。比如(id, name, addressList),如果addressList让然和student保存在一个表中,就会产生冗余: (1, "robert", "beijing"), (1, "robert", "shanghai")。 后面MySQL的输出也验证了这个想法。

    映射list(数组和list一样):

    @Entity
    @Table(name = "student_info")
    public class Student {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
    
        @Column(name = "name")
        private String name;
    
        private int age;
        
        @ElementCollection(targetClass = String.class)  // element class type
        // name = "addresses": name of table contain elements of list is address.
        // joinColumns = @JoinColumn(..): FOREIGN KEY (student_id) REFERENCES Student (id). 
        // In fact, we can rename Student.id by using @Column(name = "student_id"). 
        // And in such case we can omit joinColumns here. 
        @CollectionTable(name = "addresses", joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "id"))
        @Column(name = "address", nullable = false) // name of column representing elements of the set
        @OrderColumn(name = "addr_id")
        private List<String> addressList;
    
        // getters and setters
    }
    

    HibernateTest.java.addStudent

    session.beginTransaction();
    
    Student student = new Student();
    student.setName("robert");
    student.setAge(18);
    List<String> addrs = new ArrayList<>(2);
    addrs.add("bj");
    addrs.add("sh");
    student.setAddressList(addrs);
    
    session.save(student);
    
    session.getTransaction.commit();
    

    数据库的输出:

    mysql> desc addresses;
    +------------+--------------+------+-----+---------+-------+
    | Field      | Type         | Null | Key | Default | Extra |
    +------------+--------------+------+-----+---------+-------+
    | Student_id | int(11)      | NO   | PRI | NULL    |       |
    | address    | varchar(255) | NO   |     | NULL    |       |
    | addr_id    | int(11)      | NO   | PRI | NULL    |       |
    +------------+--------------+------+-----+---------+-------+
    3 rows in set (0.00 sec)
    
    mysql> select * from addresses;
    +------------+---------+---------+
    | Student_id | address | addr_id |
    +------------+---------+---------+
    |          7 | bj      |       0 |
    |          7 | sh      |       1 |
    +------------+---------+---------+
    2 rows in set (0.00 sec)
    
    mysql> show create table addresses;
    CREATE TABLE `addresses` (
      `student_id` int(11) NOT NULL,
      `address` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
      `addr_id` int(11) NOT NULL,
      PRIMARY KEY (`student_id`,`addr_id`),
      CONSTRAINT `FK37pobof33ovjb9oxnt205houy` FOREIGN KEY (`student_id`) REFERENCES `student_info` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
    

    映射集合用@ElementCollection注解。

    还有个@CollectionTable注解,name字段指定指定集合对应的表的名字,joinColumns是一个数组类型,它的元素类型为@JoinColumn.

    @JoinColumn:专门用来指定外键,它的name属性指定它所属于的表对应的名字,referencedColumnName指定被引用表的属性名。举个例子: @JoinColumn(name = "student_id", referencedColumnName = "id")表示在@JoinColumn所在的表建立一个属性student_id, 同时建立一个外键约束: FOREIGN KEY (student_id) REFERENCES student_info (id);

    @OrderColumn(name = "addr_id")表示addr_id这个列用来保存元素的顺序。

    可以在MySQL输出中看到的确建立了对应的属性和外键约束。 addresses.student_id <-> student_info.id。需要注意的是,addresses上的外键约束,默认是拒绝更改。意思是,如果删除student_info中id=7的行,这个操作会被拒绝。如下:

    mysql> delete from student_info where id = 7;
    ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`hibernatedb`.`addresses`, CONSTRAINT `FK37pobof33ovjb9oxnt205houy` FOREIGN KEY (`student_id`) REFERENCES `student_info` (`id`))
    

    如果我们通过hibernate删除id = 7 的Student,那么他对应的address确可以被删除。

    Integer id = 7;
    Transaction transaction = session.beginTransaction();
    
    Student student = session.get(Student.class, id);
    
    if (student != null) {
        System.out.println("deleting " + student);
        session.delete(student);
    }
    
    transaction.commit();
    

    数据库的输出:

    mysql> select * from addresses;
    Empty set (0.00 sec)
    

    这是为什么呢?仔细查看控制台,我们发现两条sql语句:

    deleting id: 7, name: robert, age: 18, addrs: [bj, sh]
    Hibernate: 
        delete 
        from
            addresses 
        where
            student_id=?
    Hibernate: 
        delete 
        from
            student_info 
        where
            id=?
    

    这下非常明显了。先删除了addresses表中 id = 7的行,再删除student_info中id = 7的行。

    映射set的话,和上面类似,只是不能再用@OrderColumn。

    当我们有疑惑的时候,仔细查看hibernate控制台的输出是一个好方法。

    1.3.2 映射map

    public class Student {
        // other code
        @ElementCollection(targetClass = Float.class)
        @CollectionTable(name = "subject_scores", joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "id"))
        @MapKeyClass(String.class)
        @MapKeyColumn(name = "subject")
        @Column(name = "score")
        private Map<String, Float> scores; // K: name of subject. V: score for the subject
    }
    

    和list映射的区别是,多了MapKeyClass和MapKeyColumn,分别代表map的key所属的类和key在表中的属性名。@ElementCollection(targetClass = String.class)表示value所属的类,@CollectionTable(name = "subject_scores")这个name表示的value在表中的名字。

    transaction.beginTransaction();
    Student student = new Student();
    
    // ... 
    Map<String, Float> subjects = new HashMap<>(2);
    subjects.put("english", 90.0f);
    subjects.put("math", 95.5f);
    student.setScores(subjects);
    
    sesssion.save(student);
    
    transaction.commit();
    

    数据库输出如下:

    mysql> select * from subject_scores;
    +------------+-------+---------+
    | student_id | score | subject |
    +------------+-------+---------+
    |          9 |    90 | english |
    |          9 |  95.5 | math    |
    +------------+-------+---------+
    2 rows in set (0.00 sec)
    
    mysql> desc subject_scores;
    +------------+--------------+------+-----+---------+-------+
    | Field      | Type         | Null | Key | Default | Extra |
    +------------+--------------+------+-----+---------+-------+
    | student_id | int(11)      | NO   | PRI | NULL    |       |
    | score      | float        | YES  |     | NULL    |       |
    | subject    | varchar(255) | NO   | PRI | NULL    |       |
    +------------+--------------+------+-----+---------+-------+
    3 rows in set (0.00 sec)
    

    1.3.3 集合的性能

    考虑这样一个情景:我们获取通过hibernate获取一个对象,该对象有个集合属性。集合包含了几万条数据。但在我们当前的业务当中,并不会访问该集合属性。如果hibernate在获取对象的时候,也从集合属性对应的表中取出所有元组并添加到集合里面,那hibernate的这种操作无疑是非常浪费内存的。幸运的是,hibernate的针对集合属性,默认采取延迟加载策略,只有在我们访问该集合属性的时候,才会从数据库中加载相关数据。如果我要改变默认的策略,我们可以为集合属性指定FetchType如下:

    //...
    @ElementCollection(targetClass = String.class, fetch = FetchType.EAGER) 
    private List<String> list;
    

    延迟加载对应的属性是: FetchType.LAZY

    1.4 集合元素是自定义的bean(不是int, String等)

    这种情况下,我们把作为元素的自定义的bean用@Embeddable修饰,把该bean称作包含类的组合属性。其他的不变:

    @Embeddable
    public class BodyInfo {
        private int height;
        private int weight;
    }
    
    public class Student {
        private BodyInfo bodyInfo;
        // ...
    }
    
    // ...
    BodyInfo bodyInfo = new BodyInfo();
    bodyInfo.setHeight(178);
    bodyInfo.setWeight(70);
    
    student.setBodyInfo(bodyInfo);
    Integer id = (Integer) session.save(student);
    
    transaction.commit();
    

    sql输出:

    mysql> select * from student_info;
    +----+-----+--------+--------+--------+
    | id | age | name   | height | weight |
    +----+-----+--------+--------+--------+
    |  9 |  18 | robert |      0 |      0 |
    | 10 |  18 | robert |    178 |     70 |
    +----+-----+--------+--------+--------+
    6 rows in set (0.00 sec)
    

    注意和1.1中的student_info输出对比,是不是多了2个列:height,weight。这两列也恰巧是自定义bean BodyInfo的两个属性。

    这说明了hibernate自动把BodyInfo中的属性(必须是非集合)添加到了student_info表中。查看控制台:

    Hibernate: 
        
        alter table hibernatedb.student_info 
           add column height integer not null
    Hibernate: 
        
        alter table hibernatedb.student_info 
           add column weight integer not null
    Hibernate: 
        insert 
        into
            student_info
            (age, height, weight, name) 
        values
            (?, ?, ?, ?)
    

    hibernate为student_info表增加了两个属性!!很智能吧。但需要注意的是,这种对数据库表结构的操作最好不要在生产环境中发生(可以删除配xml置文件中的hbm2ddl.auto的属性)。

    如果BodyInfo中有集合属性怎么办?我们可以猜想,BodyInfo中的那些非集合属性仍然会添加到student_info表中,那些集合属性,会像前面一样,用单独的表来存储。这里就不验证了。

    1.5 使用xml来配置映射

    我们可以用xml来配置属性关系,从而不用注解。

    比如如下:
    src/com/example/test/Student.java

    public class Student {
        private Integer id;
        private String name;
        
        // getter and setters
    }
    

    src/com/example/test/Student.hbm.xml

    <?xml version='1.0' encoding='utf-8'?>
    <!--
      ~ Hibernate, Relational Persistence for Idiomatic Java
      ~
      ~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
      ~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
      -->
    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    
    <hibernate-mapping package="com.example.test">
        <class name="Student" table="student_info">
            <id name="id" type="java.lang.Integer" column="id"/>
            <property name="name" type="java.lang.String" column="name"/>
        </class>
    
    </hibernate-mapping>
    

    src/hibernate.cfg.xml

    <!-- some other settings -->
    <session-factory>
        <!--<mapping class="com.example.test.Student"/>-->
        <mapping resource="com/example/test/Student.hbm.xml"/>
    </session-factory>
    

    现在用注解的比较多。

    相关文章

      网友评论

          本文标题:Hibernate入门1-基本用法

          本文链接:https://www.haomeiwen.com/subject/apbgrftx.html