双向一对多关系,一是关系维护端(owner side),多是关系被维护端(inverse side)。在关系被维护端需要通过@JoinColumn建立外键列指向关系维护端的主键列。
}
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<role>:这个类型有两种,一种为list另一种为set
*
*
*/
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="user")
private Set<role> 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<role> getSetRole() {
return setRole;
}
public void setSetRole(Set<role> setRole) {
this.setRole = setRole;
}
}
</role></role></role></role>
@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为映射列名,其他的我不一一细说了。
下面就是操作了,定义了级联操作,下面是保存的。
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) 时候,报出错误
- org.hibernate.LazyInitializationException: could not initialize proxy - no Session
- at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
- at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
- at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
- at com.evolution.warlock.entity.Department_$$_javassist_1.getName(Department_$$_javassist_1.java)
- at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
- at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267)
- at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
- 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 (没试过)
- <filter>
- <filter-name>openSessionInView</filter-name>
- <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>openSessionInView</filter-name>
- <url-pattern>/*</url-pattern>
- </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<AddressEO> addresses = new ArrayList<AddressEO>();
...
}
注释@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<AddressEO> addresses = new ArrayList<AddressEO>();
...
}
表关联@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<AddressEO> addresses = new ArrayList<AddressEO>();
// ...
}
其中,@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值的集合。