Hibernate学习之---深入Hibernate映射

来源:互联网 发布:网络诈骗转账图片大全 编辑:程序博客网 时间:2024/06/10 01:45

1.映射属性

在默认情况下,被@Entity修饰的持久化类的所有属性都会被映射到底层数据表。
Hibernate为属性映射提供了如下特殊的注解:

  • @Formula:该注解的value属性可指定一个sql表达式,指定该属性的值将根据表达式来计算。持久化类对应的表中没有和计算属性对应的数据列,因为该属性值是动态计算出来的,无须保存到数据库。
  • @Generated:设置该属性映射的数据列的值是否由数据库生成,该注解的value属性可以接受Generation.NEVER(不由数据库生成)、GenerationTime.INSERT(该属性在执行insert语句时生成,但不会在执行update语句时重新生成)和GenerationTime.ALWAYS(该属性值在执行insert和update语句时重新生成)。

@Formula的value属性允许对象属性包含表达式,包括运用sum、average、max函数求值的结果。

    @Formula("(select avg(p.price) from Product p)")    private String fullContent;

@Formula甚至可以根据另一个表的查询结果来计算当前记录的属性值

    @Formula("(select cur.name from currency cur where cur.id=currencyID)")

如果持久化对象有任何属性不是由Java程序提供,而是由数据库生成的,包括该数据列使用timestamp数据类型、数据库采用触发器来为该列自动插入值等,都可以使用@Generated修饰持久化类。

(1.)使用@Transient修饰不想持久保存的属性
使用@Transient修饰的属性,当Hibernate将该持久化类映射到底层数据表时,该属性不会映射到任何数据列。

(2.)使用@Enumerated修饰枚举类型的属性

    public enum Season{        春季、夏季、秋季、冬季    }    @Entity    @Table(name="news_inf")    public class News{        @Enumerated(EnumType.ORDINAL)        @Column(name="happen_season")        private Season happenSeason;    }

底层数据库既可保存枚举值名称来代表枚举值,也可保存枚举值序号来代表枚举值。当@Enumerated的value属性为EnumType.STRING时,底层数据库保存枚举值的名称;当@Enumerated的value属性为EnumType.ORDINAL时,底层数据库保存枚举值的序号。

(3.)使用@Lob、@Basic修饰大数据类型的属性
Hibernate使用@Lob来修饰这种大数据类型,当持久化类的属性为byte[]、Byte[]或java.io.Serializable类型时,@Lob修饰的属性将映射为底层的Blob列;当持久化类的属性为char[]、Character[]或java.lang.String类型时,@Lob修饰的属性将映射为底层的Clob列。

    @Entity    @Table(name="person")    public class Person{        ...        @Lob         private byte[] pic;    }    public class PersonManager{        main{            //实例化Configuration,这行代码默认加载hibernate.cfg.xml文件            Configuration conf = ew Configuration().configure();            ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(conf.getProperties()).build();            //以Configuration实例创建SessionFactory实例            SessionFactory sf = conf.buildSessionFactory(serviceRegistry);            //创建Session            Session sess = sf.openSession();            //开始事务            Transaction tx = sess.beginTransaction();            Person person = new Person();            File file = new File("login.jpg");            byte[] content = new byte[(int)file.length()];            new FileInputStream(file).read(content);            person.setPic(content);            sess.save(person);            //提交事务            tx.commit();            //关闭Session            sess.close();            sf.close();        }    }

Hibernate加载Person对象时并不立即加载它的pic属性,而是只加载一个”虚拟”代理,等到程序真正需要pic属性时才从底层数据表中加载数据—这就是典型的代理模式。Hibernate为这种机制提供了支持,并将这种机制称为延迟加载,只要在开发实体时使用@Basic修饰该属性即可。
使用@Basic可以指定如下属性:

  • fetch:FetchType.EAGER(立即加载)、FetchType.LAZY(延迟加载)。
  • optional:指定该属性映射的数据列是否允许使用null值。

(4.)使用@Temporal修饰日期类型的属性
使用@Temporal时可指定一个value属性,该属性支持TemporalType.DATE、TemporalType.TIME、TemporalType.TIMESTAMP三个值之一,用于指定将该属性映射到数据表的date、time和timestamp类型的数据列。

2.映射主键

Hibernate建议为持久化类定义一个标识属性,用于唯一地标识某个持久化实例,而标识属性则需要映射到底层数据表的主键。
所有现代的数据库建模理论都推荐不要使用具有实际意义的物理主键,而是推荐使用没有任何实际意义的逻辑主键。尽量避免使用复杂的物理主键,而应该考虑为数据库增加一列,作为逻辑主键。
逻辑主键没有实际意义,仅用来表示一行记录。Hibernate为这种逻辑主键提供了主键生成器,它负责为每个持久化实例生成唯一的逻辑主键值。
如果实体类的标识属性(映射成主键列)是基本数据类型、基本类型的包装类、String、Date等类型,可以简单地使用@Id修饰该实体属性。
如果希望Hibernate为逻辑主键自动生成主键值,则还应该使用@GeneratedValue来修饰实体的标识属性。
如果使用@TableGenerator定义的主键生成器会在底层数据库中额外生成一个辅助表。

    @Entity    @Table(name="person")    public class Person{        @Id                @TableGenerator(name="newsGen",table="NEWS_ID_GEN",pkColumnName="gen_key",valueColumnName="gen_value",pkColumnValue="news_id")        @GeneratedValue(strategy=GenerationType.TABLE,generator="newsGen")        private Integer id;    }

3.使用Hibernate的主键生成策略

使用Hibernate本身的@GenericGenerator注解,该注解用于定义主键生成器。

4.映射集合属性

Hibernate要求持久化集合之字段必须声明为接口,实际的接口可以是java.util.Set、java.util,Collection、java.util.List、java.util.Map、java.util.SortedSet、java.util.SortedMap等,甚至是自定义类型(字需要实现org.hibernate.usertype.UserCollectionType接口即可。)
Hibernate之所以要求用集合接口来声明集合属性,是因为当程序持久化某个实例时,Hibernate会自动把程序中的集合实现类替换成Hibernate自己的集合实现类,因此不要试图把Hibernate集合属性强制类型转换为集合实现类,如HashSet、HashMap等,但可以转换为Set、Map等集合,因为Hibernate自己的集合类也实现了Map、Set等接口。
集合类实例具有值类型的行为:当持久化对象被保存时,这些集合属性会被自动持久化;当持久化对象被删除时,这些集合属性对应的记录将被自动删除。假设集合元素被从一个持久化对象传递到另一个持久化对象,该集合元素对应的记录会从一个表转移到另一个表。
两个持久化对象不能共享同一个集合元素的引用。
不管哪种类型的集合属性,都统一使用@ElementCollection注解进行映射。
由于集合属性总需要保存到另一个数据表中,所有保存集合属性的数据表必须包含一个外键列,用于参照到主键列,该外键列使用@JoinColumn进行映射。
Hibernate使用标准的@CollectionTable注解映射保存集合属性的表。
如果要映射带索引的集合(List、数组、Map),就需要为集合元素所在的数据表指定一个索引列—用于保存数组索引、List的索引,或者Map集合的key索引。用于映射索引列的注解有:

  • @OrderColumn:用于定义List集合、数组的索引列
  • @MapKeyColumn:用于映射Map集合的索引项
    如果程序需要显示指定Map key的类型,则可以使用@MapKeyClass注解,该注解只有一个value属性,用于指定Map key的类型。

集合元素类型分为:

  • 集合元素是基本类型及其包装类、字符串类型和日期类型:使用@ElementCollection映射集合属性,并使用普通的@Column映射集合元素对应的列。
  • 集合元素是组件(非持久化实体的复合类型):使用@ElementCollection映射集合属性,然后使用@Embeddable修饰非持久化实体的复合类
  • 集合元素是关联的持久化实体:此时不再是集合属性了,应该使用@OneToMany或@ManyToMany进行关联映射。

(1.)List集合属性

    @Entity    @Table(name="person")    public class Person{        //集合属性,保留该对象关联的学校        @ElementCollection(targetClass=String.class)        //映射保存集合属性的表        @CollectionTable(name="school" joinColumns=@JoinColumn(name="person_id",nullable=false))        //指定保存集合元素的列为school_name        @Column(name="school_name")        //映射集合元素索引的列        @OrderColumn(name="list_order")        private List<String> schools = new ArrayList<>();    }

对于同一个持久化对象而言,它所包含的集合元素的索引是不会重复的,因此List集合属性可以用关联持久化对象的外键和集合索引列作为联合主键。

(2.)数组属性
Hibernate对List和数组的处理几乎完全一样

(3.)Set集合属性
Set是无序的、不可重复的集合,因此Set集合属性无须使用@OrderColum注解映射集合元素的索引列。

    @Entity    @Table(name="person")    public class Person{        //集合属性,保留该对象关联的学校        @ElementCollection(targetClass=String.class)        //映射保存集合属性的表        @CollectionTable(name="school" joinColumns=@JoinColumn(name="person_id",nullable=false))        //指定保存集合元素的列为school_name        @Column(name="school_name")        private List<String> schools = new ArrayList<>();    }

Set集合没有索引列,则以关联持久化类的外键列和元素列作为联合主键,前提是元素列不能为空。

(4.)Map集合属性

@Entity    @Table(name="person")    public class Person{        //集合属性,保留该对象关联的学校        @ElementCollection(targetClass=Float.class)        //映射保存集合属性的表        @CollectionTable(name="score" joinColumns=@JoinColumn(name="person_id",nullable=false))        @MapKeyColumn(name="subject")        //指定Map key的类型为String类型        @MapKeyClass(String.class)        //映射保存Map value的数据列        @Column(name="mark")        private Map<String,Float> scores = new HashMap<>();    }

保存scores的表将以person_id列和subject_name列作为联合主键。

5.集合属性的性能分析

对于集合属性,通常推荐使用延迟加载策略。所谓延迟加载就是等系统需要使用集合属性时才从数据库加载关联的数据。
Hibernate对集合属性默认采用延迟加载,在某些特殊的情况下,为@ElementCollection注解设置fetch=FetchType.EAGER来取消延迟加载。数组无法使用延迟加载(因为数组的长度不可变)。
如果”改变”Set集合的某个元素,Hibernate并不会立即更新(update)该元素对应的数据行。因此对于Set集合而言,只有在执行插入(insert)和删除(delete)操作时”改变”才有效。
Hibernate的关联映射都是采用Set集合进行管理的。在设计良好的Hibernate领域模型中,1-N关联的1的一端通常不再控制关联关系,所有的更新操作将会在N的一端进行处理。一旦指定了1的一端将不再控制关联关系,则表明该集合映射作为反向集合使用。在这种情况下,使用List集合属性将有较好的性能,因为可以在未初始化集合元素的情况下直接向List集合中添加新元素。因为Collection.add()和Collection.addAll()方法,以及List.add()和List.addAll()方法总是返回true。

6.有序集合映射

Hibernate还支持使用SortedSet和SortedMap两个有序集合。
Hibernate本身提供的@SortNatural或@SortComparator注解,其中前者表明对集合元素采用自然排序,后者表明对集合元素采用定制排序,因此使用@SortComparator时必须指定value属性,该属性值为Comparator实现类。
如果希望数据库查询自己对集合元素排序,则可以利用Hibernate自己提供的@OrderBy注解,它会在SQL查询中完成排序,而不是在内存中完成排序。

    @OrderBy("training_name desc")    private Set<String> trainings = new HashSet<>();

7.映射数据库对象

如果希望在映射文件中创建和删除触发器、存储过程等数据库对象,Hibernate提供了<database-object.../>元素来满足这种需求。

    <hibernate-mapping>        ...        <database-object>            <create>create trigger t_full_content_gen ...</create>            <drop>create trigger t_full_content_gen</drop>        </database-object>    </hibernate-mapping>

每个<database-object.../>元素中只有一组<create.../>、<drop.../>对
为了让Hibernate根据<database-object.../>元素创建数据表,一定要将Hibernate配置文件里的hbm2ddl.auto属性值修改成create。

Hibernate提供了SchemaExport工具,该工具可根据映射文件来生成数据库对象。

    Configuration conf = new Configuration().configure();    SchemaExport se = new SchemaExport(conf);    //设置输出格式良好的sql脚本        se.setFormat(true)      //设置保存sql脚本的文件名      .setOutputFile("new.sql")      //输出sql脚本,并执行sql脚本      .create(true,true);
原创粉丝点击