Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
import org.hibernate.metamodel.mapping.TableDetails;
import org.hibernate.metamodel.mapping.ValuedModelPart;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.metamodel.mapping.ordering.OrderByFragmentTranslator;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
Expand Down Expand Up @@ -225,11 +226,31 @@ private static void injectAttributeMapping(
@Override
public boolean isBidirectionalAttributeName(NavigablePath fetchablePath, ToOneAttributeMapping modelPart) {
return bidirectionalAttributeName == null
// If the FK-target of the to-one mapping is the same as the FK-target of this plural mapping,
// then we say this is bidirectional, given that this is only invoked for model parts of the
// collection elements
? fkDescriptor.getTargetPart() == modelPart.getForeignKeyDescriptor().getTargetPart()
: fetchablePath.getLocalName().endsWith( bidirectionalAttributeName );
// If the FK-target of the to-one mapping is the same as the FK-target of this one-to-many mapping,
// and the FK-key refer to the same column then we say this is bidirectional,
// given that this is only invoked for model parts of the collection elements
? modelPart.getSideNature() == ForeignKeyDescriptor.Nature.KEY
&& collectionDescriptor.isOneToMany()
&& fkDescriptor.getTargetPart() == modelPart.getForeignKeyDescriptor().getTargetPart()
&& areEqual( fkDescriptor.getKeyPart(), modelPart.getForeignKeyDescriptor().getKeyPart() )
: fetchablePath.getLocalName().equals( bidirectionalAttributeName );
}

private boolean areEqual(ValuedModelPart part1, ValuedModelPart part2) {
final int typeCount = part1.getJdbcTypeCount();
if ( part2.getJdbcTypeCount() != typeCount ) {
return false;
}
for ( int i = 0; i < typeCount; i++ ) {
final SelectableMapping selectable1 = part1.getSelectable( i );
final SelectableMapping selectable2 = part2.getSelectable( i );
if ( selectable1.getJdbcMapping() != selectable2.getJdbcMapping()
|| !selectable1.getContainingTableExpression().equals( selectable2.getContainingTableExpression() )
|| !selectable1.getSelectionExpression().equals( selectable2.getSelectionExpression() ) ) {
return false;
}
}
return true;
}

public void finishInitialization(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.mapping.collections;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

@Jpa( annotatedClasses = {BidirectionalOneToManyTest.Organization.class, BidirectionalOneToManyTest.User.class} )
@Jira("https://hibernate.atlassian.net/browse/HHH-19963")
public class BidirectionalOneToManyTest {

@Test
public void testParentNotTreatedAsBidirectional(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
Organization o3 = new Organization( 3L, "o3", null, new ArrayList<>() );
Organization o1 = new Organization( 1L, "o1", null, new ArrayList<>( Arrays.asList( o3 )) );
Organization o2 = new Organization( 2L, "o2", o1, new ArrayList<>() );
entityManager.persist(o3);
entityManager.persist(o1);
entityManager.persist(o2);

User u1 = new User( 1L, o2 );
User u2 = new User( 2L, o2 );
entityManager.persist(u1);
entityManager.persist(u2);
});

scope.inTransaction( entityManager -> {
User user1 = entityManager.find(User.class, 1L);

Check notice

Code scanning / CodeQL

Unread local variable Note test

Variable 'User user1' is never read.
Organization ou3 = entityManager.find(Organization.class, 3L);
assertNull( ou3.getParentOrganization(), "Parent of o3 is null");
assertEquals(0, ou3.getPredecessorOrganizations().size(), "Predecessors of o3 is empty");
});
}

@Entity(name = "Organization")
public static class Organization {

@Id
private Long id;
private String name;

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "parentorganization_objectId")
private Organization parentOrganization;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "organization_predecessor")
private List<Organization> predecessorOrganizations = new ArrayList<>();

public Organization() {
}

public Organization(Long id, String name, Organization parentOrganization, List<Organization> predecessorOrganizations) {
this.id = id;
this.name = name;
this.parentOrganization = parentOrganization;
this.predecessorOrganizations = predecessorOrganizations;
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public Organization getParentOrganization() {
return parentOrganization;
}

public List<Organization> getPredecessorOrganizations() {
return predecessorOrganizations;
}
}

@Entity(name = "User")
@Table(name = "usr_tbl")
public static class User {

@Id
private Long id;
@ManyToOne(fetch = FetchType.EAGER)
private Organization organization;

public User() {
}

public User(Long id, Organization organization) {
this.id = id;
this.organization = organization;
}

public Long getId() {
return id;
}

public Organization getOrganization() {
return organization;
}
}
}