Hibernate Collection乐观锁定

介绍

Hibernate提供了一种乐观的锁定机制 ,即使长时间通话也可以防止丢失更新 。 结合实体存储,跨越多个用户请求(扩展的持久性上下文或分离的实体),Hibernate可以保证应用程序级的可重复读取

脏检查机制检测实体状态更改并增加实体版本。 尽管始终会考虑基本属性更改,但是Hibernate集合在这方面更加微妙。

拥有与反向收藏

在关系数据库中,两个记录通过外键引用关联。 在这种关系中,引用记录是父记录,而引用行(外键侧)是子记录。 非空外键只能引用现有的父记录。

在面向对象的空间中,可以在两个方向上表示这种关联。 我们可以从孩子到父母有一对多的引用,而父母也可以有一对多的孩子集合。

因为双方都有可能控制数据库外键状态,所以我们必须确保只有一方是此关联的所有者。 仅拥有方状态更改会传播到数据库。 非持有端历来称为侧。

接下来,我将描述对该关联进行建模的最常用方法。

单向父项拥有子项关联映射

只有父方具有@OneToMany非逆子级集合。 子实体完全不引用父实体。

@Entity(name = "post")
public class Post {
	...
	@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
	private List<Comment> comments = new ArrayList<Comment>();
	...
}

单向父-子-子-子组件关联映射映射

子端不一定总是必须是实体,我们可以将其建模为组件类型 。 一个Embeddable对象(组件类型)可以同时包含基本类型和关联映射,但是它永远不能包含@Id。 可嵌入对象及其拥有的实体将被持久保存/删除。

父级具有@ElementCollection子级关联。 子实体只能通过不可查询的特定 Hibernate的@Parent注释引用父实体。

@Entity(name = "post")
public class Post {
	...
	@ElementCollection
	@JoinTable(name = "post_comments", joinColumns = @JoinColumn(name = "post_id"))
	@OrderColumn(name = "comment_index")
	private List<Comment> comments = new ArrayList<Comment>();
	...

	public void addComment(Comment comment) {
		comment.setPost(this);
		comments.add(comment);
	}
}	

@Embeddable
public class Comment {
	...
	@Parent
	private Post post;
	...
}

双向父子侧子关联映射

父级是拥有方,因此它有一个@OneToMany非逆(没有mappedBy指令)子级集合。 子实体通过@ManyToOne关联引用父实体,该关联既不可插入也不可更新:

@Entity(name = "post")
public class Post {
	...
	@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
	private List<Comment> comments = new ArrayList<Comment>();
	...

	public void addComment(Comment comment) {
		comment.setPost(this);
		comments.add(comment);
	}
}	

@Entity(name = "comment")
public class Comment {
	...
	@ManyToOne
	@JoinColumn(name = "post_id", insertable = false, updatable = false)
	private Post post;
	...
}

双向儿童拥有侧-父母关联映射

子实体通过引用父实体@ManyToOne协会和家长有一个的mappedBy @OneToMany孩子集合。 父侧是反侧,因此仅@ManyToOne状态更改会传播到数据库。

即使只有一个拥有的一方,通过使用add / removeChild()方法使双方保持同步始终是一个好习惯。

@Entity(name = "post")
public class Post {
	...
	@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "post")
	private List<Comment> comments = new ArrayList<Comment>();
	...

	public void addComment(Comment comment) {
		comment.setPost(this);
		comments.add(comment);
	}
}	

@Entity(name = "comment")
public class Comment {
	...
	@ManyToOne
	private Post post;	
	...
}

单向儿童拥有侧父母关系映射

子实体通过@ManyToOne关联引用父代。 父级没有@OneToMany子级集合,因此子级实体成为拥有方。 此关联映射类似于关系数据外键链接。

@Entity(name = "comment")
public class Comment {
    ...
    @ManyToOne
    private Post post;	
    ...
}

集合版本控制

JPA 2.1规范的3.4.2部分将乐观锁定定义为:

将对象写入数据库时​​,持久性提供程序运行时会更新version属性。 所有非关系字段和适当的关系以及实体所拥有的所有关系都包含在版本检查中[35]。

[35]这包括在联接表中维护的拥有的关系

注意:只有所有者方的子级集合可以更新父版本。

测试时间

让我们测试一下父子关联类型如何影响父版本。 由于我们对子级集合的脏检查感兴趣,因此将跳过单向的子级拥有方-父级关联,因为在这种情况下,父级不包含子级集合。

测试用例

以下测试用例将用于所有集合类型用例:

protected void simulateConcurrentTransactions(final boolean shouldIncrementParentVersion) {
	final ExecutorService executorService = Executors.newSingleThreadExecutor();

	doInTransaction(new TransactionCallable<Void>() {
		@Override
		public Void execute(Session session) {
			try {
				P post = postClass.newInstance();
				post.setId(1L);
				post.setName("Hibernate training");
				session.persist(post);
				return null;
			} catch (Exception e) {
				throw new IllegalArgumentException(e);
			}
		}
	});

	doInTransaction(new TransactionCallable<Void>() {
		@Override
		public Void execute(final Session session) {
			final P post = (P) session.get(postClass, 1L);
			try {
				executorService.submit(new Callable<Void>() {
					@Override
					public Void call() throws Exception {
						return doInTransaction(new TransactionCallable<Void>() {
							@Override
							public Void execute(Session _session) {
								try {
									P otherThreadPost = (P) _session.get(postClass, 1L);
									int loadTimeVersion = otherThreadPost.getVersion();
									assertNotSame(post, otherThreadPost);
									assertEquals(0L, otherThreadPost.getVersion());
									C comment = commentClass.newInstance();
									comment.setReview("Good post!");
									otherThreadPost.addComment(comment);
									_session.flush();
									if (shouldIncrementParentVersion) {
										assertEquals(otherThreadPost.getVersion(), loadTimeVersion + 1);
									} else {
										assertEquals(otherThreadPost.getVersion(), loadTimeVersion);
									}
									return null;
								} catch (Exception e) {
									throw new IllegalArgumentException(e);
								}
							}
						});
					}
				}).get();
			} catch (Exception e) {
				throw new IllegalArgumentException(e);
			}
			post.setName("Hibernate Master Class");
			session.flush();
			return null;
		}
	});
}

单向父母所有子女的关联测试

#create tables
Query:{[create table comment (id bigint generated by default as identity (start with 1), review varchar(255), primary key (id))][]} 
Query:{[create table post (id bigint not null, name varchar(255), version integer not null, primary key (id))][]} 
Query:{[create table post_comment (post_id bigint not null, comments_id bigint not null, comment_index integer not null, primary key (post_id, comment_index))][]} 
Query:{[alter table post_comment add constraint UK_se9l149iyyao6va95afioxsrl  unique (comments_id)][]} 
Query:{[alter table post_comment add constraint FK_se9l149iyyao6va95afioxsrl foreign key (comments_id) references comment][]} 
Query:{[alter table post_comment add constraint FK_6o1igdm04v78cwqre59or1yj1 foreign key (post_id) references post][]} 

#insert post in primary transaction
Query:{[insert into post (name, version, id) values (?, ?, ?)][Hibernate training,0,1]} 

#select post in secondary transaction
Query:{[select entityopti0_.id as id1_1_0_, entityopti0_.name as name2_1_0_, entityopti0_.version as version3_1_0_ from post entityopti0_ where entityopti0_.id=?][1]} 

#insert comment in secondary transaction
#optimistic locking post version update in secondary transaction
Query:{[insert into comment (id, review) values (default, ?)][Good post!]} 
Query:{[update post set name=?, version=? where id=? and version=?][Hibernate training,1,1,0]} 
Query:{[insert into post_comment (post_id, comment_index, comments_id) values (?, ?, ?)][1,0,1]} 

#optimistic locking exception in primary transaction
Query:{[update post set name=?, version=? where id=? and version=?][Hibernate Master Class,1,1,0]}
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.vladmihalcea.hibernate.masterclass.laboratory.concurrency.EntityOptimisticLockingOnUnidirectionalCollectionTest$Post#1]

单向父子侧子组件关联测试

#create tables
Query:{[create table post (id bigint not null, name varchar(255), version integer not null, primary key (id))][]} 
Query:{[create table post_comments (post_id bigint not null, review varchar(255), comment_index integer not null, primary key (post_id, comment_index))][]} 
Query:{[alter table post_comments add constraint FK_gh9apqeduab8cs0ohcq1dgukp foreign key (post_id) references post][]} 

#insert post in primary transaction
Query:{[insert into post (name, version, id) values (?, ?, ?)][Hibernate training,0,1]} 

#select post in secondary transaction
Query:{[select entityopti0_.id as id1_0_0_, entityopti0_.name as name2_0_0_, entityopti0_.version as version3_0_0_ from post entityopti0_ where entityopti0_.id=?][1]} 
Query:{[select comments0_.post_id as post_id1_0_0_, comments0_.review as review2_1_0_, comments0_.comment_index as comment_3_0_ from post_comments comments0_ where comments0_.post_id=?][1]} 

#insert comment in secondary transaction
#optimistic locking post version update in secondary transaction
Query:{[update post set name=?, version=? where id=? and version=?][Hibernate training,1,1,0]} 
Query:{[insert into post_comments (post_id, comment_index, review) values (?, ?, ?)][1,0,Good post!]} 

#optimistic locking exception in primary transaction
Query:{[update post set name=?, version=? where id=? and version=?][Hibernate Master Class,1,1,0]} 
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.vladmihalcea.hibernate.masterclass.laboratory.concurrency.EntityOptimisticLockingOnComponentCollectionTest$Post#1]

双向父母拥有-子-孩子关联测试

#create tables
Query:{[create table comment (id bigint generated by default as identity (start with 1), review varchar(255), post_id bigint, primary key (id))][]} 
Query:{[create table post (id bigint not null, name varchar(255), version integer not null, primary key (id))][]} 
Query:{[create table post_comment (post_id bigint not null, comments_id bigint not null)][]} 
Query:{[alter table post_comment add constraint UK_se9l149iyyao6va95afioxsrl  unique (comments_id)][]} 
Query:{[alter table comment add constraint FK_f1sl0xkd2lucs7bve3ktt3tu5 foreign key (post_id) references post][]} 
Query:{[alter table post_comment add constraint FK_se9l149iyyao6va95afioxsrl foreign key (comments_id) references comment][]} 
Query:{[alter table post_comment add constraint FK_6o1igdm04v78cwqre59or1yj1 foreign key (post_id) references post][]} 

#insert post in primary transaction
Query:{[insert into post (name, version, id) values (?, ?, ?)][Hibernate training,0,1]} 

#select post in secondary transaction
Query:{[select entityopti0_.id as id1_1_0_, entityopti0_.name as name2_1_0_, entityopti0_.version as version3_1_0_ from post entityopti0_ where entityopti0_.id=?][1]} 
Query:{[select comments0_.post_id as post_id1_1_0_, comments0_.comments_id as comments2_2_0_, entityopti1_.id as id1_0_1_, entityopti1_.post_id as post_id3_0_1_, entityopti1_.review as review2_0_1_, entityopti2_.id as id1_1_2_, entityopti2_.name as name2_1_2_, entityopti2_.version as version3_1_2_ from post_comment comments0_ inner join comment entityopti1_ on comments0_.comments_id=entityopti1_.id left outer join post entityopti2_ on entityopti1_.post_id=entityopti2_.id where comments0_.post_id=?][1]} 

#insert comment in secondary transaction
#optimistic locking post version update in secondary transaction
Query:{[insert into comment (id, review) values (default, ?)][Good post!]} 
Query:{[update post set name=?, version=? where id=? and version=?][Hibernate training,1,1,0]} 
Query:{[insert into post_comment (post_id, comments_id) values (?, ?)][1,1]} 

#optimistic locking exception in primary transaction
Query:{[update post set name=?, version=? where id=? and version=?][Hibernate Master Class,1,1,0]} 
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.vladmihalcea.hibernate.masterclass.laboratory.concurrency.EntityOptimisticLockingOnBidirectionalParentOwningCollectionTest$Post#1]

双向儿童拥有侧-父母关联测试

#create tables
Query:{[create table comment (id bigint generated by default as identity (start with 1), review varchar(255), post_id bigint, primary key (id))][]} 
Query:{[create table post (id bigint not null, name varchar(255), version integer not null, primary key (id))][]} 
Query:{[alter table comment add constraint FK_f1sl0xkd2lucs7bve3ktt3tu5 foreign key (post_id) references post][]} 

#insert post in primary transaction
Query:{[insert into post (name, version, id) values (?, ?, ?)][Hibernate training,0,1]} 

#select post in secondary transaction
Query:{[select entityopti0_.id as id1_1_0_, entityopti0_.name as name2_1_0_, entityopti0_.version as version3_1_0_ from post entityopti0_ where entityopti0_.id=?][1]} 

#insert comment in secondary transaction
#post version is not incremented in secondary transaction
Query:{[insert into comment (id, post_id, review) values (default, ?, ?)][1,Good post!]} 
Query:{[select count(id) from comment where post_id =?][1]} 

#update works in primary transaction
Query:{[update post set name=?, version=? where id=? and version=?][Hibernate Master Class,1,1,0]}

否决默认集合版本控制

如果默认的拥有方集合版本控制不适合您的用例,则始终可以使用Hibernate @OptimisticLock注释来取代它。

让我们废除双向parent-owning-side-child关联的默认父版本更新机制:

@Entity(name = "post")
public class Post {
	...
	@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
	@OptimisticLock(excluded = true)
	private List<Comment> comments = new ArrayList<Comment>();
	...

	public void addComment(Comment comment) {
		comment.setPost(this);
		comments.add(comment);
	}
}	

@Entity(name = "comment")
public class Comment {
	...
	@ManyToOne
	@JoinColumn(name = "post_id", insertable = false, updatable = false)
	private Post post;
	...
}

这次,子级集合更改不会触发父版本更新:

#create tables
Query:{[create table comment (id bigint generated by default as identity (start with 1), review varchar(255), post_id bigint, primary key (id))][]} 
Query:{[create table post (id bigint not null, name varchar(255), version integer not null, primary key (id))][]} 
Query:{[create table post_comment (post_id bigint not null, comments_id bigint not null)][]} 
Query:{[alter table post_comment add constraint UK_se9l149iyyao6va95afioxsrl  unique (comments_id)][]} 
Query:{[alter table comment add constraint FK_f1sl0xkd2lucs7bve3ktt3tu5 foreign key (post_id) references post][]} 
Query:{[alter table post_comment add constraint FK_se9l149iyyao6va95afioxsrl foreign key (comments_id) references comment][]} 
Query:{[alter table post_comment add constraint FK_6o1igdm04v78cwqre59or1yj1 foreign key (post_id) references post][]} 

#insert post in primary transaction
Query:{[insert into post (name, version, id) values (?, ?, ?)][Hibernate training,0,1]} 

#select post in secondary transaction
Query:{[select entityopti0_.id as id1_1_0_, entityopti0_.name as name2_1_0_, entityopti0_.version as version3_1_0_ from post entityopti0_ where entityopti0_.id=?][1]} 
Query:{[select comments0_.post_id as post_id1_1_0_, comments0_.comments_id as comments2_2_0_, entityopti1_.id as id1_0_1_, entityopti1_.post_id as post_id3_0_1_, entityopti1_.review as review2_0_1_, entityopti2_.id as id1_1_2_, entityopti2_.name as name2_1_2_, entityopti2_.version as version3_1_2_ from post_comment comments0_ inner join comment entityopti1_ on comments0_.comments_id=entityopti1_.id left outer join post entityopti2_ on entityopti1_.post_id=entityopti2_.id where comments0_.post_id=?][1]} 

#insert comment in secondary transaction
Query:{[insert into comment (id, review) values (default, ?)][Good post!]} 
Query:{[insert into post_comment (post_id, comments_id) values (?, ?)][1,1]} 

#update works in primary transaction
Query:{[update post set name=?, version=? where id=? and version=?][Hibernate Master Class,1,1,0]}

结论

了解各种建模结构如何影响并发模式非常重要。 递增父版本号时,将考虑拥有方集合的更改,您始终可以使用@OptimisticLock批注来绕过它。

翻译自: https://www.javacodegeeks.com/2014/11/hibernate-collections-optimistic-locking.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值