情境:
我在tomcat服务器上运行了一个JSF 2.1 webapp,hibernate实现了jpa接口和mysql数据库。
webapp的用户在数据库中注册为实体,并且有一组名为Rooms的数据,这些数据具有自己的实体,用户可以作为专有或外部管理员进行访问。为了在数据库中保存“外部管理”的概念,我为每个管理层提供了适当的实体;外部管理在用户实体上保存为OneToMany关系。
这里有Hibernate依赖项,因为它们被添加到项目的pom.xml中:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.5.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.5.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>4.3.5.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.0.Final</version>
</dependency>
问题: 由于某些奇怪的原因,将外键的代码添加到具有ManyToOne关系的外部管理实体中的受管理房间,会破坏Hibernate:对用户的外部管理属性列表进行任何更新,当存在该外键时,抛出RollbackException - “仅标记为回滚的事务”:
Caused by: it.katuiros.core.db.dao.DataAccessException: Data Access error while doing final commit.
at it.katuiros.core.db.dao.impl.GenericDaoImpl.closeDataAccess(GenericDaoImpl.java:222)
at it.katuiros.core.db.dao.impl.GenericDaoImpl.update(GenericDaoImpl.java:96)
at it.katuiros.appLogic.PersistenceTest.saveTestData(PersistenceTest.java:157)
at it.katuiros.beans.generics.TestBean.<init>(TestBean.java:44)
... 67 more
Caused by: javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:74)
at it.katuiros.core.db.dao.impl.GenericDaoImpl.closeDataAccess(GenericDaoImpl.java:217)
... 70 more
正如您可能知道的那样,Hibernate中的那些RollbackException令人遗憾的是它们是不可行的!
CODE
用户实体:
@Entity
@Table(name = "USER")
@Access(AccessType.FIELD)
public class User extends DataModelEntity {
/**
* Serialization ID
*/
private static final long serialVersionUID = -1154710247841840471L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Basic(optional = false)
@Column(name = "ID")
protected int id;
@NotNull
@Column(name = "USERNAME", unique = true)
protected String username;
@NotNull
@Column(name = "PASSWORD")
protected String password;
@NotNull
@Column(name = "EMAIL", unique = true)
protected String email;
@Column(name = "FIRST_NAME")
protected String firstName;
@Column(name = "LAST_NAME")
protected String lastName;
@Column(name = "PHONE")
protected String telefono;
@ManyToMany
@LazyCollection(LazyCollectionOption.FALSE)
@Basic(optional = false)
@JoinColumn(name = "FAVORITE_ROOMS", referencedColumnName = "ID", updatable = true)
protected Set<Room> favoriteRooms;
@OneToMany
@LazyCollection(LazyCollectionOption.FALSE)
@Basic(optional = false)
@JoinColumn(name = "REGISTERED_ROOMS", referencedColumnName = "ID", updatable = true)
protected Set<Room> registeredRooms;
@OneToMany
@LazyCollection(LazyCollectionOption.FALSE)
@Basic(optional = true)
@JoinColumn(name = "EXTERNAL_MANAGEMENTS", referencedColumnName = "ID", updatable = true)
protected Set<ExternalRoomManagementPermissions> externalManagements;
@OneToOne
@JoinColumn(name = "AVATAR", referencedColumnName = "ID", updatable = true)
protected Image avatar;
... constructors and getters/setters ...
}
外部管理实体
@Entity
@Table(name = "EXTERNAL_ROOM_MANAGEMENT_PERMISSIONS")
@Access(AccessType.FIELD)
public class ExternalRoomManagementPermissions extends DataModelEntity {
/**
* Serialization ID
*/
private static final long serialVersionUID = 7122195658297760351L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Basic(optional = false)
@Column(name = "ID")
private int id;
/* CRIMINAL CODE */
@ManyToOne
@NotNull
@JoinColumn(name = "MANAGED_ROOM", referencedColumnName = "ID")
private Room managedRoom;
/* END OF CRIMINAL CODE */
... constructors and getters/setters ...
}
我不会发布会议室实体代码,因为它不是那么重要。
这是用于更新实体的数据访问对象
public abstract class GenericDaoImpl<T> implements GenericDao<T> {
protected EntityManager em;
private Class<T> type;
public GenericDaoImpl() {
Type t = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) t;
type = (Class) pt.getActualTypeArguments()[0];
this.em=null;
}
protected void finalize() throws Throwable{
this.closeDataAccess();
super.finalize();
}
/* DATA ACCESS LOGICS */
/* BASIC CRUD OPERATIONS */
@Override
public void create(final T t) throws DataAccessException {
try{
this.startDataAccess();
this.em.persist(t);
}catch(Exception e){
throw new DataAccessException("Data Access error while persisting Object.", e);
}finally{
this.closeDataAccess();
}
}
@Override
public void delete(final Object id) throws DataAccessException {
try{
this.startDataAccess();
this.em.remove(this.em.getReference(type, id));
}catch(Exception e){
throw new DataAccessException("Data Access error while removing Object.", e);
}finally{
this.closeDataAccess();
}
}
@Override
public T find(final Object id) throws DataAccessException {
T result = null;
try{
this.startDataAccess();
result = (T) this.em.find(type, id);
}catch(Exception e){
throw new DataAccessException("Data Access error while finding Object.", e);
}finally{
this.closeDataAccess();
}
return result;
}
@Override
public T update(final T t) throws DataAccessException {
T result = null;
try{
this.startDataAccess();
result = (T) this.em.merge(t);
}catch(Exception e){
throw new DataAccessException("Data Access error while updating Object.", e);
}finally{
this.closeDataAccess();
}
return result;
}
/* MORE OPERATIONS */
/**
* Method that returns the number of entries from a table that meet some
* criteria (where clause params)
*
* @param params
* sql parameters
* @return the number of records meeting the criteria
*/
@Override
public long countAll(final Map<String, Object> params) throws DataAccessException {
long result = -1;
try{
this.startDataAccess();
final StringBuffer queryString = new StringBuffer(
"SELECT count(o) from ");
queryString.append(type.getSimpleName()).append(" o ");
queryString.append(this.getQueryClauses(params, null));
final Query query = this.em.createQuery(queryString.toString());
result = (Long) query.getSingleResult();
}catch(Exception e){
throw new DataAccessException("Data Access error while counting Object.", e);
}finally{
this.closeDataAccess();
}
return result;
}
/**
* This method returns the whole list of all T entities available on the datasource.
*
* @return the whole list of all T entities.
*/
@Override
public List<T> list() throws DataAccessException {
List<T> resultList = null;
try{
this.startDataAccess();
final StringBuffer queryString = new StringBuffer("SELECT o from ");
queryString.append(type.getSimpleName()).append(" o ");
final Query query = this.em.createQuery(queryString.toString());
resultList =(List<T>) query.getResultList();
}catch(Exception e){
throw new DataAccessException("Data Access error while retrieving list of Objects.", e);
}finally{
this.closeDataAccess();
}
return resultList;
}
/* MISCELLANEOUS UTILITY METHODS */
protected String getQueryClauses(final Map<String, Object> params,
final Map<String, Object> orderParams) {
final StringBuffer queryString = new StringBuffer();
if ((params != null) && !params.isEmpty()) {
queryString.append(" where ");
for (final Iterator<Map.Entry<String, Object>> it = params
.entrySet().iterator(); it.hasNext();) {
final Map.Entry<String, Object> entry = it.next();
if (entry.getValue() instanceof Boolean) {
queryString.append(entry.getKey()).append(" is ")
.append(entry.getValue()).append(" ");
} else {
if (entry.getValue() instanceof Number) {
queryString.append(entry.getKey()).append(" = ")
.append(entry.getValue());
} else {
// string equality
queryString.append(entry.getKey()).append(" = '")
.append(entry.getValue()).append("'");
}
}
if (it.hasNext()) {
queryString.append(" and ");
}
}
}
if ((orderParams != null) && !orderParams.isEmpty()) {
queryString.append(" order by ");
for (final Iterator<Map.Entry<String, Object>> it = orderParams
.entrySet().iterator(); it.hasNext();) {
final Map.Entry<String, Object> entry = it.next();
queryString.append(entry.getKey()).append(" ");
if (entry.getValue() != null) {
queryString.append(entry.getValue());
}
if (it.hasNext()) {
queryString.append(", ");
}
}
}
return queryString.toString();
}
protected void startDataAccess() throws DataAccessException{
this.em = ContextEMF.createEntityManager();
if (this.em==null) throw new DataAccessException("No Entity Manager was created! Check if ContextEMF is correctly working.");
this.em.getTransaction().begin();
}
protected void closeDataAccess() throws DataAccessException{
if (this.em!=null){
try{
if (this.em.isOpen()){
this.em.getTransaction().commit();
this.em.clear();
this.em.close();
}
}catch(Exception e){
throw new DataAccessException("Data Access error while doing final commit.", e);
}
this.em=null;
}
}
}
这是一段代码,其中具有一组外部管理的用户在数据库上更新并抛出异常:
this.externalManagement1 = new ExternalRoomManagementPermissions(this.room4);
this.dbAccessor.saveData(this.externalManagement1);
this.externallyManagedRoomsForUser1.add(this.externalManagement1);
this.user1.setRegisteredRooms(this.registeredRoomsForUser1);
this.user2.setRegisteredRooms(this.registeredRoomsForUser2);
userDao.update(this.user1);
userDao.update(this.user2);
this.user1.setExternalManagements(this.externallyManagedRoomsForUser1);
userDao.update(this.user1); // <---- EXPLODING LINE
最后两行是关键步骤:如果外部管理实体具有关于与托管房间的ManyToOne关系的“刑法”,则userDao.update(...)将抛出异常;如果省略“刑法”,则userDao.update(...)不会抛出异常。
(在最后一个代码段中使用它之前,房间已经被保存到数据库中)
Sumptum:我在ExternalRoomManagementPermissions上有多对一关系,其中有一个Room作为子级,而一个用户的一对多关系有一个ExternalRoomManagementPermissions子集。 正如我已经解释了代码中第一个关系(外部管理到房间)的存在 - “刑法” - 使用户无法使用ExternalRoomManagementPermissions(第二个关系)的集合进行更新。
问题:遗憾的是,我需要与托管房间的ManyToOne关系,你知道为什么它会破坏持久性吗?
Post-Scriptum:我试图将ManyToOne关系更改为OneToOne,但它没有效果。
答案 0 :(得分:3)
确定!
我认为通过密集调试和广泛使用断点我发现了这个问题......作为一名程序员,我在整个人生中的调试工作从未如此之多! 引起错误的是非常愚蠢的:
"org.hibernate.InstantiationException: No default constructor for entity: : it.katuiros.model.entities.ExternalRoomManagementPermissions"
不知何故异常是在Hibernate层内部启动并在同一个地方吞噬,这就是为什么RollbackException本身没有任何原因。我可能会在Hibernate论坛/群组上发布错误信号。
通过将默认的空构造函数添加到ExternalRoomManagementPermissions,我设法使代码工作:
public ExternalRoomManagementPermissions(){
super();
}
非常感谢来自coderanch的Tim Holloway,来自stackoverflow的Mabi和James Massey的帮助!