在博客站点中,我们可能须要从某一篇文章找到其所关联的作者。这就须要从文章方建立起对用户的关联,即是多对一的映射关系。
如今先看一个配置实例:我们的文章实体类
package com.zeng.model;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Table(name = "t_article")
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Lob//数据可能会很长,映射为数据库支持的“大对象”
private String content;
/**
* @ManyToOne 使用此标签建立多对一关联,此属性在“多”方使用注解在我们的“一”方属性上
* @cascade 指定级联操作,以数组方式指定,假设仅仅有一个,能够省略“{}”
* @fetch 定义抓取策略
* @optional 定义是否为必需属性,假设为必需(false),但在持久化时user = null,则会持久化失败
* @targetEntity 目标关联对象,默觉得被注解属性所在类
*/
@ManyToOne(cascade ={CascadeType.ALL},fetch = FetchType.LAZY,optional = false,targetEntity = User.class)
private User user;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
由于这里是单向关联,所以我们无须在在User类中建立对文章的关联属性
接下来编写我们的測试类
package com.zeng.test;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.zeng.model.Article;
import com.zeng.model.User;
public class Test2 {
private static ApplicationContext ac;
private static SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@BeforeClass//在測试类初始化时调用此方法,完毕静态对象的初始化
public static void before(){
ac = new ClassPathXmlApplicationContext("spring-datasource.xml");
sessionFactory = (SessionFactory) ac.getBean("sessionFactory");
}
@Before//每个被注解Test方法在调用前都会调用此方法一次
public void setup(){//建立针对我们当前測试方法的的会话和事务
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
//測试级联关系映射注解配置:多对一单向关联
@Test
public void test1(){
User user = new User();
user.setName("name1");
Article article = new Article();
article.setContent("content1");
article.setUser(user);//建立级联关系
session.save(article);//注意这里我们没有保存我们的user对象
}
@After//每个被注解Test方法在调用后都会调用此方法一次
public void teardown(){
transaction.commit();
session.clear();
session.close();
}
@After//在类销毁时调用一次
public void after(){
sessionFactory.close();
}
}
调用上面測试方法,我们会发现,hibernate帮我们在上篇文章已建立User类的基础上,又帮我们创建了t_article数据表:
mysql> desc t_article;
+———+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| content | varchar(255) | YES | | NULL | |
| user_id | int(11) | NO | MUL | NULL | |
+———+————–+——+—–+———+—————-+
3 rows in set (0.00 sec)然后我们查看用户表和文章表。会看到:
mysql> select * from t_user;
+—-+——-+
| id | name |
+—-+——-+
| 1 | name1 |
+—-+——-+
1 row in set (0.00 sec)mysql> select * from t_article;
+—-+———-+———+
| id | content | user_id |
+—-+———-+———+
| 1 | content1 | 1 |
+—-+———-+———+
1 row in set (0.00 sec)
能够看到。这里我们的user_id和user表的新建记录id是相应的。
看完实例,下面我们针对配置的属性进行详细分析:
1. cascade属性
属性 | 说明 |
---|---|
CascadeType.MERGE | 级联更新:若user属性改动了那么article对象保存/更新时同一时候改动user在数据库里的属性值 |
CascadeType.PERSIST | 级联保存:对article对象保存时也对user里的对象也会保存。 |
CascadeType.REFRESH | 级联刷新:获取article对象里也同一时候也又一次获取最新的user时的对象。即会又一次查询数据库里的最新数据 |
CascadeType.REMOVE | 级联删除:对article对象删除也会使相应user的象删除 |
CascadeType.ALL | 包括PERSIST, MERGE, REMOVE, REFRESH, DETACH等; |
级联属性对于一方和多方的作用效果是不一样的。经測试发现,在多对一中,多方使用CascadeType.PERSIST无法级联保存对象,必须使用CascadeType.ALL。而级联删除既可使用CascadeType.REMOVE也可使用CascadeType.ALL
对于上述方法,假设我们没有设置级联保存,在我们保存文章对象时,用户对象自然不会持久化到数据库,这时候会报错:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.zeng.model.User
在我们提交事务的时候,hibernate总会flush(清理)我们的session缓存。所谓清理,是指hibernate依照持久化对象的属性变化来同步更新数据库,当发现我们的article对象引用了暂时对象user,而article.user.id = null,会推断user对象是瞬时的Transient,这个我们要持久化到数据库中的article对象发生冲突,因此会保存失败。这里我们也意在说明,关系直接的级联映射是通过用户对象标识符id来确认的。意思是说,即使article.user.其它属性全为null,但仅仅要article.user.id在数据库中有相关记录(saved)这时就能建立两者的级联关系了。
还有一方面,假设我们习惯了xxx.htm.xml的方式来配置我们的实体映射关系,那我们必定对hibernate的级联属性更加熟悉,这时我们能够通过@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})来使用hibernate内置级联属性。关于hibernate的内置级联属性常见有:
属性名 | 说明 |
---|---|
save-update | 级联保存(load以后假设子对象发生了更新。也会级联更新)。 但它不会级联删除 |
delete | 级联删除, 但不具备级联保存和更新 |
all-delete-orphan | 在解除父子关系时,自己主动删除不属于父对象的子对象, 也支持级联删除和级联保存更新。 |
all | 级联删除, 级联更新。但解除父子关系时不会自己主动删除子对象。 |
delete-orphan | 删除全部和当前对象解除关联关系的对象 |
2. @JoinColumn
它的详细值可參照下表
属性 | 默认值 | 说明 |
---|---|---|
columnDefinition | 空 | JPA 使用最少量 SQL 创建一个数据库表列。
假设须要使用很多其它指定选项创建列,将 columnDefinition 设置为在针对列生成 DDL 时希望 JPA 使用的 String SQL 片断。 |
insertable | true | 默认情况下,JPA 持续性提供程序假设它能够插入到全部表列中。假设该列为仅仅读,请将 insertable 设置为 false。 |
name | 默认值 | 假设使用一个连接列,则 JPA 持续性提供程序假设外键列的名称是下面名称的连接: 1. 引用关系属性的名称 +“”+ 被引用的主键列的名称。 2. 引用实体的字段名称 +“”+ 被引用的主键列的名称。 3. 假设实体中没有这种引用关系属性或字段(请參阅 @JoinTable),则连接列名称格式化为下面名称的连接:实体名称 +“_”+ 被引用的主键列的名称。这是外键列的名称。 假设连接针对“一对一”或“多对一”实体关系,则该列位于源实体的表中。假设连接针对“多对多”实体关系。则该列位于连接表(请參阅 @JoinTable)中。 4. 假设连接列名难于处理、是一个保留字、与预先存在的数据模型不兼容或作为数据库中的列名无效,请将 name 设置为所需的 String 列名。 |
nullable | true | 默认情况下,JPA 持续性提供程序假设同意全部列包括空值。假设不同意该列包括空值。请将nullable 设置为 false。 |
referencedColumnName | 无 | 假设使用一个连接列,则 JPA 持续性提供程序假设在实体关系中,被引用的列名是被引用的主键列的名称。
假设在连接表(请參阅 @JoinTable)中使用,则被引用的键列位于拥有实体(假设连接是反向连接定义的一部分,则为反向实体)的实体表中。要指定其它列名,请将 referencedColumnName 设置为所需的 String 列名。 |
table | 无 | JPA 持续性提供程序假设实体的全部持久字段存储到一个名称为实体类名称的数据库表中(请參阅 @Table)。假设该列与辅助表关联(请參阅 @SecondaryTable)。请将 name 设置为相应辅助表名称的 String 名称 |
unique | false | 默认情况下,JPA 持续性提供程序假设同意全部列包括反复值。假设不同意该列包括反复值,请将 unique 设置为 true。 |
updatable | true | 默认情况下,JPA 持续性提供程序假设它能够更新全部表列。
假设该列为仅仅读。则将 updatable 设置为 false |