Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-18829 Auto generate record ID class if no explicit @IdClass #9220

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -481,10 +481,22 @@ private boolean mapAsIdClass(
final ClassDetails classWithIdClass = inheritanceState.getClassWithIdClass( false );
if ( classWithIdClass != null ) {
final IdClass idClassAnn = classWithIdClass.getDirectAnnotationUsage( IdClass.class );
final Class<?> idClassValue = idClassAnn.value();
final ClassDetails compositeClass =
getMetadataCollector().getSourceModelBuildingContext().getClassDetailsRegistry()
.resolveClassDetails( idClassValue.getName() );
final ClassDetails compositeClass;
if ( idClassAnn == null ) {
try {
compositeClass = getMetadataCollector().getSourceModelBuildingContext()
.getClassDetailsRegistry()
.resolveClassDetails( inheritanceState.getClassDetails().getClassName() + "_$Id" );
}
catch (RuntimeException e) {
return false;
}
}
else {
final Class<?> idClassValue = idClassAnn.value();
compositeClass = getMetadataCollector().getSourceModelBuildingContext()
.getClassDetailsRegistry().resolveClassDetails( idClassValue.getName() );
}
final TypeDetails compositeType = new ClassTypeDetailsImpl( compositeClass, TypeDetails.Kind.CLASS );
final TypeDetails classWithIdType = new ClassTypeDetailsImpl( classWithIdClass, TypeDetails.Kind.CLASS );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.hibernate.AnnotationException;
import org.hibernate.boot.spi.AccessType;
Expand Down Expand Up @@ -183,6 +184,13 @@ else if ( classDetails.hasDirectAnnotationUsage( IdClass.class ) ) {
return classDetails;
}
else {
final long count = Stream.concat(
classDetails.getFields().stream(),
classDetails.getMethods().stream()
).filter( t -> t.hasDirectAnnotationUsage( Id.class ) ).count();
if ( count > 1 ) {
return classDetails;
}
final InheritanceState state = getSuperclassInheritanceState( classDetails, inheritanceStatePerClass );
if ( state != null ) {
return state.getClassWithIdClass( true );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.mapping.hhh18829;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class EmployeeWithoutIdClass {
@Id
String empName;
@Id
Integer empId;
String address;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.mapping.hhh18829;

import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.lang.reflect.InvocationTargetException;

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

@DomainModel(annotatedClasses = EmployeeWithoutIdClass.class)
@JiraKey(" HHH-18829")
@SessionFactory
public class HHH18829Test {

@BeforeAll
void setUp(SessionFactoryScope sessionFactoryScope) {
sessionFactoryScope.inTransaction( sess -> {
final var one = new EmployeeWithoutIdClass();
one.empName = "John Doe";
one.empId = 1;
one.address = "10 Downing Street, SW1A 2AA";
sess.persist( one );

final var two = new EmployeeWithoutIdClass();
two.empName = "Dave Default";
two.empId = 1;
two.address = "1600 Pennsylvania Avenue";
sess.persist( two );
} );
}

@Test
public void test(SessionFactoryScope sessionFactoryScope)
throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
final var idClass = Class.forName( EmployeeWithoutIdClass.class.getName() + "_$Id" );
final var id = idClass.getConstructors()[0].newInstance( "John Doe", 1 );
final var employees = sessionFactoryScope.fromSession(
sess -> sess.createQuery( "from EmployeeWithoutIdClass where id=:id", EmployeeWithoutIdClass.class ).setParameter( "id", id )
.getResultList()
);
assertEquals( 1, employees.size() );
assertEquals( "10 Downing Street, SW1A 2AA", employees.get( 0 ).address );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,5 @@ private static String writeStaticMetaModelAnnotation(Metamodel entity) {
: "jakarta.persistence.metamodel.StaticMetamodel";
return "@" + entity.importType( annotation ) + "(" + entity.getSimpleName() + ".class)";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
*/
public class AnnotationMetaEntity extends AnnotationMeta {

private static final String ID_CLASS_MEMBER_NAME = "<ID_CLASS>";

private final ImportContext importContext;
private final TypeElement element;
private final Map<String, MetaAttribute> members;
Expand Down Expand Up @@ -439,6 +441,8 @@ && containsAnnotation( method, HQL, SQL, FIND ) ) {

addPersistentMembers( fieldsOfClass, AccessType.FIELD );
addPersistentMembers( gettersAndSettersOfClass, AccessType.PROPERTY );

addIdClassIfNeeded( fieldsOfClass, gettersAndSettersOfClass );
}

addAuxiliaryMembers();
Expand All @@ -452,6 +456,33 @@ && containsAnnotation( method, HQL, SQL, FIND ) ) {
initialized = true;
}

private void addIdClassIfNeeded(List<? extends Element> fields, List<? extends Element> methods) {
if ( hasAnnotation( element, ID_CLASS ) ) {
return;
}
final List<MetaAttribute> components = new ArrayList<>();
for ( Element field : fields ) {
if ( hasAnnotation( field, ID ) && isPersistent( field, AccessType.FIELD ) ) {
final String propertyName = propertyName( this, field );
if ( members.containsKey( propertyName ) ) {
components.add( members.get( propertyName ) );
}
}
}
for ( Element method : methods ) {
if ( hasAnnotation( method, ID ) && isPersistent( method, AccessType.PROPERTY ) ) {
final String propertyName = propertyName( this, method );
if ( members.containsKey( propertyName ) ) {
components.add( members.get( propertyName ) );
}
}
}
if ( components.size() < 2 ) {
return;
}
putMember( ID_CLASS_MEMBER_NAME, new IdClassMetaAttribute( this, components ) );
}

private boolean checkEntities(List<ExecutableElement> lifecycleMethods) {
boolean foundPersistenceEntity = false;
VariableElement nonPersistenceParameter = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
*/
package org.hibernate.processor.annotation;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;

import org.hibernate.processor.model.MetaSingleAttribute;
import org.hibernate.processor.util.Constants;

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

/**
* @author Max Andersen
* @author Hardy Ferentschik
Expand All @@ -24,4 +28,9 @@ public AnnotationMetaSingleAttribute(AnnotationMetaEntity parent, Element elemen
public final String getMetaType() {
return Constants.SINGULAR_ATTRIBUTE;
}

@Override
public List<AnnotationMirror> inheritedAnnotations() {
return new ArrayList<>(element.getAnnotationMirrors());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.processor.annotation;

import org.hibernate.processor.model.MetaAttribute;
import org.hibernate.processor.model.Metamodel;

import java.util.List;

public class IdClassMetaAttribute implements MetaAttribute {

private final Metamodel parent;

private final List<MetaAttribute> components;

public IdClassMetaAttribute(Metamodel parent, List<MetaAttribute> components) {
this.parent = parent;
this.components = components;
}

@Override
public boolean hasTypedAttribute() {
return true;
}

@Override
public boolean hasStringAttribute() {
return false;
}

@Override
public String getAttributeDeclarationString() {
final StringBuilder decl = new StringBuilder()
.append("\n/**\n * Static ID class for {@link ")
.append( parent.getQualifiedName() )
.append( "}\n **/\n" )
.append( "public record Id" );
String delimiter = "(";
for ( MetaAttribute component : components ) {
decl.append( delimiter ).append( parent.importType( component.getTypeDeclaration() ) )
.append( ' ' ).append( component.getPropertyName() );
delimiter = ", ";
}
return decl.append( ") {}" ).toString();
}

@Override
public String getAttributeNameDeclarationString() {
return "";
}

@Override
public String getMetaType() {
return "";
}

@Override
public String getPropertyName() {
return "";
}

@Override
public String getTypeDeclaration() {
return "";
}

@Override
public Metamodel getHostingEntity() {
return parent;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.processor.test.hhh18829;

import jakarta.persistence.MappedSuperclass;

@MappedSuperclass
public class Address {
String address;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.processor.test.hhh18829;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class AnotherEmployee extends Address {
Integer empId;

String empName;

@Id
public Integer getEmpId() {
return empId;
}

public void setEmpId(Integer empId) {
this.empId = empId;
}

@Id
public String getEmpName() {
return empName;
}

public void setEmpName(String empName) {
this.empName = empName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.processor.test.hhh18829;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Employee extends Address {
@Id
String empName;
@Id
Integer empId;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.processor.test.hhh18829;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;

@Entity
@IdClass(EmployeeWithIdClass.EmployeeId.class)
public class EmployeeWithIdClass extends Address {
@Id
String empName;
@Id
Integer empId;

public record EmployeeId(String empName, Integer empId) {
}

}
Loading