学习笔记_Hibernate

Hibernate

原文

1、学习网址

2、简介

2.1、ORM

[概念] 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

2.2、创始人

Gavin King是Hibernate的创始人,EJB3.0专家委员会成员,JBoss核心成员之一,也是《Hibernate in Action》一书的作者。Gavin King1974年出生于一个名叫Mudgee的小镇,居住在澳大利亚Melbourne市。Gavin对于做事情的执着始于十一岁时,他最早的软件开发成果毁于极其糟糕、不稳定的Commodore 64磁带驱动。Gavin King曾在Monash大学攻读数学,2003年9月,Gavin King加入了JBoss,全面领导hibernate的开发,并给客户提供好的服务和培训。Gavin King在红帽公司负责JBoss Hibernate 和基于CMP 引擎的新Hibernate项目的开发。 [1]

2.3、用途

  1. 解决阻抗不匹配的问题
  2. 将对象模型存入关系模型中
  3. 实体类对应关系型中的一个表
  4. 实体类的一个属性对应关系型数据库表中的一个列
  5. 实体类的一个实例对应关系型数据库表中的一条记录
  6. 简化编程

2.4、优缺点

  1. 无需编写SQL,操作面向对象,提高生产效率
  2. 开发对象化
  3. 移植性好(更换数据库时,只需更改相应配置文件)
  4. 透明持久化(对象无需继承框架任何类或接口)
  5. 轻量级框架(无侵入性)
  6. 测试方便

3、对象关系映射

3.1、对象-关系映射模式

  • 属性映射
  • 类映射
  • 关联映射
  • 一对一
  • 一对多
  • 多对多

3.2、常用的O/R映射框架

  • Hibernate
  • ApacheOJB
  • JDO
  • Toplink
  • EJB
  • IBatis
  • JAP

4、基础配置

4.1、依赖包配置(Maven)

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate </groupId>
            <artifactId> hibernate-entitymanager</artifactId>
            <version>5.2.1.final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate </groupId>
            <artifactId>hibernate-ehcache </artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate </groupId>
            <artifactId>hibernate-validator </artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.9.0</version>
        </dependency>

4.2、日志环境

1、maven配置log4j和slf4j

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.14</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.2</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

2、创建log4j配置文件log4j.properties,,并设:log4j.logger.org.hibernate.tool.hbm2ddl = debug

4.3、JUnit环境

1、环境配置

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

2、创建测试类

    public class UserTest {
        private static SessionFactory sf = null;

        @BeforeClass  // 表示JUnit此类被加载到内存中就执行这个方法
        public static void beforeClass() {
            sf = new AnnotationConfiguration().configure().buildSessionFactory();
        }

        @Test
        public void testUserSave() {
            User user = new User();
            user.setName(str);
            Session session = sf.openSession();
            session.beginTransaction();
            session.save(t);
            session.getTransaction().commit();

            session.close();
        }


        @AfterClass  // JUnit在类结果时,自动关闭
        public static void afterClass() {
            sf.close();
        }
    }

3、输出SQL语句到控制台:log4j.logger.org.hibernate.SQL = debug或者在hibernate配置文件中设置true

4、SQL语句格式化输出:true
样式如下:

16:32:39,750 DEBUG SchemaExport:377 -   
    create table Teacher (  
        id integer not null,  
        name varchar(255),  
        title varchar(255),  
        primary key (id)  
 )

5、Annotation

5.1、实体类-表 映射

@Entity
@Table(name = "tableName")
public class User {

}

5.2. 属性-字段 映射

  1. 名称相同
    @Basic
  2. 名称不同
    @Column(name="_colname")

5.3. 属性不需持久化

    @Transient
    public String getName() {

    }

5.4. 时间类型属性

@Temporal(value=TemporalType)
其中TemporalType有三个值:

  • TemporalType.TIMESTAMP 表示yyyy-MM-dd HH:mm:ss
  • TemporalType.DATE 表示yyyy-MM-dd
  • TemporalType.TIME 表示HH:mm:ss
    @Temporal(value=TemporalType.DATE)
    public Date getDate() {
        return date;
    }

5.5. 枚举类型属性

  1. 枚举注解@Enumerated(value=EnumType)

其中EnumType有两个值:

  • EnumType.STRING 表示直接将枚举名称存入数据库
  • EnumType.ORDINAL 表示将枚举所对应的数值存入数据库
    1. @Convert
  • 如果想数据库保存的是枚举类的value值,应实现类型转换类
/**
* 枚举类型
*/
public enum Power {

    Normal("普通师生"),
    Checker("检查员"),
    Manager("管理员");

    private String value;
    private Power(String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }

    // 实现转换方法
    public static Power fromString(String value) {
        Objects.requireNonNull(value,"Power value can not be null");
        Power power = null;
        if("普通师生".equals(value)) {
            power = Normal;
        } else if("检查员".equals(value)) {
            power = Checker;
        } else if("管理员".equals(value)) {
            power = Manager;
        }
        return power;
    }

/**
* 转换器类
*/
@Converter
public class PowerTypeConvertor implements AttributeConverter<Power, String> {

    @Override
    public String convertToDatabaseColumn(Power attribute) {
        return String.valueOf(attribute);
    }

    @Override
    public Power convertToEntityAttribute(String dbData) {
        return Power.fromString(dbData);
    }
}

/**
* 在属性上使用
*/ 
@Column(name = "power", nullable = false, length = 4)
@Convert(converter=PowerTypeConvertor.class)
public Power getPower() {
    return power;
}

5.6. ID主键生成策略

  1. 主键注解:@Id

  2. 生成策略:

    @GeneratedValue(strategy=GenerationType)
    
    1. Strategy有四个值:
      1. AUTO - 默认值,可以是identity column类型或者sequence类型或者table类型,取决于不同的底层数据库,相当于native;
      2. TABLE - 使用表保存id值
      3. IDENTITY - identity column
      4. SEQUENCE - sequence

AUTO

  1. 对于MySQL使用auto_increment
  2. 对于oracle使用hibernate_sequence
@Id
@GeneratedValue
public int getId() {
    return id;
}

IDENTITY

  1. 对DB2,MySQL, MS SQL Server,Sybase和HypersonicSQL的内置标识字段提供支持。 返回的标识符是long, short 或者int类型的。 (数据库自增)
  2. 不支持Oracle
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() {
    return id;
}

SEQUENCE

  1. 在DB2,PostgreSQL,Oracle, SAP DB, McKoi中使用序列(sequence), 而在Interbase中使用生成器(generator)。返回的标识符是long, short或者 int类型的。(数据库自增)
  2. 不支持MySQL
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE)
public int getId() {
    return id;
}

为Oracle指定定义的Sequence

@Entity
@SequenceGenerator(name="teacherSEQ",sequenceName="teacherSEQ_DB")
public class Teacher {
    private int id;

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE,generator="teacherSEQ")
    public int getId() {
        return id;
    }
}

TABLE

  • 原理:就是在数据库中建立一个表,这个表包含两个字段,一个字段表示名称,另一个字段表示值。每次在添加数据时,使用第一个字段的名称,来取值作为添加数据的ID,然后再给这个值累加一个值再次存入数据库,以便下次取出使用。
  • 可跨数据库平台
@Entity
@javax.persistence.TableGenerator(
        name="Teacher_GEN",              //生成策略的名称
        table="GENERATOR_TABLE",         //在数据库生成表的名称
        pkColumnName = "pk_key",         //表中第一个字段的字段名类型为varchar,key
        valueColumnName = "pk_value",    //表中第二个字段的字段名 int ,value
        pkColumnValue="teacher",         //这个策略中使用该记录的第一个字段的值(key值)
        initialValue = 1,                //这个策略中使用该记录的第二个字段的值(value值)初始化值
        allocationSize=1                 //每次使用数据后累加的数值
)

public class Teacher {
    private int id;

    @Id
    @GeneratedValue(strategy=GenerationType.TABLE,generator="Teacher_GEN")
    public int getId() {
        return id;
    }
}

联合主键

  • 复合主键(联合主键):多字段构成唯一性

  • 复合主键的映射,一般情况把主键相关的属性抽取出来单独放入一个类中。而这个类是有要求的:必需实现序列化接口(java.io.Serializable)(可以保存到磁盘上),为了确定这个复合主键类所对应对象的唯一性就会产生比较,对象比较就需要复写对象的hashCode()、equals()方法(复写方法如下图片),然后在类中引用这个复合主键类。

  • 实现方法有:

    1. 将主键类注解为@Embeddable,并将主键的属性注解为@Id
@Embeddable
public class TeacherPK implementsjava.io.Serializable{
    private int id;
    private String name;

    public int getId() {return id;  }
    public void setId(int id) {this.id = id;}

    public String getName() {   return name;}
    public void setName(Stringname) {  this.name = name;}

    @Override
    public boolean equals(Object o) {}

    @Override
    public int hashCode() { return this.name.hashCode();}
}

@Entity
public class Teacher {

  private TeacherPK pk;
  private String title;

  @Id
  public TeacherPK getPk(){
      return pk;
  }
}
  1. 将主键的属性注解为@EmbeddedId,而主键类无需任何注释
@Entity
public class Teacher {

   private TeacherPK pk;
   private String title;  

   @EmbeddedId
   public TeacherPK getPk(){
       return pk;
   }
}
  1. 将类注解为@IdClass,并将该实体中所有属于主键的属性都注解为@Id只需建立主键类,而主键类无需注释
@Entity
@IdClass(TeacherPK.class)
public class Teacher {

  //private TeacherPK pk;  //不再需要

  private int id;
  private String name;   

  @Id
  public int getId() { return id;}
  public void setId(int id) { this.id = id;}

  @Id
  public String getName() { return name;}
  public void setName(Stringname) { this.name = name;}
}

5.7. 属性不参与更新

@Column(updatable = true)  // 或者false
public String getTitle(){ return title; }

5.8. cascade(级联)属性

img

【总结】一般是多对一和多对多的时候不使用级联,一对一和一对多可以使用级联,这两种情况使用级联比较多,总结来说,这个对象归你控制,你就能够级联,它不归你一个人所有,那你就不要级联

5.9. inverse属性

  • 【概念】在双向关系中,由inverse=false的一方负责维护关联关系,即负责的一方的持久化对象的相关操作会引发hibernate发出相关联的sql语句同步更新数据库

  • 【inverse和cascade区别】

    • inverse:负责控制关系,默认为false,也就是关系两端都能控制,但是会引发重复更新
    • cascade:负责控制关联对象的级联操作,包括更新、删除等。

6、Hibernate核心开发接口

6.1、 Configuration(AnnotationConfiguration)

  • 作用:进行配置信息的管理
  • 目标:用来产生SessionFactory
  • 加载hibernate配置文件:
    1. 默认加载classpath:/hibernate.cfg.xml
sessionFactory = newAnnotationConfiguration().configure().buildSessionFactory();

​ 2. 加载指定文件

sessionFactory = newAnnotationConfiguration().configure("hibernate.xml").buildSessionFactory();

6.2.、SessionFactory

  • 作用】:主要用于产生Session的工厂(数据库连接池),当它产生一个Session时,会从数据库连接池中取出一个连接,交给这个Session
Session session = sessionFactory.getCurrentSession();

关注两个方法:

  1. getCurrentSession(): 表示当前环境没有Session时,则创建一个,否则不用创建;
  2. openSession(): 表示创建一个Session(3.0以后不常用),使用后需要关闭这个Session

区别:

  1. openSession()永远是每次都打开一个新的Session,而getCurrentSession()不是,是从上下文找、只有当前没有Session时,才创建一个新的Session
  2. openSession()需要手动close,getCurrentSession()不需要手动close,事务提交自动close
  3. getCurrentSession()界定事务边界

概念

  1. 上下文:所指的上下文是指hibernate配置文件(hibernate.cfg.xml)中的“
  2. 可取值:jta | thread | managed | custom.Class
  3. 常用:thread/jta
  4. thread:是从上下文找、只有当前没有Session时,才创建一个新的Session,主要从数据界定事务
  5. jta:主要从分布式界定事务,运行时需要Application Server来支持(Tomcat不支持)

6.3、 Session

  • 【作用】:管理一个数据库的任务单元
  • 【方法】:
  1. save()
session.save(Object);
  1. delete()
session.delete(Object);  // Object对象需要有ID,对象删除后状态为Transisten状态
  1. load()
session.load(Class arg0, Serializable arg1) throws HibernateException
  • arg0 : 需要加载对象的类,例如:User.class
    arg1 : 查询条件(实现了序列化接口的对象),例”4028818a245fdd0301245fdd06380001”字符串已经实现了序列化接口。如果是数值类类型,则hibernate会自动使用包装类,例如 1

  • 此方法返回类型为Object,但返回的是代理对象

  • 执行此方法时不会立即发出SQL语句。只有在使用对象时,才发出查询SQL语句,加载对象

  • 因为load实现了lazy(延迟加载、懒加载)

  • 采用load()方法加载数据,如果数据库中没有相应的记录,则会抛出异常对象找不到(org.hibernate.ObjectNotFoundException)

  • 【概念】

    • 延迟加载:只有真正使用这个对象的时候,才加载(才发出SQL语句)
    • hibernate实现延迟加载的原理是代理方式
try {
        session = sf.openSession();
        session.beginTransaction();

        User user =(User)session.load(User.class,1);

        //只有在使用对象时,它才发出查询SQL语句,加载对象。
        System.out.println("user.name=" + user.getName());

        //因为此的user为persistent状态,所以数据库进行同步为发哥。
        user.setName("发哥");

        session.getTransaction().commit();

    } catch (HibernateExceptione) {
        e.printStackTrace();
    	// 事务回滚
        session.getTransaction().rollback();
    } finally{
        if (session != null){
            if(session.isOpen()){
                session.close();
            }
        }
    }
  1. get()
session.get(Class arg0, Serializable arg1);
  • arg0:需要加载对象的类

  • arg1:查询条件

  • 返回值:Object若数据不存在,则返回null

  • 执行此方法会立即发出查询SQL语句

  • load()get()区别:

    • 不存在对应记录时表现不一样。load()方法加载数据,如果数据库中没有相应的记录,则会抛出异常对象找不到;get()方法查询数据若数据不存在,则返回null
    • load()返回的是代理对象,等到真正使用对象的内容时才发出sql语句,这样就要求在第一次使用对象时,要求session处于open状态,否则出错;
    • get()直接从数据库加载,不会延迟加载
  • get()load()只根据主键查询,不能根据其他字段查询,如果想根据非主键查询,可以使用HQL

  1. update()
  • 用来更新detached对象,更新完成后转为persistent状态(默认更新全部字段)
  • 更新transient对象会报错(没有ID)
  • 更新自己设定ID的transient对象可以
  • persisten状态的对象,只要设定字段不同的值,在session提交时,自动更新
  1. saveOrUpdate()
  • 在执行的时候hibernate会检查,如果对象在数据库中已经有对应的记录(是指主键),则会更新update,否则会添加数据save
  1. merge()
  • 如果对象为 unsaved,对对象的拷贝执行 save 方法,返回拷贝的对象
  • 如果对象为detached,将对象的状态拷贝到和对象的标识一样的持久化对象中,如果持久化对象不存在,就执行get方法将其加载
public Object merge(Object object);

clear()

  • 清除session缓存
  • 无论是load还是get,都会首先查找缓存(一级缓存,也叫session级缓存),如果没有,才会去数据库查找,调用clear()方法可以强制清除session缓存

flush()

  • 清理缓存

  • 执行sql

  • 默认在事务提交时执行

  • 可在事务开启前自行设置,session.setFlushMode(FlushMode)

  • FlushMode有

    • FlushMode.ALWAYS : 任意一条SQL语句都会flush一次
    • FlushMode.AUTO : 自动flush(默认)
    • FlushMode.COMMIT : 只有在commit时才flush
    • FlushMode.MANUAL : 手动flush
    • FlushMode.NEVER : 永远不flush,此选项在性能优化时可能用,比如session取数据为只读时用,这样就不需要与数据库同步了
  • 注意】如果主键生成策略是uuid等不是由数据库生成的,则session.save()时并不会发出SQL语句,只有flush时才会发出SQL语句,但如果主键生成策略是native由数据库生成的,则session.save的同时就发出SQL语句。

evict()

  • 从session缓存(EntityEntries属性)中逐出该对象
  • 注意与commit同时使用,会抛出异常
    session = HibernateUtils.getSession();
    tx = session.beginTransaction();

    User1 user = new User1();
    user.setName("李四");

    //利用Hibernate将实体类对象保存到数据库中,因为user主键生成策略 采用的是uuid,所以调用完成save后,只是将user纳入session的管理, 不会发出insert语句,但是id已经生成,session中的existsInDatabase 状态为false

    session.save(user);

    session.evict(user);  //从session缓存(EntityEntries属性)中逐出该对象

    //无法成功提交,因为hibernate在清理缓存时,在session的临时集合(insertions)中取出user对象进行insert操作后需要更新entityEntries 属性中的existsInDatabase为true,而我们采用evict已经将user从session中逐出了,所以找不到相关数据,无法更新,抛出异常。
    tx.commit();

【解决】

session.save(user);

//flush后hibernate会清理缓存,会将user对象保存到数据库中,将session中的insertions中的user对象清除,并且会设置session中的existsInDatabase状态为false
session.flush();

//从session缓存(EntityEntries属性)中逐出该对象
session.evict(user);

//可以成功提交,因为hibernate在清理缓存时,在Session的insertions中集合中无法找到user对象所以不会发出insert语句,也不会更新session中existsInDatabase的状态。
tx.commit();

7、 持久化对象的三种状态

7.1、 瞬时对象(Transient Object)

使用new操作符初始化的对象不是立刻就持久的。它们的状态是瞬时的,也就是说它们没有任何跟数据库表相关联的行为,只要应用不再引用这些对象(不再被任何其它对象所引用),它们的状态将会丢失,并由垃圾回收机制回收

7.2、持久化对象(Persistent Object)

持久实例是任何具有数据库标识的实例,它有持久化管理器Session统一管理,持久实例是在事务中进行操作的—-它们的状态在事务结束时同数据库进行同步。当事务提交时,通过执行SQL的INSERT、UPDATE和DELETE语句把内存中的状态同步到数据库中。

7.3、 离线对象(Detached Object)

Session关闭之后,持久化对象就变为离线对象。离线表示这个对象不能再与数据库保持同步,它们不再受hibernate管理。

img

7.4、三种状态的区分

  1. 有没有ID,(如果没有则是Transient状态)
  2. ID在数据库中有没有
  3. 在内存里有没有(session缓存)

7.5、 总结

  • Transient对象:随时可能被垃圾回收器回收(在数据库中没有于之对应的记录,应为是new初始化),而执行save()方法后,就变为Persistent对象(持久性对象),没有纳入session的管理。内存中一个对象,没有ID,缓存中也没有

  • Persistent对象:在数据库有存在的对应的记录,纳入session管理。在清理缓存(脏数据检查)的时候,会和数据库同步。内存中有、缓存中有、数据库有(ID)

  • Detached对象:也可能被垃圾回收器回收掉(数据库中存在对应的记录,只是没有任何对象引用它是指session引用),注引状态经过Persistent状态,没有纳入session的管理。内存有、缓存没有、数据库有(ID)

8、 关系映射(对象间)

8.1、一对一关联映射

  • 两个对象之间是一对一的关系,如Person-IDCard

  • 映射策略

    • 主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联。
    • 唯一外键关联:外键关联,本来是用于多对一的配置,但是如果加上唯一的限制之后,也可以用来表示一对一关联关系。

    唯一外键关联-单向(unilateralism)

    • 说明:
      人——>身份证,但从IDCard看不到Person对象

    • 关系模型
      关系模型中,Person中有一外键指向IDCard

    • 实体类

    @Entity
    public class Person {
        private int id;
        private String name;
        private IDCard idCard;

        // setter and getter...
        @Id
        @GeneratedValue
        public int getId() {
            return id;
        }

        @OneToOne  // 表示一对一关系
        @JoinColumn(name="idCard")  // 为数据中的外键指定一个名称
        public IDCard getIdCard() {
            return idCard;
        }
    }

    @Entity
    public class IDCard {
        private int id;
        private String cardNum;

        // setter and getter...
        @Id
        @GeneratedValue
        public int getId() {
            return id;
        }
    }
  • 【注意】操作过程中,要先save(idCard),再save(person)

    唯一外键关联-双向

    • 说明:
      人<——>身份证。双向:互相持有对方的引用
    • 关系模型
      关系模型没有改动
    • 实体类
      实体类中互相持有对方的引用,并且要求Getter和Setter方法
    @Entity
    public class Person {
        private int id;
        private String name;
        private IDCard idCard;

        // setter and getter...
        @Id
        @GeneratedValue
        public int getId() {
            return id;
        }

        @OneToOne  // 表示一对一关系
        @JoinColumn(name="idCard")  // 为数据中的外键指定一个名称
        public IDCard getIdCard() {
            return idCard;
        }
    }

    @Entity
    public class IDCard {
        private int id;
        private String cardNum;
        private Person person;
        // setter and getter...
        @Id
        @GeneratedValue
        public int getId() {
            return id;
        }
        @OneToOne(mappedBy="idCard")  // 表示当前对象在Person对象中被映射了,值为Person对象中对应属性的属性名
        public Person getPerson() {
            return person;
        }
    }
  • 主键关联-单向(不重要)

    • 说明:
      即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联。
    • 关系模型
      因为是person引用idcard,所以idcard要求先有值。而person的主键值不是自己生成的。而是参考idcard的值,person表中即是主键,同时也是外键。
    • 实体类
    @Entity
    public class Person {
        private int id;
        private String name;
        private IDCard idCard;

        // setter and getter...
        @Id
        @GeneratedValue
        public int getId() {
            return id;
        }

        @OneToOne  // 表示一对一关系
        @PrimaryKeyJoinColumn  // 注解主键关联映射
        public IDCard getIdCard() {
            return idCard;
        }
    }

    @Entity
    public class IDCard {
        private int id;
        private String cardNum;

        // setter and getter...
        @Id
        @GeneratedValue
        public int getId() {
            return id;
        }
    }
  • 主键关联-双向(不重要)

    // Person不变
    // IDCard中加入
    @OneToOne(mappedBy="idCard")
    public Person getPerson() {
        return person;
    }
    
  • 联合主键关联

    // 原理同唯一外键关联-单向
    @OneToOne
    @JoinColumns(
        {
            @JoinColumn(name="wifeID",referencedColumnName="id"),
            @JoinColumn(name="wifeName",referencedColumnName="name")
        }
    )
    public Wife getWife() {
        return wife;
    }

8.2、Component(组件)关联映射

  • 将不同类的相同名称的属性抽出成一个组件类,提高复用性

  • Component映射的好处:它实现了对象模型的细粒度划分,层次会更加分明,复用率会更高。

  • 【实现】

    @Entity
    public class User {
        private int id;
        private String name;
        private Contact contact;  // 值对象的引用

        // getter and setter...

        @Embedded  // 用于注解组件映射,表示嵌入对象的映射
        public Contact getContact() {
            return contact;
        }

    }
  • 【存储】
    session =HibernateUtils.getSession();
    tx =session.beginTransaction();

    User user= new User();
    user.setName("10");

    Contactcontact = new Contact();
    contact.setEmail("wjt276");
    contact.setAddress("aksdfj");
    contact.setZipCode("230051");
    contact.setContactTel("3464661");

    user.setContact(contact);

    session.save(user);

    tx.commit();
  • 实体类中引用值对象时,不用先保存值对象,因为它不是实体类,它只是一个附属类,而session.save()中保存的对象是实体类。

8.3、多对一 单向

  • 【场景】用户和组:从用户角度来看,多个用户属于一个组,多个学生对应一个老师
  • 【实体类】Group(一对一端)注解只需正常注解
    @Entity
    public class User {
        private int id;
        private String name;
        private Teacher teacher;

        @ManyToOne
        @JoinColumn(name="teacherId")
        public Teacher getTeacher() {
            return teacher;
        }
    }
  • 【存储】
    session = HibernateUtils.getSession();

    tx =session.beginTransaction();

    Teacher teacher = new Teacher();
    teacher.setName("扫地生");
    // 先存储Teacher对象,使其持久化
    session.save(teacher); 

    Useruser1 = new User();
    user1.setName("小刘");
    // 设置用户所对应的老师
    user1.setTeacher(teacher);

    // 开始存储
    session.save(user1);//存储用户

    tx.commit();//提交事务

执行后Hibernate会执行以下SQL语句:

Hibernate: insert into t_teacher (name) values (?)

Hibernate: insert into t_user (name, teacherid) values (?, ?)
  • 注意】如果上面的session.save(teacher)不执行,则存储不成功。则抛出TransientObjectException异常。因为Teacher为Transient状,Object的id没有分配值。

  • 结果persistent状态的对象是不能引用Transient状态的对象

  • 【解决方案】以上代码操作,必须首先保存teacher对象,再保存user对象。我们可以利用cascade(级联)方式,不需要先保存teacher对象。而是直接保存user对象,这样就可以在存储user之前先把teacher存储了。利用cascade属性是解决TransientObjectException异常的一种手段。

  • 重要属性cascade(级联)

    • cascade属性:
    • CascadeType.ALL - 所有情况
    • CascadeType.MERGE - save and update
    • CascadeType.PERSIST -
    • CascadeType.REFRESH -
    • CascadeType.REMOVE -

如:@ManyToOne(cascade={CascadeType.ALL})

8.4、 一对多 单向

  • 【场景】Teacher和Student:一个老师对应多个学生
  • 【实现】
    // Student(多的一端)正常注解

    @Entity
    public class Teacher {

        private int id;
        private String name;

        // 一对多通常使用Set来映射,Set是不可重复内容。
        // 注意使用Set这个接口,不要使用HashSet,因为hibernate有延迟加载
        private Set<Student>students = new HashSet<Student>();

        @OneToMany  // 进行注解为一对多的关系
        @JoinColumn(name="teacherId")  // 在多的一端注解一个字段(名为teacherid)
        public Set<Student> getStudents() {
            return students;
        }
    }
  • 【存储】Student(多的一端)需先存储
  • 【缺点】
    • 因为是在一的一端维护关系,这样会发出多余的更新语句,这样在批量数据时,效率不高。
    • 当在多的一端的那个外键设置为非空时,则在添加多的一端数据时会发生错误,数据存储不成功。

8.5、一对多 双向

  • 只需在学生对象模型中添加对老师的引用,关系模型和存储没有变化
    // 多的一端
    @Entity
    public class Student {

        private int id;
        private String name;
        private Teacher teacher;

        @ManyToOne
        @JoinColumn(name="teacherId")
        public Teacher getTeacher() {
            return teacher;
        }

    // 一的一端
    @Entity
    public class Teacher {

        private int id;
        private String name;

        // 一对多通常使用Set来映射,Set是不可重复内容。
        // 注意使用Set这个接口,不要使用HashSet,因为hibernate有延迟加载,
        private Set<Student>students = new HashSet<Student>();

        @OneToMany(mappedBy="teacher")//进行注解为一对多的关系
        public Set<Student>getStudents() {
            return students;
        }
  • 【涉及】关于inverse属性

    • inverse(标签属性)主要用在一对多和多对多双向关联上,inverse可以被设置到集合标签上,默认inverse为false,所以我们可以从一的一端和多的一端维护关联关系,如果设置inverse为true,则我们只能从多的一端维护关联关系。
    • 【注意】inverse属性,只影响数据的存储,也就是持久化
  • 【区别】

    • Inverse是关联关系的控制方向
    • Casecade操作上的连锁反应

8.6、 多对多 单向

  • 【场景】一个用户拥有多个角色,一个角色可以属于多个用户
  • 【注意】未持有引用的一方正常注解即可
    @Entity
    public class User {

    private int id;
    private String name;
    private Set<User> roles = new HashSet<User>();// Role对象的集合  

    @Id
    @GeneratedValue
    public int getId() {return id;}

    @ManyToMany
    // 使用@JoinTable标签的name属性注解第三方表名称  
    // 使用joinColumns属性来注解当前实体类在第三方表中的字段名称并指向该对象  
    // 使用inverseJoinColumns属性来注解当前实体类持有引用对象在第三方表中的字段名称并指向被引用对象表
    @JoinTable(name="u_r",joinColumns={
        @JoinColumn(name="userId")},
               inverseJoinColumns={
                   @JoinColumn(name="roleId")})
    public Set<User> getRoles() {
        return roles;

    }

8.7、多对多 双向

  • 【注意】User类如上没有变化,只需修改Role类
    @Entity
    public class Role {

    private int id;
    private String name;
    private Set<User> users = newHashSet<User>();

    @Id
    @GeneratedValue
    public int getId() {return id;  }

    @ManyToMany(mappedBy="roles")
    public Set<User> getUsers() {
        return users;

    }

    public void setUsers(Set<User> users) { 
        this.users = users;  

    }

8.8、?关联关系中的CRUD_Cascade_Fetch

8.9、 集合映射

  1. Set

  2. List

    1. @OrderBy("name ASC") // 指定字段排序,可以组合字段,用逗号分开
  3. Map

    1. @Mapkey(name="id") // 注解使用哪个字段为key

8.10、 !继承关联映射

9、 查询

9.1、HQL(Hibernate Query Language,Hibernate 查询语言)

    // 保存实体
    session.save(Object);  

    // 获取实体集合
    // "from + 实体类名"
    Query q = session.createQuery("from User");
    List<User> list = (List<User>)q.list();

    // 投影查询,查询多个指定字段
    List<Object[]> users = (List<Object[]>)Query q = session.createQuery("select u.id, u.name from User u").list();

    // 获取唯一对象
    User u = (User)q.uniqueResult();

    // 可起别名
    Query q = session.createQuery("from User u where u.name = 'Tom'");

    // 排序
    Query q = session.createQuery("from User u orderby u.name desc");

    // :占位符
    Query q = session.createQuery("from User u where u.name = :name")
        .setString("name","Tom");

    // ?占位符
    Query q = session.createQuery("from User u where u.name = ?")
        .setParameter(0,"Tom");

    // 分页
    Query q = session.createQuery("select distinct u from User u");
    q.setMaxResults(Integer);  // 每页记录数
    q.setFirstResult(Integer);  // 开始页数,从0开始
    List<User> list = (List<User>)q.list;

    // VO - Value Object
    // DTO - Data Transfer Object
    Query q = session.createQuery("select new com.scau.User(u.id,u.name) from User u");
    for(Object o : q.list()) {
        User u = (User)o;
    }

    // 内连接,Group为User成员,一对多关系
    List<User> users = (List<User>)session.createQuery("select u from User u join u.group").list();


    // 更新实体
    Query q = session.createQuery("update User u set u.name = "Tom"");
    q.executeUpdate();

    // 更新实体
    session.update(Object);

    // 删除实体,要求对象主键不为空
    session.delete(Objet);


9.2、QBC(Query By Criteria)

  • 【步骤】

    1. 使用Session实例的createCriteria()方法创建Criteria对象
    2. 使用工具类Restrictions的相关方法为Criteria对象设置查询对象
    3. 使用Criteria对象的list()方法执行查询,返回查询结果
  • 【Restrictions用法】

image-20210330092145222

  • 【工具类】

  • 【Order】

    • 升序 Order.asc(String propertyName)
    • 降序 Order.desc(String propertyName)
  • 【Projections】

    • 求某属性的平均值 Porjections.avg(String propertyName)

    • 统计某属性的数量 Projections.count(String propertyName)

    • 统计某属性的不同值的数量

      Projections.countDistinct(String propertyName)

    • 指定一组属性值 Projections.groupProperty(String propertyName)

    • 某属性的最大值 Projections.max(String propertyName)

    • 某属性的最小值 Projections.min(String propertyName)

    • 创建一个新的projectionList对象 Projections.projectionList()

    • 查询结果集中记录的条数 Projections.rowCount()

    • 返回某属性值的合计 Projections.sum(String propertyName)

    // 示例
    Criteria c = session.createCriteria(User.class)  // From User
            .add(Restrictions.eq("id",2));  // where user.id = 2

    List<User> users = (List<User>)c.list();

    // 分页查询
    Criteria c = session.createCreteria(User.class);
    int pageSize;  // 每页记录数
    int pageNum;  // 页数
    c.setFirstResult((pageNum-1)*pageSize);  // 起始记录
    c.setMaxResults(pageSize);  // 每页记录数
    List<User> users = (List<User>)c.list();

    // 复合查询,在原有查询上再查询
    Criteria c_User = session.createCriteria(User.class);
    Criteria c_Class = c_User.createCriteria("class");
    c_Class.add(Restrictions.eq("number","1"));
    Iterator it = c_Class.list().iterator();

    // 离线查询

9.3、QBE(Query By Example,通过例子进行查询)

  • 【概念】QBE查询就是检索与指定样本对象具有相同属性值的对象。因此QBE查询的关键就是样本对象的创建,样本对象中的所有非空属性均将作为查询条件。
  • 【示例】
    User uExample = new User();
    uExample.setName("Tom");
    Example e = Example.create(uExample);
    Criteria c = session.createCriteria(User.class).add(e);
    for(Object o : c.list()) {
        User user = (User)o;
    }

10、 Query.list与Query.iterate

10.1、Query.iterate查询数据

  1. 发送一条语句:查询所有记录ID语句,并加载入缓存
  2. 每次迭代都首先访问一级缓存,若无则发出查询语句进行查询
    • 【N+1问题】N:N条记录,发出N条语句;1:发出一条查询所有记录ID的语句

10.2、先Query.list()再Query.iterate()

  • 先执行query.list(),再执行query.iterate(),这样不会出现N+1问题
  • 【原因】 因为list操作已经将Student对象放到了一级缓存中,所以再次使用iterate操作的时候它首先发出一条查询id列表的sql,再根据id到缓存中取数据,只有在缓存中找不到相应的数据时,才会发出sql到数据库中查询

10.3、 两次Query.list()

会再次发出查询sql在默认情况下list每次都会向数据库发出查询对象的sql,除非配置了查询缓存。所以,虽然list操作已经将数据放到一级缓存,但list默认情况下不会利用缓存,而再次发出sql默认情况下,list会向缓存中放入数据,但不会使用数据。

10.4、 性能优化策略

  1. 注意session.clear()的动用,尤其在不断分页循环的时候
    1. 在一个大集合中进行遍历,遍历msg,取出其中的含有敏感字样的对象
    2. 另外一种形式的内存泄露(面试是:Java有内存泄漏吗?)
  2. 【1+N】问题(典型的面试题)
    1. Lazy
    2. BatchSize 设置在实体类的前面
    3. joinfetch
  3. list 和 iterate不同之处
    1. list取所有
    2. Iterate先取ID,等用到的时候再根据ID来取对象
    3. session中list第二次发出,仍会到数据库查询
    4. iterate第二次,首先找session级缓存

11、hibernate缓存

11.1、Session级缓存(一级缓存)

  • 一级缓存很短和session的生命周期一致,因此也叫session级缓存或事务级缓存
  • 【支持方法】
    • get()
    • load()
    • iterate()
  • 【管理缓存】
    • session.clear()
    • session.evict()
  • 【避免内存溢出】先flush,再clear
  • 【tips】如果数据量特别大,考虑采用jdbc实现,如果jdbc也不能满足要求可以考虑采用数据本身的特定导入工具

11.2、 二级缓存

  • 【概念】二级缓存也称为进程级的缓存,也可称为SessionFactory级的缓存(因为SessionFactory可以管理二级缓存),它与session级缓存不一样,一级缓存只要session关闭缓存就不存在了。而二级缓存则只要进程在二级缓存就可用。
  • 【特性】
    • Hibernate默认的二级缓存是开启的
    • 二级缓存可以被所有的session共享
    • 二级缓存的生命周期和SessionFactory的生命周期一样,SessionFactory可以管理二级缓存
    • 二级缓存同session级缓存一样,只缓存实体对象,普通属性的查询不会缓存
    • 二级缓存一般使用第三方的产品,如EHCache

11.3、查询缓存

  • 【概念】查询缓存,是用于缓存普通属性查询的,当查询实体时缓存实体ID。默认情况下关闭,需要打开。对list这样的操作会起作用。

  • 【配置】<property name=”hibernate.cache.use_query_cache”>true</property>

  • 【生命周期】当前关联的表发生修改,那么查询缓存生命周期结束

  • 【注意】

    • 所谓查询缓存:即让hibernate缓存list、iterator、createQuery等方法的查询结果集。如果没有打开查询缓存,hibernate将只缓存load方法获得的单个持久化对象。
    • 在打开了查询缓存之后,需要注意,调用query.list()操作之前,必须显式调用query.setCachable(true)来标识某个查询使用缓存
    • 注意查询缓存依赖于二级缓存,因为使用查询缓存需要打开二级缓存

11.4、缓存算法

  • LRU、LFU、FIFO

12、 事务并发处理

12.1、 数据库的隔离级别

  1. ReadUncommited(未提交读):没有提交就可以读取到数据(发出了Insert,但没有commit就可以读取到。)
  2. ReadCommited(提交读):只有提交后才可以读
  3. RepeatableRead(可重复读):mysql默认级别,必需提交才能见到,读取数据时数据被锁住。
  4. Serialiazble(序列化读):最高隔离级别,串行,并发性不好

image-20210330093159338

  • 【知识点】
    • 脏读:没有提交就可以读取到数据称为脏读
    • 不可重复读:重复读取,数据不一致。称不可重复读。
    • 幻读:在查询某一条件的数据,开始查询的时候,别人改动了数据,再读取时与原来的数据不一样了。

12.2、事务概念(ACID)

  • 【ACID】
    • 原子性
    • 一致性
    • 隔离性
    • 持久性

12.3、 hibernate悲观锁、乐观锁

image-20210330093502110

悲观锁

  • 【概念】具有排他性
  • 【实现】通常依赖于数据库机制,在整修过程中将数据锁定,其它任何用户都不能读取或修改
  • 【适用场景】短事务
    session.lock(Object,LockMode.UPGRADE);  
    // upgrade指升级为悲观锁
  • 【注意】使用悲观锁则懒加载无效

乐观锁

  • 【概念】乐观锁不是锁,是一种冲突检测机制
  • 【实现方法】常用的是版本的方式(每个数据表中有一个版本字段version,某一个用户更新数据后,版本号+1,另一个用户修改后再+1,当用户更新发现数据库当前版本号与读取数据时版本号不一致(等于小于数据库当前版本号),则更新不了。
  • session.lock(Oject,LockMode.READ);
posted @ 2021-04-10 11:42  技术扫地生—楼上老刘  阅读(75)  评论(0编辑  收藏  举报