HHH-13020 - When proxying an entity having a private default constructor, the log message is not very clear about the problem

This commit is contained in:
Vlad Mihalcea 2018-10-10 12:41:13 +03:00
parent 4dac6dbe3b
commit df3edbd7b7
5 changed files with 319 additions and 9 deletions

View File

@ -476,6 +476,9 @@ public interface CoreMessageLogger extends BasicLogger {
@Message(value = "Bytecode enhancement failed: %s", id = 142) @Message(value = "Bytecode enhancement failed: %s", id = 142)
String bytecodeEnhancementFailed(String entityName); String bytecodeEnhancementFailed(String entityName);
@Message(value = "Bytecode enhancement failed because no public, protected or package-private default constructor was found for entity: %s. Private constructors don't work with runtime proxies!", id = 143)
String bytecodeEnhancementFailedBecauseOfDefaultConstructor(String entityName);
@LogMessage(level = WARN) @LogMessage(level = WARN)
@Message(value = "%s = false breaks the EJB3 specification", id = 144) @Message(value = "%s = false breaks the EJB3 specification", id = 144)
void jdbcAutoCommitFalseBreaksEjb3Spec(String autocommit); void jdbcAutoCommitFalseBreaksEjb3Spec(String autocommit);

View File

@ -6,14 +6,11 @@
*/ */
package org.hibernate.proxy.pojo.bytebuddy; package org.hibernate.proxy.pojo.bytebuddy;
import static org.hibernate.internal.CoreLogging.messageLogger;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Set; import java.util.Set;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.ReflectHelper;
@ -23,6 +20,8 @@ import org.hibernate.proxy.ProxyConfiguration;
import org.hibernate.proxy.ProxyFactory; import org.hibernate.proxy.ProxyFactory;
import org.hibernate.type.CompositeType; import org.hibernate.type.CompositeType;
import static org.hibernate.internal.CoreLogging.messageLogger;
public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { public class ByteBuddyProxyFactory implements ProxyFactory, Serializable {
private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class ); private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class );
@ -87,14 +86,20 @@ public class ByteBuddyProxyFactory implements ProxyFactory, Serializable {
); );
try { try {
final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); final HibernateProxy proxy = (HibernateProxy) proxyClass.getConstructor().newInstance();
( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor ); ( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor );
return proxy; return proxy;
} }
catch (NoSuchMethodException e) {
String logMessage = LOG.bytecodeEnhancementFailedBecauseOfDefaultConstructor( entityName );
LOG.error( logMessage, e );
throw new HibernateException( logMessage, e );
}
catch (Throwable t) { catch (Throwable t) {
LOG.error( LOG.bytecodeEnhancementFailed( entityName ), t ); String logMessage = LOG.bytecodeEnhancementFailed( entityName );
throw new HibernateException( LOG.bytecodeEnhancementFailed( entityName ), t ); LOG.error( logMessage, t );
throw new HibernateException( logMessage, t );
} }
} }
} }

View File

@ -122,15 +122,21 @@ public class JavassistProxyFactory implements ProxyFactory, Serializable {
); );
try { try {
final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); final HibernateProxy proxy = (HibernateProxy) proxyClass.getConstructor().newInstance();
( (Proxy) proxy ).setHandler( initializer ); ( (Proxy) proxy ).setHandler( initializer );
initializer.constructed(); initializer.constructed();
return proxy; return proxy;
} }
catch (NoSuchMethodException e) {
String logMessage = LOG.bytecodeEnhancementFailedBecauseOfDefaultConstructor( entityName );
LOG.error( logMessage, e );
throw new HibernateException( logMessage, e );
}
catch (Throwable t) { catch (Throwable t) {
LOG.error( LOG.bytecodeEnhancementFailed( entityName ), t ); String logMessage = LOG.bytecodeEnhancementFailed( entityName );
throw new HibernateException( LOG.bytecodeEnhancementFailed( entityName ), t ); LOG.error( logMessage, t );
throw new HibernateException( logMessage, t );
} }
} }

View File

@ -0,0 +1,160 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.callbacks;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.SessionImpl;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.logger.LoggerInspectionRule;
import org.hibernate.testing.logger.Triggerable;
import org.hibernate.testing.util.ExceptionUtil;
import org.junit.Rule;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@TestForIssue( jiraKey = "HHH-13020" )
public class PrivateConstructorTest extends BaseEntityManagerFunctionalTestCase {
@Rule
public LoggerInspectionRule logInspection = new LoggerInspectionRule( Logger.getMessageLogger( CoreMessageLogger.class, ByteBuddyProxyFactory.class.getName() ) );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Parent.class,
Child.class
};
}
@Test
public void test() {
Child child = new Child();
EntityManager entityManager = null;
EntityTransaction txn = null;
try {
entityManager = createEntityManager();
txn = entityManager.getTransaction();
txn.begin();
entityManager.persist( child );
txn.commit();
entityManager.clear();
Integer childId = child.getId();
Triggerable triggerable = logInspection.watchForLogMessages( "HHH000143:" );
Child childReference = entityManager.getReference( Child.class, childId );
try {
assertEquals( child.getParent().getName(), childReference.getParent().getName() );
}
catch (Exception expected) {
assertEquals( NoSuchMethodException.class, ExceptionUtil.rootCause( expected ).getClass() );
assertTrue( expected.getMessage().contains(
"Bytecode enhancement failed because no public, protected or package-private default constructor was found for entity"
) );
}
assertTrue( triggerable.wasTriggered() );
}
catch (Throwable e) {
if ( txn != null && txn.isActive() ) {
txn.rollback();
}
throw e;
}
finally {
if ( entityManager != null ) {
entityManager.close();
}
}
}
@Entity(name = "Parent")
public static class Parent {
private Integer id;
private String name;
private Parent() {
name = "Empty";
}
public Parent(String s) {
this.name = s;
}
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.AUTO)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity(name = "Child")
public static class Child {
private Integer id;
private Parent parent;
public Child() {
this.parent = new Parent( "Name" );
}
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.AUTO)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
}

View File

@ -0,0 +1,136 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.callbacks;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@TestForIssue( jiraKey = "HHH-13020" )
public class ProtectedConstructorTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Parent.class,
Child.class
};
}
@Test
public void test() {
Child child = new Child();
EntityManager entityManager = null;
EntityTransaction txn = null;
try {
entityManager = createEntityManager();
txn = entityManager.getTransaction();
txn.begin();
entityManager.persist( child );
txn.commit();
entityManager.clear();
Integer childId = child.getId();
Child childReference = entityManager.getReference( Child.class, childId );
assertEquals( child.getParent().getName(), childReference.getParent().getName() );
}
catch (Throwable e) {
if ( txn != null && txn.isActive() ) {
txn.rollback();
}
throw e;
}
finally {
if ( entityManager != null ) {
entityManager.close();
}
}
}
@Entity(name = "Parent")
public static class Parent {
private Integer id;
private String name;
protected Parent() {
name = "Empty";
}
public Parent(String s) {
this.name = s;
}
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.AUTO)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity(name = "Child")
public static class Child {
private Integer id;
private Parent parent;
public Child() {
this.parent = new Parent( "Name" );
}
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.AUTO)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
}