Hibernate 多对多删除关系
Hibernate many-to-many remove relation
我有一个休眠多对多关系的问题:当我从我的集合中删除一个项目时,它并没有从我的数据库中删除。我知道有很多类似的问题,但我没有通过阅读来成功解决我的问题。
我已经为它写了一个JUnit测试用例。我的关联在建筑物和用户之间:
@Test
public void testBuildingManyToMany(){
//Create 2 buildings
Building building = createBuilding("b1");
Building building2 = createBuilding("b2");
//Create 1 user
User user = createUser("u1");
//Associate the 2 buildings to that user
user.getBuildings().add(building);
building.getUsers().add(user);
user.getBuildings().add(building2);
building2.getUsers().add(user);
userController.save(user);
user = userController.retrieve(user.getId());
Assert.assertEquals(2, user.getBuildings().size());//Test OK
//Test 1: remove 1 building from the list
user.getBuildings().remove(building);
building.getUsers().remove(user);
userController.save(user);
//Test 2: clear and add
//user.getBuildings().clear();
//user.getBuildings().add(building);
//userController.save(user);
//user = userController.retrieve(user.getId());
//Assert.assertEquals(1, user.getBuildings().size());
}
这是我得到的错误:
...
Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?)
Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?)
Hibernate: delete from building_useraccount where userid=? and buildingid=?
Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?)
4113 [main] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 23505, SQLState: 23505
4113 [main] ERROR org.hibernate.util.JDBCExceptionReporter - Unique index or primary key violation: "PRIMARY_KEY_23 ON PUBLIC.BUILDING_USERACCOUNT(BUILDINGID, USERID) VALUES ( /* key:0 */ 201, 201)"; SQL statement:
insert into building_useraccount (userid, buildingid) values (?, ?) [23505-176]
当我注释 "Test 1" 并取消注释 "Test 2" 行时,出现以下错误:
junit.framework.AssertionFailedError:
Expected :1
Actual :2
这是我的 hbm.xml 类:
<hibernate-mapping default-lazy="true">
<class name="my.model.pojo.Building" table="building">
<cache usage="read-write" />
<id name="id" column="id" type="java.lang.Long">
<generator class="sequence">
<param name="sequence">building_id_sequence</param>
</generator>
</id>
<property name="name" type="java.lang.String" column="name" not-null="true" />
...
<set name="users" cascade="none" lazy="true" inverse="true" table="building_useraccount">
<key column="buildingid" />
<many-to-many class="my.model.pojo.User" column="userid" />
</set>
</class>
</hibernate-mapping>
和
<hibernate-mapping default-lazy="true">
<class name="my.model.pojo.User" table="useraccount">
<cache usage="read-write" />
<id name="id" column="id" type="java.lang.Long">
<generator class="sequence">
<param name="sequence">useraccount_id_sequence</param>
</generator>
</id>
<property name="login" type="java.lang.String" column="login" not-null="true" unique="true" length="40" />
...
<set name="buildings" cascade="none" lazy="false" fetch="join" table="building_useraccount">
<key column="userid" />
<many-to-many class="my.model.pojo.Building" column="buildingid" />
</set>
</class>
</hibernate-mapping>
和类
public class User implements Serializable, Identifiable {
private static final long serialVersionUID = 1L;
private int hashCode;
private Long id;
private String login;
private Set<Building> buildings = new HashSet<Building>();
public boolean equals(Object value) {
if (value == this)
return true;
if (value == null || !(value instanceof User))
return false;
if (getId() != null && getId().equals(((User) value).getId()))
return true;
return super.equals(value);
}
public int hashCode() {
if (hashCode == 0) {
hashCode = (getId() == null) ? super.hashCode() : new HashCodeBuilder().append(getId()).toHashCode();
}
return hashCode;
}
/* Getter / Setter ... */
和
public class BuildingBase implements Serializable, Identifiable {
private static final long serialVersionUID = 1L;
private int hashCode;
private Long id;
private String name;
private Set<User> users = new HashSet<User>();
public boolean equals(Object value) {
if (value == this)
return true;
if (value == null || !(value instanceof Building))
return false;
if (getId() != null && getId().equals(((Building) value).getId()))
return true;
return super.equals(value);
}
public int hashCode() {
if (hashCode == 0) {
hashCode = (getId() == null) ? super.hashCode() : new HashCodeBuilder().append(getId()).toHashCode();
}
return hashCode;
}
/* Getter / Setter ... */
编辑: 添加 userController 实现,用于事务
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public User save(User user) throws ServiceException {
validate(user);//Validation stuffs
return userDAO.update(user);
}
userDAO:
public class UserDAOImpl extends HibernateDAOImpl<User> implements UserDAO {
}
和 HibernateDAOImpl:
public class HibernateDAOImpl<T> implements DAO<T> {
public T update(T entity) {
return executeAndCreateSessionIfNeeded(new HibernateAction<T>() {
@Override
public T execute(Session session) {
return (T) session.merge(entity);
}
});
}
protected <E> E executeAndCreateSessionIfNeeded(HibernateAction<E> action) {
Session session = null;
try {
session = sessionFactory.getCurrentSession();
return executeAction(action, session);
} finally {
if (session != null) {
session.close();
}
}
}
}
为什么 cascade="none"
?
您应该使用 cascade="detached,merge,refresh,persist"
(而不是删除!)来更新集合中的删除。
在用户定义的 buildings
关系上用 cascade='all'
替换 cascade='none'
应该可以解决问题。
由于您正在保存用户,为了同时更新数据库中的多对多,您需要级联用户对关系的更改。
CascadeType.REMOVE
对 many-to-many
关联没有意义,因为当在两侧设置时,它可能会触发父子之间的链删除并返回父子。如果您只在父端设置它,当删除的子项仍被其他一些父项引用时,您可能会遇到问题。
It does not usually make sense to enable cascade on a many-to-one or
many-to-many association. In fact the @ManyToOne and @ManyToMany don't
even offer a orphanRemoval attribute. Cascading is often useful for
one-to-one and one-to-many associations.
恐怕你正在做的事情对于休眠来说并不是一个好主意,即使它是你对一段关系所做的更常见的任务之一。实现你想要的方法是使用级联,但正如 Vlad Mihalcea 所说,这最终可能会删除关系的一端或另一端,而不仅仅是关系本身。
作为正确的回应,我会告诉你老师会怎么说......你们真的有n:m关系吗?你确定它本身没有实体吗? N:M 很难找到关系,通常意味着建模有误。即使情况并非如此并且您实际上有一个 n:m,它也应该保留在模型中,永远不要忘记您正在使用 ORM 将 link ACTUAL 模型 java 模型所以你实际上可以在 Java 中拥有一个实体,每一端都有 1:n 关系,并将其存储在关系 table.
中
此致!
更改级联 属性 没有解决我的问题。我最终决定通过为中间table创建一个对象来自己处理多对多关系,并自己管理它。它的代码有点多,但为我想要实现的目标提供了一致的行为。
我有一个休眠多对多关系的问题:当我从我的集合中删除一个项目时,它并没有从我的数据库中删除。我知道有很多类似的问题,但我没有通过阅读来成功解决我的问题。
我已经为它写了一个JUnit测试用例。我的关联在建筑物和用户之间:
@Test
public void testBuildingManyToMany(){
//Create 2 buildings
Building building = createBuilding("b1");
Building building2 = createBuilding("b2");
//Create 1 user
User user = createUser("u1");
//Associate the 2 buildings to that user
user.getBuildings().add(building);
building.getUsers().add(user);
user.getBuildings().add(building2);
building2.getUsers().add(user);
userController.save(user);
user = userController.retrieve(user.getId());
Assert.assertEquals(2, user.getBuildings().size());//Test OK
//Test 1: remove 1 building from the list
user.getBuildings().remove(building);
building.getUsers().remove(user);
userController.save(user);
//Test 2: clear and add
//user.getBuildings().clear();
//user.getBuildings().add(building);
//userController.save(user);
//user = userController.retrieve(user.getId());
//Assert.assertEquals(1, user.getBuildings().size());
}
这是我得到的错误:
...
Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?)
Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?)
Hibernate: delete from building_useraccount where userid=? and buildingid=?
Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?)
4113 [main] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 23505, SQLState: 23505
4113 [main] ERROR org.hibernate.util.JDBCExceptionReporter - Unique index or primary key violation: "PRIMARY_KEY_23 ON PUBLIC.BUILDING_USERACCOUNT(BUILDINGID, USERID) VALUES ( /* key:0 */ 201, 201)"; SQL statement:
insert into building_useraccount (userid, buildingid) values (?, ?) [23505-176]
当我注释 "Test 1" 并取消注释 "Test 2" 行时,出现以下错误:
junit.framework.AssertionFailedError:
Expected :1
Actual :2
这是我的 hbm.xml 类:
<hibernate-mapping default-lazy="true">
<class name="my.model.pojo.Building" table="building">
<cache usage="read-write" />
<id name="id" column="id" type="java.lang.Long">
<generator class="sequence">
<param name="sequence">building_id_sequence</param>
</generator>
</id>
<property name="name" type="java.lang.String" column="name" not-null="true" />
...
<set name="users" cascade="none" lazy="true" inverse="true" table="building_useraccount">
<key column="buildingid" />
<many-to-many class="my.model.pojo.User" column="userid" />
</set>
</class>
</hibernate-mapping>
和
<hibernate-mapping default-lazy="true">
<class name="my.model.pojo.User" table="useraccount">
<cache usage="read-write" />
<id name="id" column="id" type="java.lang.Long">
<generator class="sequence">
<param name="sequence">useraccount_id_sequence</param>
</generator>
</id>
<property name="login" type="java.lang.String" column="login" not-null="true" unique="true" length="40" />
...
<set name="buildings" cascade="none" lazy="false" fetch="join" table="building_useraccount">
<key column="userid" />
<many-to-many class="my.model.pojo.Building" column="buildingid" />
</set>
</class>
</hibernate-mapping>
和类
public class User implements Serializable, Identifiable {
private static final long serialVersionUID = 1L;
private int hashCode;
private Long id;
private String login;
private Set<Building> buildings = new HashSet<Building>();
public boolean equals(Object value) {
if (value == this)
return true;
if (value == null || !(value instanceof User))
return false;
if (getId() != null && getId().equals(((User) value).getId()))
return true;
return super.equals(value);
}
public int hashCode() {
if (hashCode == 0) {
hashCode = (getId() == null) ? super.hashCode() : new HashCodeBuilder().append(getId()).toHashCode();
}
return hashCode;
}
/* Getter / Setter ... */
和
public class BuildingBase implements Serializable, Identifiable {
private static final long serialVersionUID = 1L;
private int hashCode;
private Long id;
private String name;
private Set<User> users = new HashSet<User>();
public boolean equals(Object value) {
if (value == this)
return true;
if (value == null || !(value instanceof Building))
return false;
if (getId() != null && getId().equals(((Building) value).getId()))
return true;
return super.equals(value);
}
public int hashCode() {
if (hashCode == 0) {
hashCode = (getId() == null) ? super.hashCode() : new HashCodeBuilder().append(getId()).toHashCode();
}
return hashCode;
}
/* Getter / Setter ... */
编辑: 添加 userController 实现,用于事务
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public User save(User user) throws ServiceException {
validate(user);//Validation stuffs
return userDAO.update(user);
}
userDAO:
public class UserDAOImpl extends HibernateDAOImpl<User> implements UserDAO {
}
和 HibernateDAOImpl:
public class HibernateDAOImpl<T> implements DAO<T> {
public T update(T entity) {
return executeAndCreateSessionIfNeeded(new HibernateAction<T>() {
@Override
public T execute(Session session) {
return (T) session.merge(entity);
}
});
}
protected <E> E executeAndCreateSessionIfNeeded(HibernateAction<E> action) {
Session session = null;
try {
session = sessionFactory.getCurrentSession();
return executeAction(action, session);
} finally {
if (session != null) {
session.close();
}
}
}
}
为什么 cascade="none"
?
您应该使用 cascade="detached,merge,refresh,persist"
(而不是删除!)来更新集合中的删除。
在用户定义的 buildings
关系上用 cascade='all'
替换 cascade='none'
应该可以解决问题。
由于您正在保存用户,为了同时更新数据库中的多对多,您需要级联用户对关系的更改。
CascadeType.REMOVE
对 many-to-many
关联没有意义,因为当在两侧设置时,它可能会触发父子之间的链删除并返回父子。如果您只在父端设置它,当删除的子项仍被其他一些父项引用时,您可能会遇到问题。
It does not usually make sense to enable cascade on a many-to-one or many-to-many association. In fact the @ManyToOne and @ManyToMany don't even offer a orphanRemoval attribute. Cascading is often useful for one-to-one and one-to-many associations.
恐怕你正在做的事情对于休眠来说并不是一个好主意,即使它是你对一段关系所做的更常见的任务之一。实现你想要的方法是使用级联,但正如 Vlad Mihalcea 所说,这最终可能会删除关系的一端或另一端,而不仅仅是关系本身。
作为正确的回应,我会告诉你老师会怎么说......你们真的有n:m关系吗?你确定它本身没有实体吗? N:M 很难找到关系,通常意味着建模有误。即使情况并非如此并且您实际上有一个 n:m,它也应该保留在模型中,永远不要忘记您正在使用 ORM 将 link ACTUAL 模型 java 模型所以你实际上可以在 Java 中拥有一个实体,每一端都有 1:n 关系,并将其存储在关系 table.
中此致!
更改级联 属性 没有解决我的问题。我最终决定通过为中间table创建一个对象来自己处理多对多关系,并自己管理它。它的代码有点多,但为我想要实现的目标提供了一致的行为。