" When executing a jpql query FetchMode.JOIN (which is the default for eager to one relations) is ignored. This is reasonable, because it would alter the query executed by the user. The unfortunate side effect is that n+1 queries are executed to fill the eager relation. Making the relation lazy is not always possible, because a non-owning optional relation can't be lazy. To \r\nTo prevent n+1 queries you can use the awesome feature @BatchSize or hibernate.default_batch_fetch_size. This works fine for both lazy and eager one-to-one relations one the owning side, but it does not work for non-owning eager (lazy is not possible due to HHH-12709) one-to-one relations. This \r\nThis is a big performance problem and I believe this is the main reason why people say one should avoid bidirectional one-to-one relations in hibernate. Looking \r\nLooking at the code this is because in [EntityType.resolve|https://github.com/hibernate/hibernate-orm/blob/f2238ec089d139270ff151771692fa4dcc4d7256/hibernate-core/src/main/java/org/hibernate/type/EntityType.java#L463-L468] it uses loadByUniqueKey for non-owning one-to-one realations, but unlike resolveIdentifier which internally calls [DefaultLoadEventListener.loadFromDatasource|https://github.com/hibernate/hibernate-orm/blob/f2238ec089d139270ff151771692fa4dcc4d7256/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java#L509] which uses batch fetching, [loadByUniqueKey uses Loader.doQuery|https://github.com/hibernate/hibernate-orm/blob/f2238ec089d139270ff151771692fa4dcc4d7256/hibernate-core/src/main/java/org/hibernate/loader/Loader.java#L924] which does not use batch fetching.
\r\n\r\nTestcase:\r\n {code:java} package \r\npackage org.hibernate.bugs;
import \r\n\r\nimport static org.assertj.core.api.Assertions.assertThat;
import \r\n\r\nimport java.util.ArrayList; import \r\nimport java.util.Collections; import \r\nimport java.util.List;
import \r\n\r\nimport javax.persistence.CascadeType; import \r\nimport javax.persistence.Entity; import \r\nimport javax.persistence.EntityManager; import \r\nimport javax.persistence.EntityManagerFactory; import \r\nimport javax.persistence.FetchType; import \r\nimport javax.persistence.Id; import \r\nimport javax.persistence.OneToOne; import \r\nimport javax.persistence.Persistence; import \r\nimport javax.persistence.Query;
import \r\n\r\nimport org.apache.log4j.AppenderSkeleton; import \r\nimport org.apache.log4j.Level; import \r\nimport org.apache.log4j.Logger; import \r\nimport org.apache.log4j.spi.LoggingEvent; import \r\nimport org.hibernate.annotations.BatchSize; import \r\nimport org.junit.After; import \r\nimport org.junit.Before; import \r\nimport org.junit.Test;
\r\n\r\n /** \r\n * This template demonstrates how to develop a test case for Hibernate ORM, using the Java Persistence API. \r\n */ public \r\npublic class JPAUnitTestCase {
\r\n\r\n private EntityManagerFactory entityManagerFactory; \r\n private Logger sqlLogger; \r\n private Level initialSqlLevel;
\r\n\r\n @Before \r\n public void init() { \r\n sqlLogger = Logger.getLogger( \ "org.hibernate.SQL \ "); \r\n initialSqlLevel = sqlLogger.getLevel(); \r\n sqlLogger.setLevel(Level.DEBUG); \r\n entityManagerFactory = Persistence.createEntityManagerFactory( \ "templatePU \ "); \r\n }
\r\n\r\n @After \r\n public void destroy() { \r\n entityManagerFactory.close(); \r\n sqlLogger.setLevel(initialSqlLevel); \r\n }
\r\n\r\n // Entities are auto-discovered, so just add them anywhere on class-path \r\n // Add your tests, using standard JUnit. \r\n @Test \r\n public void hhh123Test() throws Exception { \r\n EntityManager entityManager = entityManagerFactory.createEntityManager(); \r\n int numberOfParents = 5;
\r\n\r\n runInTransaction(entityManager, () -> { \r\n for (int id = 0; id < numberOfParents; id++) { \r\n Parent parent = new Parent(id); \r\n new Child(id + 100, parent); \r\n entityManager.persist(parent); \r\n } \r\n });
\r\n\r\n runInTransaction(entityManager, () -> { \r\n RecordingLog4jAppender appender = new RecordingLog4jAppender(); \r\n sqlLogger.addAppender(appender);
\r\n\r\n Query query = entityManager.createQuery( \ "from \ " + Parent.class.getSimpleName()); \r\n @SuppressWarnings( \ "unchecked \ ") \r\n List<Parent> parents = query.getResultList(); \r\n sqlLogger.removeAppender(appender); \r\n assertThat(parents).hasSize(numberOfParents); \r\n assertThat(appender.getLogEvents()).size().isLessThan(numberOfParents); \r\n });
\r\n\r\n entityManager.close(); \r\n }
\r\n\r\n private void runInTransaction(EntityManager entityManager, Runnable runnable) { \r\n entityManager.getTransaction().begin(); \r\n runnable.run(); \r\n entityManager.getTransaction().commit(); \r\n entityManager.clear(); \r\n } \r\n }
\r\n\r\n\r\n @Entity \r\n @BatchSize(size = 20) class \r\nclass Parent { \r\n @Id \r\n private long id;
\r\n\r\n @OneToOne(mappedBy = \ "parent \ ", cascade = CascadeType.ALL) \r\n private Child child;
\r\n\r\n Parent() {}
\r\n\r\n public Parent(long id) { \r\n this.id = id; \r\n }
\r\n\r\n public Child getChild() { \r\n return child; \r\n }
\r\n\r\n void setChild(Child child) { \r\n this.child = child; \r\n }
\r\n\r\n @Override \r\n public String toString() { \r\n return \ "Parent [id= \ " + id + \ ", child= \ " + child + \ "] \ "; \r\n } \r\n }
\r\n\r\n\r\n @Entity \r\n @BatchSize(size = 20) class \r\nclass Child { \r\n @Id \r\n private long id;
\r\n\r\n @OneToOne(fetch = FetchType.LAZY) \r\n private Parent parent;
\r\n\r\n Child() {}
\r\n\r\n public Child(long id, Parent parent) { \r\n this.id = id; \r\n setParent(parent); \r\n }
\r\n\r\n public Parent getParent() { \r\n return parent; \r\n }
\r\n\r\n public void setParent(Parent parent) { \r\n this.parent = parent; \r\n parent.setChild(this); \r\n }
\r\n\r\n @Override \r\n public String toString() { \r\n return \ "Child [id= \ " + id + \ "] \ "; \r\n } \r\n }
class \r\n\r\n\r\nclass RecordingLog4jAppender extends AppenderSkeleton { \r\n private final List<LoggingEvent> logEvents = new ArrayList<>(); \r\n private boolean active = true;
\r\n\r\n @Override \r\n public boolean requiresLayout() { \r\n return false; \r\n }
\r\n\r\n @Override \r\n protected void append(LoggingEvent loggingEvent) { \r\n if (active) { \r\n logEvents.add(loggingEvent); \r\n } \r\n }
\r\n\r\n @Override \r\n public void close() {}
\r\n\r\n public void setActive(boolean value) { \r\n active = value; \r\n }
\r\n\r\n public void clear() { \r\n logEvents.clear(); \r\n }
\r\n\r\n public List<LoggingEvent> getLogEvents() { \r\n return Collections.unmodifiableList(logEvents); \r\n } \r\n } \r\n {code} \r\n" |
|