前言
本篇文章主要讲解有关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 这个注解 用来指定主键生成策略:有下面四种
- GenerationType.TABLE 使用一个特定的数据库表格来保存主键,持久化引擎通过关系数据库的一张特定的表格来生成主键。该策略一般与另外一个注解一起使用@TableGenerator,@TableGenerator注解指定了生成主键的表(可以在实体类上指定也可以在主键字段或属性上指定),然后JPA将会根据注解内容自动生成一张表作为序列表(或使用现有的序列表)。如果不指定序列表,则会生成一张默认的序列表,表中的列名也是自动生成.
- GenerationType.SEQUENCE 在某些数据库中,不支持主键自增长,比如Oracle,其提供了一种叫做"序列(sequence)"的机制生成主键。此时,GenerationType.SEQUENCE就可以作为主键生成策略。
- GenerationType.IDENTITY 此种主键生成策略就是通常所说的主键自增长,数据库在插入数据时,会自动给主键赋值,比如MYSQL可以在创建表时声明"auto_increment" 来指定主键自增长
- 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;
}