diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index bde56cf1e4..863471e24e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -476,6 +476,9 @@ public interface CoreMessageLogger extends BasicLogger { @Message(value = "Bytecode enhancement failed: %s", id = 142) 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) @Message(value = "%s = false breaks the EJB3 specification", id = 144) void jdbcAutoCommitFalseBreaksEjb3Spec(String autocommit); diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java index d67b9720c6..8b91af7dc9 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyFactory.java @@ -6,14 +6,11 @@ */ package org.hibernate.proxy.pojo.bytebuddy; -import static org.hibernate.internal.CoreLogging.messageLogger; - import java.io.Serializable; import java.lang.reflect.Method; import java.util.Set; import org.hibernate.HibernateException; -import org.hibernate.cfg.Environment; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; @@ -23,6 +20,8 @@ import org.hibernate.proxy.ProxyConfiguration; import org.hibernate.proxy.ProxyFactory; import org.hibernate.type.CompositeType; +import static org.hibernate.internal.CoreLogging.messageLogger; + public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class ); @@ -87,14 +86,20 @@ public class ByteBuddyProxyFactory implements ProxyFactory, Serializable { ); try { - final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); + final HibernateProxy proxy = (HibernateProxy) proxyClass.getConstructor().newInstance(); ( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor ); return proxy; } + catch (NoSuchMethodException e) { + String logMessage = LOG.bytecodeEnhancementFailedBecauseOfDefaultConstructor( entityName ); + LOG.error( logMessage, e ); + throw new HibernateException( logMessage, e ); + } catch (Throwable t) { - LOG.error( LOG.bytecodeEnhancementFailed( entityName ), t ); - throw new HibernateException( LOG.bytecodeEnhancementFailed( entityName ), t ); + String logMessage = LOG.bytecodeEnhancementFailed( entityName ); + LOG.error( logMessage, t ); + throw new HibernateException( logMessage, t ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistProxyFactory.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistProxyFactory.java index ac76f1cbce..ce28831258 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistProxyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/javassist/JavassistProxyFactory.java @@ -122,15 +122,21 @@ public class JavassistProxyFactory implements ProxyFactory, Serializable { ); try { - final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance(); + final HibernateProxy proxy = (HibernateProxy) proxyClass.getConstructor().newInstance(); ( (Proxy) proxy ).setHandler( initializer ); initializer.constructed(); return proxy; } + catch (NoSuchMethodException e) { + String logMessage = LOG.bytecodeEnhancementFailedBecauseOfDefaultConstructor( entityName ); + LOG.error( logMessage, e ); + throw new HibernateException( logMessage, e ); + } catch (Throwable t) { - LOG.error( LOG.bytecodeEnhancementFailed( entityName ), t ); - throw new HibernateException( LOG.bytecodeEnhancementFailed( entityName ), t ); + String logMessage = LOG.bytecodeEnhancementFailed( entityName ); + LOG.error( logMessage, t ); + throw new HibernateException( logMessage, t ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PrivateConstructorTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PrivateConstructorTest.java new file mode 100644 index 0000000000..4dde40f5d6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/PrivateConstructorTest.java @@ -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 . + */ +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; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/ProtectedConstructorTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/ProtectedConstructorTest.java new file mode 100644 index 0000000000..03a9c664f5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/callbacks/ProtectedConstructorTest.java @@ -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 . + */ +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; + } + } +}