电子商务网站建设与维护李建忠,杭州网络推广,政务公开网站项目建设书,凡科建站手机网站建设jpa和hibernate介绍 JPA将实体状态转换转换为数据库DML语句。 由于对实体图进行操作很常见#xff0c;因此JPA允许我们将实体状态更改从父级传播到子级 。 通过CascadeType映射配置此行为。 JPA与Hibernate级联类型 Hibernate支持所有JPA级联类型和一些其他旧式级联样式。 下… jpa和hibernate 介绍 JPA将实体状态转换转换为数据库DML语句。 由于对实体图进行操作很常见因此JPA允许我们将实体状态更改从父级传播到子级 。 通过CascadeType映射配置此行为。 JPA与Hibernate级联类型 Hibernate支持所有JPA级联类型和一些其他旧式级联样式。 下表绘制了JPA级联类型与其等效的Hibernate本机API之间的关联 JPA EntityManager操作 JPA CascadeType Hibernate本机会话操作 Hibernate原生CascadeType 事件监听器 分离实体 分离 逐出实体 分离或 EVICT 默认驱逐事件侦听器 合并实体 合并 合并实体 合并 默认合并事件监听器 坚持实体 坚持 坚持实体 坚持 默认的持久事件监听器 刷新实体 刷新 刷新实体 刷新 默认刷新事件监听器 删除实体 去掉 删除实体 删除或删除 默认删除事件监听器 saveOrUpdate实体 SAVE_UPDATE 默认的保存或更新事件监听器 复制实体复制模式 复制 默认复制事件监听器 锁实体lockModeType buildLockRequest实体lockOptions 锁 默认锁定事件监听器 以上所有EntityManager方法 所有 以上所有的Hibernate Session方法 所有 从该表可以得出以下结论 在JPA EntityManager或Hibernate Session上调用persist merge或refresh没有什么区别。 JPA的remove和detach调用被委托给Hibernate Delete和逐出本机操作。 只有Hibernate支持复制和saveOrUpdate 。 尽管复制对于某些非常特定的情况很有用当确切的实体状态需要在两个不同的数据源之间进行镜像时但持久 合并合并始终是比本机saveOrUpdate操作更好的替代方法。将持久对象用于TRANSIENT实体并为已分离实体进行合并。saveOrUpdate的缺点将分离的实体快照传递给已经管理该实体的会话时 导致了合并操作的前身现已不存在的saveOrUpdateCopy操作。 JPA锁定方法与Hibernate锁定请求方法具有相同的行为。 JPA CascadeType.ALL不仅适用于EntityManager状态更改操作而且还适用于所有Hibernate CascadeTypes 。因此如果将关联与CascadeType.ALL映射您仍然可以级联Hibernate特定事件。 例如即使JPA没有定义LOCK CascadeType 您也可以级联JPA锁定操作尽管它的行为就像是重新附加而不是实际的锁定请求传播。 级联最佳做法 级联仅对父级 - 子级关联有意义 父级实体状态转换级联到其子级实体。 从孩子级联到父级不是很有用通常这是映射代码的味道。 接下来我将采取分析所有JPA 家长的级联行为- 子关联。 一对一 最常见的一对一双向关联如下所示 Entity
public class Post {IdGeneratedValue(strategy GenerationType.AUTO)private Long id;private String name;OneToOne(mappedBy post,cascade CascadeType.ALL, orphanRemoval true)private PostDetails details;public Long getId() {return id;}public PostDetails getDetails() {return details;}public String getName() {return name;}public void setName(String name) {this.name name;}public void addDetails(PostDetails details) {this.details details;details.setPost(this);}public void removeDetails() {if (details ! null) {details.setPost(null);}this.details null;}
}Entity
public class PostDetails {IdGeneratedValue(strategy GenerationType.AUTO)private Long id;Column(name created_on)Temporal(TemporalType.TIMESTAMP)private Date createdOn new Date();private boolean visible;OneToOnePrimaryKeyJoinColumnprivate Post post;public Long getId() {return id;}public void setVisible(boolean visible) {this.visible visible;}public void setPost(Post post) {this.post post;}
} Post实体扮演Parent角色而PostDetails是Child 。 双向关联应始终在两侧进行更新因此父级侧应包含addChild和removeChild组合。 这些方法确保我们始终同步关联的双方以避免对象或关系数据损坏问题。 在这种特殊情况下 CascadeType.ALL和孤立删除是有意义的因为PostDetails生命周期绑定到其Post Parent实体的生命周期。 进行一对一的持久化操作 CascadeType.PERSIST与CascadeType.ALL配置一起提供因此我们仅需持久化Post实体并且关联的PostDetails实体也将持久化 Post post new Post();
post.setName(Hibernate Master Class);PostDetails details new PostDetails();post.addDetails(details);session.persist(post); 生成以下输出 INSERT INTO post(id, NAME)
VALUES (DEFAULT, Hibernate Master Class)insert into PostDetails (id, created_on, visible)
values (default, 2015-03-03 10:17:19.14, false)级联一对一合并操作 CascadeType.MERGE继承自CascadeType.ALL设置因此我们只需要合并Post实体并且关联的PostDetails也将合并 Post post newPost();
post.setName(Hibernate Master Class Training Material);
post.getDetails().setVisible(true);doInTransaction(session - {session.merge(post);
}); 合并操作生成以下输出 SELECT onetooneca0_.id AS id1_3_1_,onetooneca0_.NAME AS name2_3_1_,onetooneca1_.id AS id1_4_0_,onetooneca1_.created_on AS created_2_4_0_,onetooneca1_.visible AS visible3_4_0_
FROM post onetooneca0_
LEFT OUTER JOIN postdetails onetooneca1_ ON onetooneca0_.id onetooneca1_.id
WHERE onetooneca0_.id 1UPDATE postdetails SET created_on 2015-03-03 10:20:53.874, visible true
WHERE id 1UPDATE post SET NAME Hibernate Master Class Training Material
WHERE id 1级联一对一删除操作 CascadeType.REMOVE也继承自CascadeType.ALL配置因此Post实体的删除也会触发PostDetails实体的删除 Post post newPost();doInTransaction(session - {session.delete(post);
}); 生成以下输出 delete from PostDetails where id 1
delete from Post where id 1一对一删除孤立级联操作 如果一个孩子实体从母公司分离儿童外键设置为NULL。 如果我们也要删除“ 子行”则必须使用孤立删除支持。 doInTransaction(session - {Post post (Post) session.get(Post.class, 1L);post.removeDetails();
}); 除去孤儿将生成以下输出 SELECT onetooneca0_.id AS id1_3_0_,onetooneca0_.NAME AS name2_3_0_,onetooneca1_.id AS id1_4_1_,onetooneca1_.created_on AS created_2_4_1_,onetooneca1_.visible AS visible3_4_1_
FROM post onetooneca0_
LEFT OUTER JOIN postdetails onetooneca1_ON onetooneca0_.id onetooneca1_.id
WHERE onetooneca0_.id 1delete from PostDetails where id 1单向一对一关联 大多数情况下 父实体是反方如的mappedBy 儿童 controling通过它的外键关联。 但是级联不限于双向关联我们还可以将其用于单向关系 Entity
public class Commit {IdGeneratedValue(strategy GenerationType.AUTO)private Long id;private String comment;OneToOne(cascade CascadeType.ALL)JoinTable(name Branch_Merge_Commit,joinColumns JoinColumn(name commit_id, referencedColumnName id),inverseJoinColumns JoinColumn(name branch_merge_id, referencedColumnName id))private BranchMerge branchMerge;public Commit() {}public Commit(String comment) {this.comment comment;}public Long getId() {return id;}public void addBranchMerge(String fromBranch, String toBranch) {this.branchMerge new BranchMerge(fromBranch, toBranch);}public void removeBranchMerge() {this.branchMerge null;}
}Entity
public class BranchMerge {IdGeneratedValue(strategy GenerationType.AUTO)private Long id;private String fromBranch;private String toBranch;public BranchMerge() {}public BranchMerge(String fromBranch, String toBranch) {this.fromBranch fromBranch;this.toBranch toBranch;}public Long getId() {return id;}
} 层叠在于传播父实体状态过渡到一个或多个儿童的实体它可用于单向和双向关联。 一对多 最常见的父 - 子关联由一到多和多到一的关系其中级联是只对一个一对多侧有用 Entity
public class Post {IdGeneratedValue(strategy GenerationType.AUTO)private Long id;private String name;OneToMany(cascade CascadeType.ALL, mappedBy post, orphanRemoval true)private ListComment comments new ArrayList();public void setName(String name) {this.name name;}public ListComment getComments() {return comments;}public void addComment(Comment comment) {comments.add(comment);comment.setPost(this);}public void removeComment(Comment comment) {comment.setPost(null);this.comments.remove(comment);}
}Entity
public class Comment {IdGeneratedValue(strategy GenerationType.AUTO)private Long id;ManyToOneprivate Post post;private String review;public void setPost(Post post) {this.post post;}public String getReview() {return review;}public void setReview(String review) {this.review review;}
} 像一对一示例一样 CascadeType.ALL和孤立删除是合适的因为Comment生命周期绑定到其Post Parent实体的生命周期。 级联一对多持久化操作 我们只需保留Post实体所有相关的Comment实体也将保留 Post post new Post();
post.setName(Hibernate Master Class);Comment comment1 new Comment();
comment1.setReview(Good post!);
Comment comment2 new Comment();
comment2.setReview(Nice post!);post.addComment(comment1);
post.addComment(comment2);session.persist(post); 持久操作将生成以下输出 insert into Post (id, name)
values (default, Hibernate Master Class)insert into Comment (id, post_id, review)
values (default, 1, Good post!)insert into Comment (id, post_id, review)
values (default, 1, Nice post!)级联一对多合并操作 合并Post实体也将合并所有Comment实体 Post post newPost();
post.setName(Hibernate Master Class Training Material);post.getComments().stream().filter(comment - comment.getReview().toLowerCase().contains(nice)).findAny().ifPresent(comment - comment.setReview(Keep up the good work!)
);doInTransaction(session - {session.merge(post);
}); 生成以下输出 SELECT onetomanyc0_.id AS id1_1_1_,onetomanyc0_.NAME AS name2_1_1_,comments1_.post_id AS post_id3_1_3_,comments1_.id AS id1_0_3_,comments1_.id AS id1_0_0_,comments1_.post_id AS post_id3_0_0_,comments1_.review AS review2_0_0_
FROM post onetomanyc0_
LEFT OUTER JOIN comment comments1_ON onetomanyc0_.id comments1_.post_id
WHERE onetomanyc0_.id 1update Post set name Hibernate Master Class Training Material
where id 1update Comment set post_id 1, reviewKeep up the good work!
where id 2级联一对多删除操作 删除Post实体后关联的Comment实体也将被删除 Post post newPost();doInTransaction(session - {session.delete(post);
}); 生成以下输出 delete from Comment where id 1
delete from Comment where id 2
delete from Post where id 1一对多删除孤立级联操作 移除孤儿使我们可以在其父级不再引用子级实体时将其删除 newPost();doInTransaction(session - {Post post (Post) session.createQuery(select p from Post p join fetch p.comments where p.id :id).setParameter(id, 1L).uniqueResult();post.removeComment(post.getComments().get(0));
}); 正如我们在以下输出中看到的评论已删除 SELECT onetomanyc0_.id AS id1_1_0_,comments1_.id AS id1_0_1_,onetomanyc0_.NAME AS name2_1_0_,comments1_.post_id AS post_id3_0_1_,comments1_.review AS review2_0_1_,comments1_.post_id AS post_id3_1_0__,comments1_.id AS id1_0_0__
FROM post onetomanyc0_
INNER JOIN comment comments1_ON onetomanyc0_.id comments1_.post_id
WHERE onetomanyc0_.id 1delete from Comment where id 1多对多 多对多关系非常棘手因为此关联的每一方都扮演“ 父母”和“ 孩子”角色。 尽管如此我们仍可以从一侧传播我们想要传播实体状态变化的位置。 我们不应该默认使用CascadeType.ALL 因为CascadeTpe.REMOVE最终可能会删除比我们期望的更多的东西您很快就会发现 Entity
public class Author {IdGeneratedValue(strategyGenerationType.AUTO)private Long id;Column(name full_name, nullable false)private String fullName;ManyToMany(mappedBy authors, cascade {CascadeType.PERSIST, CascadeType.MERGE})private ListBook books new ArrayList();private Author() {}public Author(String fullName) {this.fullName fullName;}public Long getId() {return id;}public void addBook(Book book) {books.add(book);book.authors.add(this);}public void removeBook(Book book) {books.remove(book);book.authors.remove(this);}public void remove() {for(Book book : new ArrayList(books)) {removeBook(book);}}
}Entity
public class Book {IdGeneratedValue(strategyGenerationType.AUTO)private Long id;Column(name title, nullable false)private String title;ManyToMany(cascade {CascadeType.PERSIST, CascadeType.MERGE})JoinTable(name Book_Author,joinColumns {JoinColumn(name book_id, referencedColumnName id)},inverseJoinColumns {JoinColumn(name author_id, referencedColumnName id)})private ListAuthor authors new ArrayList();private Book() {}public Book(String title) {this.title title;}
}级联多对多持久操作 坚持作者实体也将保留书籍 Author _John_Smith new Author(John Smith);
Author _Michelle_Diangello new Author(Michelle Diangello);
Author _Mark_Armstrong new Author(Mark Armstrong);Book _Day_Dreaming new Book(Day Dreaming);
Book _Day_Dreaming_2nd new Book(Day Dreaming, Second Edition);_John_Smith.addBook(_Day_Dreaming);
_Michelle_Diangello.addBook(_Day_Dreaming);_John_Smith.addBook(_Day_Dreaming_2nd);
_Michelle_Diangello.addBook(_Day_Dreaming_2nd);
_Mark_Armstrong.addBook(_Day_Dreaming_2nd);session.persist(_John_Smith);
session.persist(_Michelle_Diangello);
session.persist(_Mark_Armstrong); Book和Book_Author行与Authors一起插入 insert into Author (id, full_name)
values (default, John Smith)insert into Book (id, title)
values (default, Day Dreaming)insert into Author (id, full_name)
values (default, Michelle Diangello)insert into Book (id, title)
values (default, Day Dreaming, Second Edition)insert into Author (id, full_name)
values (default, Mark Armstrong)insert into Book_Author (book_id, author_id) values (1, 1)
insert into Book_Author (book_id, author_id) values (1, 2)
insert into Book_Author (book_id, author_id) values (2, 1)
insert into Book_Author (book_id, author_id) values (2, 2)
insert into Book_Author (book_id, author_id) values (3, 1)解除多对多关联的一侧 要删除Author 我们需要取消关联属于可移动实体的所有Book_Author关系 doInTransaction(session - {Author _Mark_Armstrong getByName(session, Mark Armstrong);_Mark_Armstrong.remove();session.delete(_Mark_Armstrong);
}); 该用例生成以下输出 SELECT manytomany0_.id AS id1_0_0_,manytomany2_.id AS id1_1_1_,manytomany0_.full_name AS full_nam2_0_0_,manytomany2_.title AS title2_1_1_,books1_.author_id AS author_i2_0_0__,books1_.book_id AS book_id1_2_0__
FROM author manytomany0_
INNER JOIN book_author books1_ON manytomany0_.id books1_.author_id
INNER JOIN book manytomany2_ON books1_.book_id manytomany2_.id
WHERE manytomany0_.full_name Mark ArmstrongSELECT books0_.author_id AS author_i2_0_0_,books0_.book_id AS book_id1_2_0_,manytomany1_.id AS id1_1_1_,manytomany1_.title AS title2_1_1_
FROM book_author books0_
INNER JOIN book manytomany1_ON books0_.book_id manytomany1_.id
WHERE books0_.author_id 2delete from Book_Author where book_id 2insert into Book_Author (book_id, author_id) values (2, 1)
insert into Book_Author (book_id, author_id) values (2, 2)delete from Author where id 3 多对多关联会生成太多冗余SQL语句并且经常很难调整它们。 接下来我将演示多对多CascadeType.REMOVE隐藏的危险。 多对多CascadeType.REMOVE陷阱 多对多CascadeType.ALL是另一个代码异味我在查看代码时经常碰到。 所述CascadeType.REMOVE使用CascadeType.ALL时自动继承但实体去除不仅应用到链接表但对关联的另一侧为好。 让我们将Author实体书籍多对多关联更改为使用CascadeType.ALL代替 ManyToMany(mappedBy authors, cascade CascadeType.ALL)
private ListBook books new ArrayList(); 删除一位作者时 doInTransaction(session - {Author _Mark_Armstrong getByName(session, Mark Armstrong);session.delete(_Mark_Armstrong);Author _John_Smith getByName(session, John Smith);assertEquals(1, _John_Smith.books.size());
}); 属于已删除作者的所有图书都将被删除即使我们仍与已删除图书相关联的其他作者也是如此 SELECT manytomany0_.id AS id1_0_,manytomany0_.full_name AS full_nam2_0_
FROM author manytomany0_
WHERE manytomany0_.full_name Mark Armstrong SELECT books0_.author_id AS author_i2_0_0_,books0_.book_id AS book_id1_2_0_,manytomany1_.id AS id1_1_1_,manytomany1_.title AS title2_1_1_
FROM book_author books0_
INNER JOIN book manytomany1_ ON books0_.book_id manytomany1_.id
WHERE books0_.author_id 3 delete from Book_Author where book_id2
delete from Book where id2
delete from Author where id3 通常此行为与业务逻辑期望不符仅在首次删除实体时才发现。 如果我们也将CascadeType.ALL设置为Book实体则可以进一步推动该问题 ManyToMany(cascade CascadeType.ALL)
JoinTable(name Book_Author,joinColumns {JoinColumn(name book_id, referencedColumnName id)},inverseJoinColumns {JoinColumn(name author_id, referencedColumnName id)}
) 这次不仅书籍被删除而且作者也被删除 doInTransaction(session - {Author _Mark_Armstrong getByName(session, Mark Armstrong);session.delete(_Mark_Armstrong);Author _John_Smith getByName(session, John Smith);assertNull(_John_Smith);
}); 作者的删除触发所有相关书籍的删除这进一步触发所有相关的作者的删除。 这是一个非常危险的操作会导致大规模实体删除这很少是预期的行为。 SELECT manytomany0_.id AS id1_0_,manytomany0_.full_name AS full_nam2_0_
FROM author manytomany0_
WHERE manytomany0_.full_name Mark Armstrong SELECT books0_.author_id AS author_i2_0_0_,books0_.book_id AS book_id1_2_0_,manytomany1_.id AS id1_1_1_,manytomany1_.title AS title2_1_1_
FROM book_author books0_
INNER JOIN book manytomany1_ON books0_.book_id manytomany1_.id
WHERE books0_.author_id 3 SELECT authors0_.book_id AS book_id1_1_0_,authors0_.author_id AS author_i2_2_0_,manytomany1_.id AS id1_0_1_,manytomany1_.full_name AS full_nam2_0_1_
FROM book_author authors0_
INNER JOIN author manytomany1_ON authors0_.author_id manytomany1_.id
WHERE authors0_.book_id 2 SELECT books0_.author_id AS author_i2_0_0_,books0_.book_id AS book_id1_2_0_,manytomany1_.id AS id1_1_1_,manytomany1_.title AS title2_1_1_
FROM book_author books0_
INNER JOIN book manytomany1_ON books0_.book_id manytomany1_.id
WHERE books0_.author_id 1 SELECT authors0_.book_id AS book_id1_1_0_,authors0_.author_id AS author_i2_2_0_,manytomany1_.id AS id1_0_1_,manytomany1_.full_name AS full_nam2_0_1_
FROM book_author authors0_
INNER JOIN author manytomany1_ON authors0_.author_id manytomany1_.id
WHERE authors0_.book_id 1 SELECT books0_.author_id AS author_i2_0_0_,books0_.book_id AS book_id1_2_0_,manytomany1_.id AS id1_1_1_,manytomany1_.title AS title2_1_1_
FROM book_author books0_
INNER JOIN book manytomany1_ON books0_.book_id manytomany1_.id
WHERE books0_.author_id 2 delete from Book_Author where book_id2
delete from Book_Author where book_id1
delete from Author where id2
delete from Book where id1
delete from Author where id1
delete from Book where id2
delete from Author where id3 这种用例在很多方面都是错误的。 大量不必要的SELECT语句最终我们最终删除了所有作者及其所有书籍。 这就是为什么当您在多对多关联中发现CascadeType.ALL时它应该引起您的注意。 当涉及到Hibernate映射时您应该始终追求简单性。 Hibernate文档也证实了这一假设 真正的多对多关联的实际测试案例很少见。 大多数时候您需要存储在“链接表”中的其他信息。 在这种情况下最好将两个一对多关联用于中间链接类。 实际上大多数关联是一对多和多对一的。 因此在使用任何其他关联样式时您应谨慎进行。 结论 级联是一种方便的ORM功能但并非没有问题。 您应该仅从父级实体级联到子级而不是相反。 您应该始终仅使用业务逻辑要求所要求的Casacde操作而不应将CascadeType.ALL转换为默认的Parent-Child关联实体状态传播配置。 代码可在GitHub上获得 。 翻译自: https://www.javacodegeeks.com/2015/03/a-beginners-guide-to-jpa-and-hibernate-cascade-types.htmljpa和hibernate