@OneToMany、@ManyToOne以及@ManyToMany讲解

双向一对多关系,一是关系维护端(owner side),多是关系被维护端(inverse side)。在关系被维护端需要通过@JoinColumn建立外键列指向关系维护端的主键列

 
 
publicclass  Order  implements  Serializable {
     private Set orderItems  =  new  HashSet();
          。。。。
     @ OneToMany (mappedBy= "order" ,cascade = CascadeType. ALL , fetch = FetchType. LAZY )
     @ OrderBy (value =  "id ASC" )
     public  Set getOrderItems() {
         return orderItems ;
     }
}
 
publicclass  OrderItem  implements  Serializable {
private  Order  order ;
。。。。
     @ ManyToOne (cascade=CascadeType. REFRESH ,optional= false )
     @ JoinColumn (name =  "order_id" )
     public  Order getOrder() {
         return order ;
     }
}
@OrderBy(value = "id ASC")  指明加载 OrderItem  时按 id  的升序排序
 
@OneToMany 的属性
1>targetEntity
定义关系类的类型,默认是该成员属性对应的类类型,所以通常不需要提供定义
 
2>mappedBy
定义类之间的双向关系。如果类之间是单向关系,不需要提供定义如果类和类之间形成双向关系,我们就需要使用这个属性进行定义,否则可能引起数据一致性的问题
该属性的值是“多”方class里的“一”方的变量名
 
3>cascade
该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操作,而且这种关系是递归调用的。举个例子: Order  OrderItem 有级联关系,那么删除 Order 时将同时删除它所对应的 OrderItem 对象。而如果 OrderItem 还和其他的对象之间有级联关系,那么这样的操作会一直递归执行下去。
 
cascade 的值只能从 CascadeType.PERSIST (级联新建)、 CascadeType.REMOVE (级联删除)、 CascadeType.REFRESH (级联刷新)、 CascadeType.MERGE (级联更新)中选择一个或多个。还有一个选择是使用 CascadeType.ALL ,表示选择全部四项。
 
4>fatch
可选择项包括: FetchType.EAGER FetchType.LAZY 。前者表示关系类 ( 本例是 OrderItem  ) 在主类 ( 本例是 Order ) 加载的时候同时加载,后者表示关系类在被访问时才加载。默认值是 FetchType.LAZY
 
@JoinColumn(name = "order_id") 注释指定OrderItem映射表的order_id列作为外键与Order 映射表的主键列关联。
 
@ManyToOne :指明OrderItem和Order之间为多对一关系。
 
@ManyToOne 注释有四个属性:targetEntity、cascade、fetch 和optional,前三个属性的具体含义和@OneToMany的同名属性相同,但@ManyToOne的fetch 属性默认值是FetchType.EAGER
 
optional 属性是定义该关联类是否必须存在值为false 时,关联类双方都必须存在,如果关系被维护端不存在,查询的结果为null。值为true 时, 关系被维护端可以不存在,查询的结果仍然会返回关系维护端,在关系维护端中指向关系被维护端的属性为nulloptional属性的默认值是trueoptional 属性实际上指定关联类与被关联类的join 查询关系,如optional=false 时join 查询关系为inner join, optional=true 时join 查询关系为left join。下面代码片断解释如下:
 
 
 
 
 
有一点需要强调:当业务方法需要把一个实体 Bean 作为参数返回给客户端时,除了实体 Bean 本身需要实现 Serializable  接口之外 ,如果关联类(OrderItem)是延迟加载,还需在返回实体Bean之前通过访问关联类的方式加载关联类(见下例)。否则在客户端访问关联类时将会抛出加载例外。
     public  Order getOrderByID(Integer orderid) {
        Order order =  em .find(Order. class , orderid);        
        //!!!!! 因为是延迟加载,通过执行 size() 这种方式获取订单下的所有订单项
        order.getOrderItems().size();
        return  order;
     }
 
另外不管是否延迟加载,通过join fetch 关联语句都可显式加载关联类 ,如下例:
 
     public  List getAllOrder() {
         Query query =  em .createQuery( "select DISTINCT o from Order o inner
join fetch o.orderItems order by o.orderid" );
         List result = query.getResultList();
         return  result;

     }




hibernate之实体@onetomany和@manytoone双向注解

下面是User类:

@onetomany

@Entity
@Table(name="user")
public class User implements Serializable{
 
     
    private static final long serialVersionUID = 6177417450707400228L;
     
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name="id")
    private int id;
     
    @Column(name="name")
    private String name;
     
    @Column(name="password")
        private String password;
     
    /*
     * cascade:为级联操作,里面有级联保存,级联删除等,all为所有 
     * fetch:加载类型,有lazy和eager二种,
     *   eager为急加载,意为立即加载,在类加载时就加载,lazy为慢加载,第一次调用的时候再加载,由于数据量太大,onetomany一般为lazy
     * mappedBy:这个为manytoone中的对象名,这个不要变哦
     * Set:这个类型有两种,一种为list另一种为set
     * 
     *
     */
    @OneToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="user")
    private Set setRole; 
     
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
     
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
 
    public Set getSetRole() {
        return setRole;
    }
 
    public void setSetRole(Set setRole) {
        this.setRole = setRole;
    }
     
}




@manytoone

@Entity
@Table(name="role")
public class Role {
     
     
    private static final long serialVersionUID = 6177417450707400228L;   
     
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="role_id")
    private int role_id;
     
    @Column(name="role_name")
    private String role_name;
     
    @ManyToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
    @JoinColumn(name="user_id")//加入一列作为外键
    private User user;
     
    public int getRole_id() {
        return role_id;
    }
    public void setRole_id(int role_id) {
        this.role_id = role_id;
    }
     
    public String getRole_name() {
        return role_name;
    }
    public void setRole_name(String role_name) {
        this.role_name = role_name;
    }
     
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
 
}

有了这二个注解,我们不需要建表,hibernate会为我们自动建立表以及各种表级联系

其中@table为映射表名,@column为映射列名,其他的我不一一细说了。
下面就是操作了,定义了级联操作,下面是保存的。

public Role save() {
        User user=new User();
        user.setName("123456");
        user.setPassword("123456");
                 
        Role role=new Role();
        role.setRole_name("student");
        role.setUser(user);
         
        Role roler=registerDao.save(role);
         
        return roler;
    }



  使用HIBERNATE的注解@ManyToOne(fetch = FetchType.lazy) 时候,报出错误 

  1. org.hibernate.LazyInitializationException: could not initialize proxy - no Session  
  2. at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)  
  3. at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)  
  4. at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)  
  5. at com.evolution.warlock.entity.Department_$$_javassist_1.getName(Department_$$_javassist_1.java)  
  6. at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)  
  7. at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267)  
  8. at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198
找了下答案,主要是因为: 
Html代码   收藏代码
  1. fetch=FetchType.LAZY就等于打开了延迟加载,Hibernate中的get方法不论是否开启延迟加载都会一次加载所有基本数据类型属性的值,而load则不同,开启延迟加载后load(id),返回的对象会被Hibernate加一个壳(持久化上下文中没有缓存该对象的前提下),拦截所有非id属性的访问操作(getter,setter调用),只有id属性中有实际值(其实就是你调用load方法时传的那个),在session没有关闭的之前,如果访问除id外的其他属性才会发sql语句去查询,你这个错误就是因为在当前session关闭以后访问由load()加载的对象的非id属性,此时Hibernate尝试通过当前session发sql查询,但发现session已经关闭,这样才会报错。  


   我测试发现的确是上面所说的,LAZY阻挡了除ID以外的所有访问,所以解决的办法就很多, 
   一.就是只拿ID,再从数据库从新拿一次你需要的东西。 
   二.将FetchType.lazy 改成 EAGER 
   三.配置xml (没试过) 
Xml代码   收藏代码
  1. <filter>  
  2.   <filter-name>openSessionInViewfilter-name>  
  3.   <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilterfilter-class>  
  4. filter>  
  5.   
  6. <filter-mapping>  
  7.   <filter-name>openSessionInViewfilter-name>  
  8.   <url-pattern>/*url-pattern>  
  9. filter-mapping>  

   四.在service层使用Hibernate.initialize()将你需要的对象从新持久化 

   五.在MVC里面配置,个人觉得这个办法更好




一、一对多(@OneToMany)
1、单向一对多模型
假设通过一个客户实体可以获得多个地址信息。
对于一对多的实体关系而言,表结构有两种设计策略,分别是外键关联和表关联。
(1) 映射策略---外键关联
在数据库中表customer和表结构address定义,如下:

create table customer (
  id int(20) not null auto_increment,
  name varchar(100),
  primary key(id)
)

create table address (
  id int(20) not null auto_increment,
  province varchar(50),
  city varchar(50),
  postcode varchar(50),
  detail varchar(50),
  customer_id int(20),
  primary key (id)
)

注意此时外键定义在多的一方,也就是address表中。

 此时,表customer映射为实体CustomerEO,代码如下:

@Entity
@Table(name="customer")
public class CustomerEO implements java.io.Serializable {
  @OneToMany(cascade={ CascadeType.ALL })
  @JoinColumn(name="customer_id")
  private Collection addresses = new ArrayList();
 ...
}
注释@OneToMany的定义代码如下:
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToMany {
  Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fetch() default LAZY;
  String mappedBy() default "";
}
使用时要注意一下几点问题: 
a、targetEntity属性表示默认关联的实体类型。如果集合类中指定了具体类型了,不需要使用targetEntity.否则要指定targetEntity=AddressEO.class。 
b、mappedBy属性用于标记当实体之间是双向时使用。 

(2) 映射策略---表关联 
在上面address表中去掉customer_id字段,在增加一个表ref_customer_address,如下: 
--客户地址关系表
create table ref_customer_address (
  customer_id int(20) not null,
  address_id int(20) not null unique
)
此时表customer映射为CustomerEO实体,代码如下:
@Entity
@Table(name = "customer")
public class CustomerEO implements java.io.Serializable {
  ...
  @OneToMany(cascade = { CascadeType.ALL })
  @JoinTable(name="ref_customer_address",
           joinColumns={ @JoinColumn(name="customer_id",referencedColumnName="id")},
           inverseJoinColumns={@JoinColumn(name="address_id",referencedColumnName="id")})
  private Collection addresses = new ArrayList();
  ...
}
表关联@JoinTable,定义如下:
@Target({METHOD,FIELD}) 
public @interface JoinTable {
  String name() default "";
  String catalog() default "";
  String schema() default "";
  JoinColumn[] joinColumns() default {};
  JoinColumn[] inverseJoinColumns() default {};
  UniqueConstraint[] uniqueConstraints default {};
}

其中:
a、该标记和@Table相似,用于标注用于关联的表。
b、name属性为连接两张表的表名。默认的表名为:“表名1”+“-”+“表名2”,上面例子默认的表名为customer_address。
c、joinColumns属性表示,在保存关系中的表中,所保存关联的外键字段。
d、inverseJoinColumns属性与joinColumns属性类似,不过它保存的是保存关系的另一个外键字段。

(3) 默认关联
在数据库底层为两张表添加约束,如下:

create table customer_address (
  customer_id int(20) not null,
  address_id int(20) not null unique
)
alter table customer_address add constraint fk_ref_customer foreign key (customer_id) references customer (id);

alter table customer_address add constraint fk_ref_address foreign key (address_id) references address (id);

这样,在CustomerEO中只需要在标注@OneToMany即可!


二、多对一@ManyToOne
1、单向多对一模型。
(1) 外键关联
配置AddressEO实体如下:

@Entity
@Table(name="address")
public class AddressEO implements java.io.Serializable {
	
  @ManyToOne(cascade = { CascadeType.ALL })
  @JoinColumn(name="customer_id")
  private CustomerEO customer;
	
  // ...
}

@ManyToOne定义如下:

@Target({METHOD,FIELD}) @Retention(RUNTIME)
public @interface ManyToOne {
  Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fatch() default EAGER;
  boolean optional() default true;
}


(2) 默认关联
数据库脚本定义的相关字段的约束,创建外键后,直接使用@ManyToOne

三、高级一对多和多对一映射
即双向关联模型,确定了双向关联后,多的一方AddressEO不变使用@ManyToOne,而CustomerEO实体修改为:

@Entity
@Table(name="customer")
public class CustomerEO {
	
  @OneToMany(mappedBy="customer")
  private Collection addresses = new ArrayList();
	
  // ...
}
其中,@OneToMany标记中的mappedBy属性的值为AddressEO实体中所引用的CustomerEO实体的属性名。 

四、多对多(@ManyToMany)
和一对多类型,不在赘述。@ManyToMany标记的定义如下:

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface ManyToMany {
  Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fecth() default LAZY;
  String mappedBy() default "";
}


五、最后,谈谈关于集合类的选择
在映射关系中可以使用的集合类有Collection、Set、List和Map,下面看下如何选择。
1、定义时使用接口,初始化使用具体的类。
如Collection可以初始化为ArrayList或HashSet;
Set可以初始化为HashSet;
List可以初始化为ArrayList;
Map可以初始化为HashMap.
2、集合类的选择
Collection类是Set和List的父类,在未确定使用Set或List时可使用;
Set集合中对象不能重复,并且是无序的;
List集合中的对象可以有重复,并且可以有排序;
Map集合是带有key和value值的集合。

https://my.oschina.net/liangbo/blog/92301

你可能感兴趣的:(java)