Hibernate 5.3(四)[注解]

前言

本篇文章主要讲解有关Hibernate的注解,由于时间有限,我只能讲解一些比较常用,还有一些,如果以后用到,会陆续补充进来的。

题外知识

说时间JPA 、EJB 这些都是啥呀,经常看到有些人挂在嘴边。

  • JPA: Java 持久层的API,主要为我们提供了ORM对象的处理。Hibernate 就是在这个基础上编写的。
  • EJB:是以前一个老的,里面的实体Bean,已经逐渐被JPA替代,JPA的使用范围更广。

javax persistence or org hibernate annotations

相信使用过Hibernate 注解,自动提示的时候,会出现这两种注解,那么我们该使用哪种呢,看了上面的介绍,你应该了解实际上,Hibernate是基于JPA,org hibernate annotations 实际上在原先的注解上进行的修改成自己,但是我们使用的话一般还是使用javax persistence 的多,本文的介绍也是基于这个。

Hibernate 注解文档哪里找

如果你使用的是Hibernate 注解,那么打开你当时下载的Hibernate的资料,里面有一个D:\hibernate\hibernate-release-5.3.0.Beta1\documentation\javadocs\org\hibernate\annotations(自己对应去找)

如果是使用的javax persistence 注解,那就直接找到javaee 的api 文档,即可看到。

注解放在字段ORget方法

在对一个类进行注解时,你可以选择对它的的属性或者方法进行注解,根据你的选择,Hibernate的访问类型分别为 field或property. EJ3规范要求在需要访问的元素上进行注解声明,例如,如果访问类型为 property就要在getter方法上进行注解声明, 如果访问类型为 field就要在字段上进行注解声明.应该尽量避免混合使用这两种访问类型. Hibernate根据@Id 或 @EmbeddedId的位置来判断访问类型。

注解例子

为避免大量代码,我们注解就注解在字段上,没有提供相应的set、get方法,需要读者自行添加。

继承

该模块是继承相关的注解

@MappedSuperclass
@MappedSuperclass
//申明成超类,不可以和@entity一起使用
public class Account  {
	 @Column(name="balance")
     private BigDecimal balance;
     @Id
     @GeneratedValue(strategy=GenerationType.IDENTITY)
     private long id;
     @Column(name="interest_rate")
     private BigDecimal interestRate;     
}
@Entity(name="creditaccount")
public class CredittAccount extends Account {
	@Column(name="limitcredit")
	private BigDecimal limitCredit;
	}

该注解主要用来进行继承关系的实体映射,用来修饰父类

Account 是父类,该实体映射是不会成一张表,CredittAccount 是子类,映射的表包含父类的所有属性和自身的。

该继承关系未在数据库进行镜像,无法进行多态查询,也就是说无法从父类获取所有的子类。

单表继承

所有子类只继承于一个父类,所有的子类的属性都存放在父类的表中。

@Entity(name="account")
@DiscriminatorValue("a")
@DiscriminatorColumn(name="type")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)//该注解策略,默认就是单表继承
public class Account  {
     @Column(name="balance")
     private BigDecimal balance;
     @Id
     @GeneratedValue(strategy=GenerationType.IDENTITY)
     private long id;
     @Column(name="interest_rate")
     private BigDecimal interestRate;
     }
@Entity(name="creditaccount")
@DiscriminatorValue("ca")
//他们的id 都是继承于account
public class CredittAccount extends Account {
	@Column(name="limitcredit")
	private BigDecimal limitCredit;
	}
@Entity(name="DebitAccount")
@DiscriminatorValue("da")
@Table(name="debitaccount")
public class DebitAccount extends Account {
      @Column(name="overdraftFee")
       private BigDecimal overdraftFee;
       }
		   DebitAccount da = new DebitAccount();
		   da.setBalance(new BigDecimal(11.2));
		   da.setInterestRate(new BigDecimal(11.9));
		   da.setOverdraftFee(new BigDecimal(23.4));
		   ss.save(da);
		   
		   CredittAccount ca = new CredittAccount();
		   ca.setBalance(new BigDecimal(11.2));
		   ca.setInterestRate(new BigDecimal(11.9));
		   ca.setLimitCredit(new BigDecimal(35.6));
		   ss.save(ca);
		   
		   Account account = new Account();
		   ca.setBalance(new BigDecimal(11.4));
		   ca.setInterestRate(new BigDecimal(15.9));
		   ss.save(account);

我们可以看到这种配置,父类也是可以映射成一张表,这点和之前的不一样。子类的主键都是继承于父类的,不需要单独的定义。同时基于这种继承关系,还会为底层的数据表中添加一个DTYPE 的列,该列用于存放鉴别值的,这种带有鉴别值列,是基于SINGLE_TABLE and JOINED 这两种继承策略。discriminator列包含标记值,用于告知持久层为特定子类进行持久化操作。鉴别值不需要手动插入,在保存某一个实体的时候,会自动插入。

  • @DiscriminatorColumn(name=“type”)
    一般用于在父类(其实放子类也ok),定义鉴别值列的名称。如果没有设置,默认就是DTYPE。
  • @DiscriminatorValue(“ca”)
    指定某一个实体的鉴别值,一般用字符串居多。如果没有设置,默认就是该实体@entity 里面的name 。
List<DebitAccount> list = ss.createQuery("select  a from account a where a.id=:id1").setParameter("id1", 2L).list();
		   //基于注解实体类的hql 查询,需要注意 ,不在像映射文件配置 ,直接用类名,而是用你@entity 的name
		   for(DebitAccount da:list){
			   System.out.println(da.getOverdraftFee());
		   }

使用多态查询,只要查询一个父类表,即可返回子类的所有实例(子类所有字段均可以获取)。

基于这种单表继承的策略是比较好,所有的子类都在父类表中,所以属性不在强制not null。

Joined table

每一个子类都将映射成一张表。

@Entity(name="account")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Account  {
    @Column(name="balance")
     private BigDecimal balance;
     @Id
     private long id;
     //这里id 的生成策略不可以设置为自动生成
     @Column(name="interest_rate")
     private BigDecimal interestRate;
     }
@Entity(name="creditaccount")
@PrimaryKeyJoinColumn(name="account_id")
public class CredittAccount extends Account {
	@Column(name="limitcredit")
	private BigDecimal limitCredit;
	}
@Entity(name="DebitAccount")
@Table(name="debitaccount")
@PrimaryKeyJoinColumn(name="account_id")
public class DebitAccount extends Account {
      @Column(name="overdraftFee")
       private BigDecimal overdraftFee;
       }

采用这种映射关系,需要注意,子类的主键是依赖于父类主键,所以,父类id 不建议设置自动递增,设置有异常,同时,子类,可以使用@PrimaryKeyJoinColumn 来设置子类的主键名称,该主键是默认关联父类的主键。如果,没有设置该注解,子类的主键名默认和父类主键名一致。

添加操作:

		   Account account = new Account();
		   account.setBalance(new BigDecimal(11.4));
		   account.setInterestRate(new BigDecimal(15.9));
		   account.setId(1L);
		   ss.save(account);
		   
		   DebitAccount da = new DebitAccount();
		   da.setBalance(new BigDecimal(1123.3));
		   da.setInterestRate(new BigDecimal(34.5));
		   da.setOverdraftFee(new BigDecimal(23.4));
		   da.setId(2L);//这里不能是1,因为每一个子表save ,都会先save 父表,父表的主键不就和上面重复了。
		   ss.save(da);
		   
		   CredittAccount ca = new CredittAccount();
		   ca.setBalance(new BigDecimal(1138.3));
		   ca.setInterestRate(new BigDecimal(39.5));
		   ca.setLimitCredit(new BigDecimal(35.6));
		   ca.setId(3L);
		   ss.save(ca);

查看数据库,我们发现每一个子类插入的时候,父类的属性放在父表中(没有的话,属性为NULL),子类的属性放在子表中(这里子类的属性包含其他子类的属性,不是该子类的属性,是为NULL),子表的主键关联父表主键。

List<DebitAccount> list = ss.createQuery("select  a from account a where a.id=:id1").setParameter("id1", 2L).list();
		   //基于注解实体类的hql 查询,需要注意 ,不在像映射文件配置 ,直接用类名,而是用你@entity 的name
		   for(DebitAccount da:list){
			   System.out.println(da.getOverdraftFee());
		   }

通过打印sql,发现利用多态查询,采用这种继承策略,会通过left join 去多表查询,采用join ,性能有所消耗。

Table per class
@Entity(name="account")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Account  {
	 @Column(name="balance")
     private BigDecimal balance;
     @Id
     private long id;
     @Column(name="interest_rate")
     private BigDecimal interestRate;
     }
@Entity(name="creditaccount")
public class CredittAccount extends Account {
	@Column(name="limitcredit")
	private BigDecimal limitCredit;
	}
@Entity(name="DebitAccount")
@Table(name="debitaccount")
public class DebitAccount extends Account {
	   @Column(name="overdraftFee")
       private BigDecimal overdraftFee;
       }

查看表结构,你会发现,所有子表都有父类属性,包含自己特有属性。

添加操作:

		   Account account = new Account();
		   account.setBalance(new BigDecimal(11.4));
		   account.setInterestRate(new BigDecimal(15.9));
		   account.setId(1L);
		   ss.save(account);
		   
		   DebitAccount da = new DebitAccount();
		   da.setBalance(new BigDecimal(1123.3));
		   da.setInterestRate(new BigDecimal(34.5));
		   da.setOverdraftFee(new BigDecimal(23.4));
		   da.setId(2L);// 虽然子表save ,父表的所有属性都在子表中,父表没有记录,但是实际上有一个引用的(比如下面这个clazz_  这个字段,我估摸着应该和这个有关,如果有明白的,可以告知我),所以不可以为1。
                   select
	       	/*select
	        account0_.id as id1_0_0_,
	        account0_.balance as balance2_0_0_,
	        account0_.interest_rate as interest3_0_0_,
	        account0_.overdraftFee as overdraf1_2_0_,
	        account0_.limitcredit as limitcre1_1_0_,
	        account0_.clazz_ as clazz_0_*/ 
		   ss.save(da);
		   
		   CredittAccount ca = new CredittAccount();
		   ca.setBalance(new BigDecimal(1138.3));
		   ca.setInterestRate(new BigDecimal(39.5));
		   ca.setLimitCredit(new BigDecimal(35.6));
		   ca.setId(3L);
		   ss.save(ca);

多态查询:

List<DebitAccount> list = ss.createQuery("select  a from account a where a.id=:id1").setParameter("id1", 2L).list();
		   //基于注解实体类的hql 查询,需要注意 ,不在像映射文件配置 ,直接用类名,而是用你@entity 的name
		   for(DebitAccount da:list){
			   System.out.println(da.getOverdraftFee());
		   }

从查询sql ,我们可以看到,它是通过union,嵌套子查询所有子表、父表。和上面的继承关系相比,它没有关联父表的主键,但是我查询的时候,却能关联子表,从查询语句,我看到class_,这个字段,而我并没有配置,所以,可能是靠该字段,还望其他知道人,赐教!

接口多态查询
public interface TestInterface {
}

@Entity(name = "BookNew")
@Table(name = "book_new")
public class BookNew implements TestInterface {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(name="book_name")
	private String name;
	}
@Entity(name = "Blog")
@Table(name = "blog")
@Polymorphism(type=PolymorphismType.EXPLICIT)
public class Blog implements TestInterface {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(name = "blog_site")
	private String site;
	}

基本注解映射关系,很简单,就不做太多说明了。

List list = ss.createQuery("select a from com.example.test.bean.TestInterface a").list();

注意,在hql 中,查询是面向对象的,由于TestInterface 没有映射成表,所以在这里需要写接口的全限名,才可以找到。对于继承多态查询,查询父类一定会查询所有子类。但是接口多态查询,可以通过@Polymorphism(type=PolymorphismType.EXPLICIT),配置在实现类中,多态查询就无法获得该实体。

二级缓存
配置二级缓存

org.hibernate.cache.spi.RegionFactory 这个类提供了hibernate 关于缓存的。所以在hibernate 配置文件的属性hibernate.cache.region.factory_class,要提供对应缓存的实现类,hibernate 支持主要的缓存JChache、Ehchache。

<property name="hibernate.cache.region.factory_class">
         org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
		<property name="hibernate.cache.use_second_level_cache" >true</property>  //设置启用hibernate 的二级缓存,如果没有设置上面的,则默认区域工厂为NoCachingRegionFactory,一样二级缓存不起作用。

<property name="hibernate.cache.use_query_cache">false</property>//设置二级缓存的查询缓存

其他还需要配置ehcache.xml,导入相应的jar,具体参看添加链接描述

经过上述的配置,就可以针对相应的实体,进行设置缓存:

@Entity(name = "Phone")
@Table(name = "phone")
@Cacheable
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Phone {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(name = "phone_number", unique = true)
	}

但是需要,注意的是,在hibernate 5.3 这样的配置,会出现空指针异常,之前的版本,我看网上说没啥问题,之后的版本我也没去试,大家咋使用的时候注意一下,经过google,也是每找不到解决办法,找到一个类似的问题,每找到解决办法。添加链接描述,还望知道的人,赐教。

@NaturalId

自然Id,自然id不能成为一个好的主键(代理键通常是首选),我们仍然可以像加载主键,获取某一实体。

NaturalId Mapping

注解普通属性

@Entity(name="Book")
@Table(name="book")
public class Book {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(name="book_name")
	private String bookname;
	@NaturalId
	private String isbn;
	}

查看数据库的表,发现通过该注解的属性被映射成一个唯一性的索引。

注解组件属性

@Entity(name="Book")
@Table(name="book")
public class Book {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(name="book_name")
	private String bookname;
	@NaturalId
	private Isbn isbn;
	@Embeddable
 	static class Isbn implements Serializable{
	 private String isbn1;
	 private String isbn2;
	 }
	}

NaturalId 怎么也算Id,所以组件类需要实现序列化接口,同时重写hashcode、equals方法。Book 表中多了组件类的两个属性,但是此时唯一性索引是isbn1和isbn2的组合。

注解多个属性

@Entity(name="Book")
@Table(name="book")
public class Book1 {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@NaturalId
	private String isbn;
	@NaturalId
	private String name;
	}

多个被注解的属性,联合组成唯一性索引。

根据NaturalId去获取实体
		   Book book = ss.byNaturalId(Book.class).using("isbn",new Book.Isbn("aa","bb")).load();
		   Book book1 = ss.bySimpleNaturalId(Book.class).load(new Book.Isbn("aa","bb"));
关联关系

该模块是涉及关联关系的相关注解

many to one
@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
    @OneToMany(cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
	//一个有多个电话
	private List<Phone> phones = new ArrayList<Phone>();
	}

public void addPhone(Phone phone){
			//这个地方,你必须要设置的级联,不然,它不会自动帮你save phone,不save 
			//就没有持久化的状态,所以这时你的add,根据就不不是插入数据库中。
			phones.add(phone);
			phone.setPerson(this);//交给phone 去控制,这个地方如果不设置,那么
			//personid 将为空
		}
		
		public void removePhone(Phone phone){
			phones.remove(phone);
			phone.setPerson(null);//交给phone 将关系清除
		}
@Entity(name="Phone")
@Table(name="phone")
public class Phone {
		@Id
		@GeneratedValue(strategy=GenerationType.IDENTITY)
		private Long id;
		@Column(name="phone_number")
		private String number;
		@ManyToOne()
		private Person person;
		}
 		   Person p = new Person();
		   ss.save(p);
		   Phone p1 = new Phone();
		   p1.setNumber("123");
		   p.addPhone(p1);
		   ss.flush();//刚插入的,你也要等他flush 到数据库,你在查
		   Phone p2 = ss.load(Phone.class, 1L);
		   p.removePhone(p2);//这边删除的实体必须要是持久化的状态
		   //这边删除只是将personid 置为null,设置orphanRemoval,就会主动帮你删除phone 的条目,而不是讲外键设置为空。
  Person p = ss.load(Person.class, 1L);
		   ss.delete(p);

有人会想下面的这个删除和上面的删除的区别在哪,上面的删除是将满足的person 和phone 关系删除,通过orphanRemoval 来指定没有外键引用(也就是外键为空)的条目自动删除,而且该属性应该设置位置在person 实体中针对集合属性。而且,你发现上面的删除,都是基于面向对象的方法,但是下面的删除方法是基于数据库操作,下面的删除会删除所有外键依赖于该person 的phone 对象,因为设置级联cascade=CascadeType.ALL。

@many to one 注解主要映射实体之间多对一的关系,需要配合@JoinColumn的使用,因为在实体关系中,多方会产生一个外键。而@JoinColumn 的name 是用来指定添加的外键列的列名,默认是关联实体的主键,可以通过 referencedColumnName去指定。而foreignKey 的name 外键名字。(该注解可以不配置,就采用默认的)

在双向关系中,我们在xml 中通过配置inverse 去设置控制方,在注解方式,我们可以设置mappedBy 去达到一样的效果。

one to one
@Entity(name="Address")
@Table(name="address")
public class Address {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="detail")
	private String detail;

}

@Entity(name="Phone")
@Table(name="phone")
public class Phone {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="phone_number")
	private String number;
	@OneToOne(cascade=CascadeType.ALL)
	//这里指定级联,就是一个人删除了,地址也跟着没有
	@JoinColumn(name="address_id")
	private Address address;
	

}

一对一关系,还是相对比较简单的,不存在双向啦,在某个实体中添加该注解,同时配合@JoinColumn 指定关联实体的主键,这里不会产生,不需要使用foreignKey 。

many to many
@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@ManyToMany(mappedBy="persons",cascade=CascadeType.ALL)
	//一个人有多个地址,一个地址可以对应多个人
	private Set<Address> addresses = new HashSet<Address>();

}
@Entity(name="Address")
@Table(name="address")
public class Address {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="detail")
	private String detail;
}

多对多插入操作:

		  Person person = new Person();
		   person.setName("laohe");
		   Address address = new Address();
		   address.setDetail("dawanglu");
		   
		   person.getAddress().add(address);//控制权给person
		   ss.save(person);//设置级联操作,直接保存person 即可。
          
           

多对多关系映射,会产生一个中间表,包含两个实体对应的主键。在多对多关系中,删除某个实体,如果没有设置级联设置,不可以直接删除,违反外键约束。

Person person1 = entityManager.find( Person.class, personId );
entityManager.remove( person1 );//这种删除会引发异常

如果设置级联操作,进行如下操作,对应的person、address、person_address 相应的记录均会被删除。

Person p = ss.get(Person.class,1L);
ss.delete(p)

双向多对多关系

@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
@ManyToMany(mappedBy="persons")//将控制权交给address
	//一个人有多个地址,一个地址可以对应多个人
	private Set<Address> addresses = new HashSet<Address>();
}
@Entity(name="Address")
@Table(name="address")
public class Address {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="detail")
	private String detail;
	@ManyToMany()
	private Set<Person> addresses = new HashSet<Person>();
}

在双向的多对多关系中,我们在Phone 实体配,mappedBy ,将关系维护权交给了person 实体。这里必须要设置一个mappedBy ,否则它会根据两个many to many 的标签去自动生成两个中间表,这里只需要一个。

在上面的配置中,我们如果执行下面的删除:

Address address = ss.get(Address.class, 1L);
address.getPersons().clear();//因为主控制权在address ,

执行之后,我们发现删除是中间表,也就是删除多对多之间的关系。

双向的多对多的关系,我们还可以使用双向的一对多的关系来表示,但是需要一张中间表去维护关系(这张表就不是自动生成的)。

@Entity(name = "Person")
@Table(name = "person")
public class Person implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "person",orphanRemoval=true)
	// 一个人有多个地址,一个地址可以对应多个人
	private Set<PersonAddress> addresses = new HashSet<PersonAddress>();
	@Column(name = "person_name")
	private String name;
	}
@Entity(name = "Address")
@Table(name = "address")
public class Address implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(name = "detail")
	private String detail;
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "address",orphanRemoval=true)
	private Set<PersonAddress> persons = new HashSet<PersonAddress>();
}
@Entity(name = "PersonAddress")
@Table(name = "person_address")
/* 具有复合主键的实体,必须要实现序列化接口 */
public class PersonAddress implements Serializable {
	@ManyToOne
	@JoinColumn(name = "personid")
	@Id
	private Person person;
	@ManyToOne
	@Id
	@JoinColumn(name = "addressid")
	private Address address;

	public Person getPerson() {
		return person;
	}

	public void setPerson(Person person) {
		this.person = person;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((address == null) ? 0 : address.hashCode());
		result = prime * result + ((person == null) ? 0 : person.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		PersonAddress other = (PersonAddress) obj;
		if (address == null) {
			if (other.address != null)
				return false;
		} else if (!address.equals(other.address))
			return false;
		if (person == null) {
			if (other.person != null)
				return false;
		} else if (!person.equals(other.person))
			return false;
		return true;
	}

}

PersonAddress pa = new PersonAddress();
		   Person p = new Person();
		   p.setId(1L);
		   Address a = new Address();
		   a.setId(2L);
		   pa.setPerson(p);
		   pa.setAddress(a);
		   PersonAddress pa1 = ss.load(PersonAddress.class, pa);//如何通过复合主键去查找某一实体
		   Person person = ss.load(Person.class,1L);
		   Address address1 = ss.load(Address.class, 2L);
		   //person.getAddresses().remove(pa1);//两者选择任意一个,均可以删除满足条件的PersonAddress 的关系。
		   //address1.getPersons().remove(pa1);
组件

对于符合对象,Hibernate 称之为components,JPA 称之为embeddables。

这部分的注解,包含3个:@Embeddable,@Embedded、@EmbeddedId

@Embeddable、@Embedded
@Entity(name="Book")
@Table(name="book")
public class Book {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(name="book_name")
	private String bookname;
	private Publisher publisher;

@Embeddable
public static class Publisher{
	private Location location;
	
  }
@Embeddable
public static class Location{
	@Column(name="publisher_city")
	private String city;
	
}
}

上述数据库执行之后,所有的字段将在一个表中,对于组件类是不需要定义主键的。对于组件类,我们建立将它定义在父实体类中,作为静态嵌套类存在。

组件类属性的重写

有时候,我们需要在一个父类实体中,多次引用该组件类,如果这时按照组件类默认的属性name来,就会冲突,所以需要重写。

@Entity(name="Book")
@Table(name="book")
@AttributeOverrides({
	@AttributeOverride(name="epublisher.name",column=@Column(name="epublisher_name")),//epublisher 必须是你定义的属性名
	@AttributeOverride(name="ppublisher.name",column=@Column(name="ppublisher_name")),
})
public class Book {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(name="book_name")
	private String bookname;
	private Publisher epublisher;//电子出版社
	private Publisher ppublisher;//纸质出版社

@Embeddable
public static class Publisher{
	@Column(name="publisher_name")
	private String name;
	
  }
}

如果涉及到组建类的关系(比如一对多等)的重写,则需要使用@AssociationOverride。

另外,最好把属性、关系的重写的注解,放到class 上。

如果,你嫌这样重写属性麻烦,那么,还有一种简单的方法:

直接应用命名冲突的策略,让Hibernate 为你解决,不用你自己麻烦。

@embeddedId
@Embeddable
public class CompitedId implements Serializable {
	private String title;
	private String name;
	}
@Entity(name = "People")
@Table(name = "people")
public class People {
	@EmbeddedId
	private CompitedId id;
	@Column(name="topic")
	private String topic;
	}
 	           People p = new People();
		   CompitedId cId = new CompitedId("liu","lao");
		   p.setId(cId);
		   p.setTopic("ki");
		   ss.save(p);
		   ss.flush();
		   tt.commit();
		   People  people =ss.load(People.class,new CompitedId("liu","lao"));
		   System.out.println(people.getTopic());

同样的,对于复合主键类,重写hashcode、equals,实现序列化接口,这些都是必须的。

在表中通过@EmbeddedId 去引用一个组件类型作为复合主键。注意查询的时候,还是要构造一个复合的主键去查找。

如果是采用的是HQL 查询语句:

		   List list = ss.createQuery("select p from People  p  where  p.id.name=:name1 and p.id.title=:title1").setParameter("name1","lao").setParameter("title1","liu").list();

虽然复合主键的属性都是在person 表中,但是对于复合主键的属性引用,不能直接p.name ,会提示找不到。

该复合主键可以和下面@IdClasss 对比着看。

Collection

下面的注解大部分和集合相关。

在hibernate 中,集合是不允许在嵌套集合类型。

常见的集合类型接口:java.util.Collection, java.util.List, java.util.Set, java.util.Map, java.util.SortedSet, java.util.SortedMap

@ElementCollection

下面直接演示集合的类型是组件类型,基础类型在这就不做说明了。

@Entity(name = "Person")
@Table(name = "person")
public class Person {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@ElementCollection
	List<Phone> List = new ArrayList<Phone>();
	@Column(name = "person_name")
	private String name;
    @Embeddable
	public static class Phone{
		@Column(name="phone_name")
		private String name;
	}
	}

之前,我们说过@Embeddable这个注解的class 是嵌套在主实体中,而在集合中是不一样的,这时会新建一张表来存放phone 的属性和主表person 的主键。

上面说过,集合中可以存放组件类型,或者是基本类型,但是我们更多是集合中存放的是某个实体,从而形成关联关系。

Bags

是用来表示List 类型的集合接口,特点就是没有顺序。

这部分的例子,参看上面one to many 映射关系的例子,即可。

Order List

Bags 用来表示Java 的List 接口,但是它里面不能保留有序的元素,所以我们提供下面注解,可以解决该问题。

@OrderBy 可以用于查询集合子实体,按照子实体的某一个属性,进行排序显示。

@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@OrderBy("number")
    @OneToMany(cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
	//一个有多个电话
	private List<Phone> phones = new ArrayList<Phone>();
	}
@Entity(name="Phone")
@Table(name="phone")
public class Phone {
		@Id
		@GeneratedValue(strategy=GenerationType.IDENTITY)
		private Long id;
		@Column(name="phone_number")
		private String number;
		@ManyToOne()
		private Person person;
		}

该注解查询的时候,就相当于数据库order by 关键字。@orderby 后面是指定集合子实体的属性名。可以指定多个集合字实体的属性名( @OrderBy(“name ASC, type DESC”))

@OrderColumn 用来指定某一个列作为排序列

将@OrderBy 注解去除,修改如下,其他均不变:

	@OrderColumn(name="order_id")

我们发现在Phone 表中,多出了该列,但是在查询的语句中,我们可以看出,并没有在数据库层面上排序,而是加载集合对象,在内存中,进行排序,这点是和上面排序不同的地方。

我们在插入的Phone 表的时候,hibernate 会自动帮我们填充order_id 的值,该值是从0 开始递增的。我们如果自己在数据库设置值,也必须从0 开始,否则会报错。

Set

Set 是集合但是不允许出现重复的条目。

@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
    @OneToMany(cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
	//一个有多个电话
	private Set<Phone> phones = new HashSet<Phone>();
	}
@Entity(name="Phone")
@Table(name="phone")
public class Phone {
		@Id
		@GeneratedValue(strategy=GenerationType.IDENTITY)
		private Long id;
		@Column(name="phone_number")
		private String number;
		@ManyToOne
		private Person person;
		}

集合中使用Set 接口,一定要注意,重写子实体属性(普通属性即可)的hashcode和equals 方法,必须要保证不可以重复

Sorted sets

对于排序的Set,常使用Java 的SortedSet。对于SortedSet 类型的集合实体,必须要实现Comparable ,提供排序方法。

@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
    @OneToMany(cascade=CascadeType.ALL,mappedBy="person",orphanRemoval=true)
	//一个有多个电话
    @SortNatural	
    private SortedSet<Phone> phones = new TreeSet<Phone>();

	}
@Entity(name="Phone")
@Table(name="phone")
public class Phone implements Comparable<Phone> {
		@Id
		@GeneratedValue(strategy=GenerationType.IDENTITY)
		private Long id;
		@Column(name="phone_number")
		private String number;
		@ManyToOne
		private Person person;
		
		@Override
		public int compareTo(Phone o) {
			// TODO Auto-generated method stub
			return number.compareTo(o.number);
		}
		}

对于排序的集合set,必须用 @SortNatural 指定,同时集合实体实现Comparable 接口,如果想自己自定义排序方法,可以通过 @SortComparator(Xxxx.class),该类实现Comparator接口。

Map

主要对应于Java 的Map 接口

  • Map 的键为组件类,值为普通值
@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="person_name")
	private String name;
    @Temporal(TemporalType.TIMESTAMP)//用来指定Date 类型对应是时间戳
    @CollectionTable(name="person_phone_date")//这个注解会把Map 的键值映射成一张表,可以没有,就采用默认的,默认是关联person 实体的主键
    @ElementCollection//申明该属性是集合类型
    @Column(name="since")//这个column 映射的是map 的值
    private Map<Phone,Date> map = new HashMap<Phone, Date>();
    }

	
		public void addSince(Phone phone,Date date){
			map.put(phone, date);//这里由于phone 作为组件,所以直接设置值,它会跟着person 的持久化,(如果是实体,就要单独持久化)一起到数据库的。
		}

		public void removeSince(Phone phone){
			map.remove(phone);
		}
@Embeddable
public class Phone{
		@Column(name="phone_number",unique=true)
		private String number;
		}

这里的Phone 在集合中作为组件类使用,这里由于用了Phone 组建类作为Map 的键,需要重写hashcode、equals。

           Person person = new Person();
		   person.setName("laotie");
		   Phone phone = new Phone();
		   phone.setNumber("122223455");
		   person.addSince(phone, new Date());//因为上面将Date类型的值,映射成时间戳,那么你这里传Date类型,会自动转成时间戳。
		   ss.save(person);

查看数据库表,可以看出person_phone_date 里面包含person 的id 引用,phone 作为组件类,所有的属性也映射到表中,map 的value,since 也在该表中。

  • 定制自己的map的键
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="person_name")
	private String name;
    @CollectionTable(name="person_phone_date",joinColumns=@JoinColumn(name="person_id1"))//这个注解会把Map 的键值映射成一张表
    @ElementCollection
    @MapKey(name="map_key")//用来指定map 键的列名
    @Column(name="phone_number")//这个column 映射的是map 的值
    private Map<Date,Integer> map = new HashMap<Date,Integer>();

这时你看到自己map 键的类型是Date 因为没有指定 @Temporal,这里影响不大,hibernate会采用默认的Date 类型,这里如果想重写一个类作为键的类型(这里和键的类型是组件类,不是一个概念,不要乱),就需要@MapKeyType ,由于用的比较少,这里就不演示了。

  • Map 的键是接口类型
public interface PhoneInterface {
             
}

@Embeddable
public class MobilePhone implements PhoneInterface {
	@Column(name="mobile_phone_number")
	private String number;

	public String getNumber() {
		return number;
	}

	public void setNumber(String number) {
		this.number = number;
	}

}
@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="person_name")
	private String name;
	//一个有多个电话
    @CollectionTable(name="person_mobile_phone",joinColumns=@JoinColumn(name="person_id"))//这个注解会把Map 的键值映射成一张表
    @ElementCollection
    @MapKeyClass(MobilePhone.class)//虽然下面是map 的键是接口类型,但是在这申明接口的实现类,注意MobilePhone需要使用      	    @Embeddable 注解。**由于是作为map 键,所以最好重写hashcode和equals方法**。
    @Column(name="phone__mobile_number")//这个column 映射的是map 的值
    private Map<PhoneInterface,String> map  = new HashMap<PhoneInterface, String>();//因为该map 的键是组件类(由多个属性组成),顾不需要设置@mapkeycolumn
    }

和上面一样person_mobile_phone 包含person的主键 还有MobilePhone 的键的字段属性,组件类的形式嵌套在该表中,以及map 的value 属性。

添加查询操作:

           Person p = new Person();
		   p.setName("laotie");
		   MobilePhone mp = new MobilePhone();
		   mp.setNumber("12334");
		   p.addMobileNumber(mp,"smallliu");
		   ss.save(p);
		   Person p1 = ss.load(Person.class,1L);
		   Map<PhoneInterface, String> map = p1.getMap();
		   Set<PhoneInterface> set = map.keySet();
		   Iterator<PhoneInterface> iterator = set.iterator();
		   while(iterator.hasNext()){
			   MobilePhone mb = (MobilePhone) iterator.next();
			   System.out.println(mb.getNumber()+" "+map.get(mb));
		   }
  • Map 的单向关系

请注意,当出现实体的时候,这时就会形成关系。

@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="person_name")
	private String name;
	//这里的一对多,不是那么简单,对应多个实体,而是一个人有多个map,这样的关系,map 里面包含实体
	@OneToMany(cascade=CascadeType.ALL,orphanRemoval=true)
	**//我们都知道一1对多的关系,无需设置中间表,这里为啥要设置一个中间表不明白,还望他人指点。**
	@JoinTable(
			name="person_map_phone",
			joinColumns=@JoinColumn(name="person_id"),//joinColumns操作外键关联主表的主键,正是配置是单向关系,所以才知道person 是主表
			inverseJoinColumns=@JoinColumn(name="phone_id")//操作外键关联副表(两个实中的另一个实体)的主键
			)
	@MapKeyColumn(name="since")
	//实际上这里map value 是一个实体,无法保存在中间表,所以,我们保存phone 主键
	//因为map 的value 是一个实体,无需为它设置@column 设置value的列名
	@MapKeyTemporal(TemporalType.TIMESTAMP)//将map 的key 的date 映射成时间戳
	private Map<Date,Phone> map = new HashMap<Date,Phone>();
@Entity(name="Phone")
@Table(name="phone")
public class Phone{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
		@Column(name="phone_number",unique=true)
		private String number;
		}

添加操作
在person 补充如下的方法:

public void addPhone(Phone p,Date date){
			map.put(date,p);
			//因为设置map 的级联属性,保存person,会save phone 实体的
			
		}

在这,我要说明一下的,就是@MapKeyColumn和@MapKey

@MapKeyColumn 对于基本类型(组件类型排除在外)用来指定map 的key 的列名
@MapKey 这个用来map 的key 是map value 里面实体的某个属性,如果map value 没有该属性名,则报错。

  • map 的双向关系

由于是双向关系,就比上面的配置容易很多,就不需要设置一个中间表。(这也就是我不明白,上面为啥要搞一个中间表)

@Entity(name="Person")
@Table(name="person")
public class Person{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="person_name")
	private String name;
	@MapKeyEnumerated//用来指定map 的key 为枚举类型
	@OneToMany(cascade=CascadeType.ALL,orphanRemoval=true,mappedBy="person")
	@MapKeyColumn(name="enum_phone")//指定map key 对于的列名
	private Map<EnumType,Phone> map = new HashMap<EnumType, Phone>();
	public void addPhone(Phone p,EnumType e){
		p.setPerson(this);//设置控制关系,控制权在phone
		map.put(e,p);
	}
	}
@Entity(name = "Phone")
@Table(name = "phone")
public class Phone {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(name = "phone_number", unique = true)
	private String number;
	@ManyToOne
	private Person person;
	}
public enum EnumType {
         RED,
         YEELLO
}

通过上述的设置,可以不通过使用中间表,将map 的key 和value 都存放在phone 表中。

数组

hibernate 不支持本地数据库的数组类型,支持Java 类型的数组,由于数组不具备类似集合的懒加载,从性能上看,不是很支持使用。

@Entity(name="Person11")
@Table(name="person11")
public class Person11 {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	private String[] phones;
}

查看数据库,mysql底层给数组类型,用tinyblob tinyblob。

零散的一些注解
  • @table 用来指定注解实体对应的表,这里的name是可选,如果你不指定, 那么数据库的表名,就是你自己的实体类名(小写)。该注解中有一个属性uniqueConstraints,因为一个表 可以有多个唯一约束,可以通过@UniqueConstraint ,来指定每一个唯一约束由哪些字段组成。这里列名,就是你字段对应属性配置的@column的name。
  • @Transient 用来指定某个字段不需要持久化。
  • @Assess Hibernate使用的访问类型将是字段或属性。即注解如果添加在getter方法,使用PROPERTY访问,必须提供set、get方法;如果注解添加在字段上,则为AccessType.FIELD,则对set、get方法无要求。 应避免在两个字段和方法中混合注释。 Hibernate将从@Id或@EmbeddedId的位置猜测访问类型。 推荐使用PROPERTY。建议整个项目应该使用一致的访问策略,避免引来不必要的麻烦。
  • @Temporal 用来映射字段类型java.util.Date and java.util.Time(不要去定义java.sql包下的),里面有三种类型,可以去映射,Date 含年月日,Time含时分秒,TIMESTAMP含年月日时分秒。
  • @Id 用于指定某一个字段映射成主键,如果某个键需要申明成主键,只要用该属性设置即可,无法在使用@column,这个是映射普通列。
  • @GeneratedValue 这个注解 用来指定主键生成策略:有下面四种
  1. GenerationType.TABLE 使用一个特定的数据库表格来保存主键,持久化引擎通过关系数据库的一张特定的表格来生成主键。该策略一般与另外一个注解一起使用@TableGenerator,@TableGenerator注解指定了生成主键的表(可以在实体类上指定也可以在主键字段或属性上指定),然后JPA将会根据注解内容自动生成一张表作为序列表(或使用现有的序列表)。如果不指定序列表,则会生成一张默认的序列表,表中的列名也是自动生成.
  2. GenerationType.SEQUENCE 在某些数据库中,不支持主键自增长,比如Oracle,其提供了一种叫做"序列(sequence)"的机制生成主键。此时,GenerationType.SEQUENCE就可以作为主键生成策略。
  3. GenerationType.IDENTITY 此种主键生成策略就是通常所说的主键自增长,数据库在插入数据时,会自动给主键赋值,比如MYSQL可以在创建表时声明"auto_increment" 来指定主键自增长
  4. GenerationType.AUTO 把主键生成策略交给持久化引擎(persistence engine),持久化引擎会根据数据库在以上三种主键生成策略中选择其中一种。

该注解有一个属性generator 是用来指定主键生成的东东,SequenceGenerator or TableGenerator 属性的值,就是这些生产者name的字符串。

@TableGenerator 之前在讲解主键生成的,有一种是通过数据库表格,就是该注解去处理。里面有的属性即使用参看下图:
在这里插入图片描述

在这里插入图片描述

生成器的表中只会有一个数据,这个数据的值会根据你的设置,每次自增的。

@SequenceGenerator 和@TableGenerator 很类似,就是它是针对oracle的主键的序列

  • @Entity 主要标识该类为实体类,里面有一个name,使用该注解,在hql可以识别,你想想hql 怎么从查询语句,知道某个对象、属性的,就是靠它。这个每一个实体类还必须要添加该注解,否则,Hibernate找不到。,并不是所有的都要改注解,在一些组件类,是不需要,该注解只是针对实体类。如果不显示添加name 属性,默认就是不带包名的该类名。

  • @Basic 主要是用于字段和column的基本映射,fetch 主要设置是否懒加载(Hibernate在创建一个实体Bean的实例时,不会即时将这个属性的值从数据库中读出. 只有在该实体Bean的这个属性第一次被调用时,Hibernate才会去获取对应的值),加载模式默认不是懒加载,optional 字段是否可以为空。

  • @Enumerated 指定某个字段持久化成枚举类型 ,你需要自己先定义枚举类,它有两种策略,EnumType.STRING 在持久化值的必须根据枚举类的名字去插入,EnumType.ORDINAL 在持久化值的可以根据枚举类属性值定义的顺序去插入。

  • @Lob 将数据库字段映射成大的对象类型,大文本类型名称为clob,大二进制类型名称为blob。该注解既可以
    映射二进制,也可以映射字符,看你字段的类型。(常用某个描述、自我介绍、文件、图片),读取该属性需要使用inputstream。

  • @Column 将字段和列对应 。常用属性:
    name 可选,列名(默认值是属性名)
    unique 可选,是否在该列上设置唯一约束(默认值false)
    nullable 可选,是否设置该列的值可以为空(默认值false)
    insertable 可选,该列是否作为生成的insert语句中的一个列(默认值true)
    updatable 可选,该列是否作为生成的update语句中的一个列(默认值true)
    columnDefinition 可选: 为这个特定列覆盖SQL DDL片段 (这可能导致无法在不同数据库间移植)
    table 可选,定义对应的表(默认为主表)
    length 可选,列长度(默认值255)
    precision 可选,列十进制精度(decimal precision)(默认值0)
    scale 可选,如果列十进制数值范围(decimal scale)可用,在此设置(默认值0)

  • @IdClass 用来指定一个复合主键。

public class CompitedId implements Serializable {
	private String title;
	private String name;
	
	public CompitedId(String title, String name) {
		this.title = title;
		this.name = name;
	}
	
	public CompitedId() {
		super();
		// TODO Auto-generated constructor stub
	}

@Entity(name = "People")
@Table(name = "people")
@IdClass(CompitedId.class)
public class People {
	@Id
	private String name;
	@Id
	private String title;
	@Column(name="topic")
	private String topic;
		public People(String name, String title) {
		this.name = name;
		this.title = title;
	}
	}
 		   People p = new People("liu","lao");
		   p.setTopic("ti");
		   ss.save(p);
		   ss.flush();
		   People p1 = ss.load(People.class,new CompitedId("lao","liu"));//注意对应后构造器参数的一致
		   //对于复合主键的记录查询,必须要构造一个复合主键对象
		   System.out.println(p1.getName());

如果是基于HQL查询:

List list = ss.createQuery("select p from People  p  where  p.name=:name1 and p.title=:title1").setParameter("name1","lao").setParameter("title1","liu").list();
		   System.out.println(list.size());

//这里和@EmbeddedId,就有所不同,因为不是嵌套组件对象,所以,可以直接引用。

在具体的表中通过@IdClass 来指定复合主键的类,通过还要重写在这里定义复合主键的属性,必须和复合主键类的属性一致,同时用@Id,分别去注解。

复合主键要保证唯一,必须要实现hashcode、equals方法,同时实现序列化接口

注意设置复合主键,就不可以设置主键递增,必须是自己手动赋值。

注意复合主键类必须要提供默认的构造器,否则会报异常。

  • @Index

该注解用于在表中,根据某些字段去产生相应的索引。

@Entity(name = "People")
@Table(name = "people",indexes={//一个表中可以有多个索引。
		@Index(unique=false,columnList="first_name,last_name",name="name_index")//columnList 里面的值应该是你对应属性配置对应的列,一个索引可以参考多个列,所以可以有多个列值,用逗号区分,unique 用于指定该索引是否唯一,name 是指定索引的名字。
})
public class People {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	@Column(name="first_name")
	private String firstname;
	@Column(name="last_name")
	private String lastname;
	}

参考学习
参考学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值