在上两篇文章里,我们详细地分别讲解了一对多和多对一的单向关联配置的具体属性含义,在这一篇文章里,我们完成两者的的整合建立双向关联。
在实际的博客网站中,我们可能需要根据文章读取作者(用户)信息,但肯定也要让用户能获取自己的文章信息,针对这种需求,我们可以建立文章(多)对用户(一)的双向关联映射。
下面先看实例映射配置文件:
/********************一方配置User********************/
@Entity
@Table(name = "t_user1")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,targetEntity = Article.class,orphanRemoval = true,mappedBy = "user")
@JoinColumn(name = "user_id")
private Set<Article> articles;
}
/*****************多方配置****************/
@Table(name = "t_article1")
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String content;
/**
* @ManyToOne 使用此标签建立多对一关联,此属性在“多”方使用注解在我们的“一”方属性上
* @cascade 指定级联操作,以数组方式指定,如果只有一个,可以省略“{}”
* @fetch 定义抓取策略
* @optional 定义是否为必需属性,如果为必需(false),但在持久化时user = null,则会持久化失败
* @targetEntity 目标关联对象,默认为被注解属性所在类
*/
@ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
private User user;
映射关系确立好,开始编写我们的测试文件:
public class Test2 {
private ApplicationContext ac;
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@BeforeClass
public static void before(){
}
@Before
public void setup(){
ac = new ClassPathXmlApplicationContext("spring-datasource.xml");
sessionFactory = (SessionFactory) ac.getBean("sessionFactory");
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@Test
public void test3(){
User user = new User();
user.setName("oneObject");
Set<Article> articles = new HashSet<Article>();
for(int i = 0 ; i < 3;i ++){
Article article = new Article();
article.setContent("moreContent" + i) ;
articles.add(article);
}
user.setArticles(articles);
session.save(user);
}
@After
public void teardown(){
if(transaction.isActive()){
transaction.commit();
}
session.clear();
session.close();
sessionFactory.close();
}
@After
public void after(){
}
}
这个时候,我们运行测试方法test3会发现报错:
org.hibernate.AnnotationException: Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn: com.zeng.model.User.articles
意思是,一旦被注解@mapperBy,即放弃了维护关联关系,而@JoinColumn注解的都是在“主控方”,因而我们需要注解在Article类中
@ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinColumn(name = "user_id",unique = false,updatable = true)
private User user;
然后我们再运行测试方法:会看到:
Hibernate: drop table if exists t_article1
Hibernate: drop table if exists t_user1
Hibernate: create table t_article1 (id integer not null auto_increment, content varchar(255), user_id integer, primary key (id))
Hibernate: create table t_user1 (id integer not null auto_increment, name varchar(255), primary key (id))
Hibernate: alter table t_article1 add index FK6D6D45665B90FD3C (user_id), add constraint FK6D6D45665B90FD3C foreign key (user_id) references t_user1 (id)——————在这里,我们添加了外键约束,这也是hibernate对象关联在数据库的重要体现
上面是我们的表创建工作,下面是记录创建工作
Hibernate: insert into t_user1 (name) values (?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent0, id=3, user=null}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.User{id=1, articles=[com.zeng.model.Article#1, com.zeng.model.Article#2, com.zeng.model.Article#3], name=oneObject}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent2, id=1, user=null}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent1, id=2, user=null}
参考前面一篇文章的测试结果,在一对多单向配置中,因为关联关系是有一方维护,所以在最后总有三句额外的update语句,来完成article表到user表的映射关系,但在user放弃维护权后,如果我们再尝试通过保存用户通过建立起两表的映射关系,是不成功的。 从蓝色粗体部分,似乎User和article建立了关联关系。事实上,这是一种伪关联,它看似让我们通过session.save(user)。就完成了4者的关联创建,但在数据库层次,他们的关联关系是没有建立的,这从蓝色记录Article记录中user=null可以说明这一点。
此外,我们可以通过测试尝试从user中获取article对象来进一步验证:
User user = (User) session.get(User.class, 1);
System.out.println("获取用户对应的文章数据:"+user.getArticles());
打印结果:获取用户对应的文章数据:[]
这是因为我们的关联信息是由多方维护的(user_id),我们想要真正完成两者,必须从主维护方:article下手
运行以下测试代码:
User user = new User();
user.setName("oneObject1");
for(int i = 0 ; i < 3;i ++){
Article article = new Article();
article.setContent("moreContent1" + i) ;
article.setUser(user);
session.save(article);
}
得到打印结果:
Hibernate: insert into t_user1 (name) values (?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent10, id=4, user=com.zeng.model.User#2}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent12, id=6, user=com.zeng.model.User#2}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent11, id=5, user=com.zeng.model.User#2}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.User{id=2, articles=null, name=oneObject1}
再查看数据库:
mysql> select * from t_article1;
+—-+—————+———+
| id | content | user_id |
+—-+—————+———+
| 1 | moreContent2 | NULL |——————上次操作遗留
| 2 | moreContent1 | NULL |——————上次操作遗留
| 3 | moreContent0 | NULL |——————上次操作遗留
| 4 | moreContent10 | 2 |
| 5 | moreContent11 | 2 |
| 6 | moreContent12 | 2 |
+—-+—————+———+
6 rows in set (0.00 sec)
从sql语句和蓝色DEBUG、数据库记录我们能够看出,这才是最优雅的添加关联操作,既没有多余的update语句,同时完成了数据库关联关系的建立。