HHH-15960 make @Comment annotation repeatable and properly test it

I didn't quite nail this one first time round :-/
This commit is contained in:
Gavin 2023-01-04 11:54:57 +01:00 committed by Gavin King
parent 98957c3509
commit e3f1c2741d
7 changed files with 213 additions and 7 deletions

View File

@ -9,16 +9,12 @@ package org.hibernate.userguide.pc;
import java.util.List; import java.util.List;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NoResultException;
import jakarta.persistence.OneToMany;
import jakarta.persistence.SecondaryTable; import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.Filter; import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef; import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef; import org.hibernate.annotations.ParamDef;
@ -92,9 +88,11 @@ public class FilterSqlFragementAliasTest extends BaseEntityManagerFunctionalTest
//tag::pc-filter-sql-fragment-alias-example[] //tag::pc-filter-sql-fragment-alias-example[]
@Entity(name = "Account") @Entity(name = "Account")
@Table(name = "account") @Table(name = "account")
@Comment(on="account", value = "The account table")
@SecondaryTable( @SecondaryTable(
name = "account_details" name = "account_details"
) )
@Comment(on="account_details", value = "The account details secondary table")
@SQLDelete( @SQLDelete(
sql = "UPDATE account_details SET deleted = true WHERE id = ? " sql = "UPDATE account_details SET deleted = true WHERE id = ? "
) )

View File

@ -8,6 +8,7 @@ package org.hibernate.annotations;
import org.hibernate.binder.internal.CommentBinder; import org.hibernate.binder.internal.CommentBinder;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@ -51,6 +52,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@AttributeBinderType(binder = CommentBinder.class) @AttributeBinderType(binder = CommentBinder.class)
@Target({METHOD, FIELD, TYPE}) @Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME) @Retention(RUNTIME)
@Repeatable(Comments.class)
public @interface Comment { public @interface Comment {
/** /**
* The text of the comment. * The text of the comment.

View File

@ -0,0 +1,34 @@
/*
* 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.annotations;
import org.hibernate.binder.internal.CommentBinder;
import org.hibernate.binder.internal.CommentsBinder;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A list of {@link Comment}s.
* <p>
* If there are multiple {@link Comment}s on a class or attribute,
* they must have distinct {@link Comment#on() on} members.
*
* @author Gavin King
*/
@TypeBinderType(binder = CommentsBinder.class)
@AttributeBinderType(binder = CommentsBinder.class)
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
public @interface Comments {
Comment[] value();
}

View File

@ -0,0 +1,59 @@
/*
* 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.binder.internal;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.Comments;
import org.hibernate.binder.AttributeBinder;
import org.hibernate.binder.TypeBinder;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import java.util.HashSet;
import java.util.Set;
/**
* Handles the {@link Comments} annotation.
*
* @author Gavin King
*/
public class CommentsBinder implements TypeBinder<Comments>, AttributeBinder<Comments> {
@Override
public void bind(Comments comments, MetadataBuildingContext context, PersistentClass entity, Property property) {
final CommentBinder commentBinder = new CommentBinder();
final Set<String> ons = new HashSet<>( comments.value().length );
for ( Comment comment : comments.value() ) {
if ( !ons.add( comment.on() ) ) {
throw new AnnotationException( "Multiple '@Comment' annotations of '"
+ property.getName() + "' had the same 'on'" );
}
commentBinder.bind( comment, context, entity, property );
}
}
@Override
public void bind(Comments comments, MetadataBuildingContext context, PersistentClass entity) {
final CommentBinder commentBinder = new CommentBinder();
final Set<String> ons = new HashSet<>( comments.value().length );
for ( Comment comment : comments.value() ) {
if ( !ons.add( comment.on() ) ) {
throw new AnnotationException( "Multiple '@Comment' annotations of entity '"
+ entity.getEntityName() + "' had the same 'on'" );
}
commentBinder.bind( comment, context, entity );
}
}
@Override
public void bind(Comments comments, MetadataBuildingContext context, Component embeddable) {
throw new AnnotationException( "Embeddable class '" + embeddable.getComponentClassName()
+ "' was annotated '@Comment' (annotate its attributes instead)" );
}
}

View File

@ -2035,7 +2035,7 @@ public class EntityBinder {
QualifiedTableName logicalName, QualifiedTableName logicalName,
Table table) { Table table) {
final Join join = new Join(); final Join join = new Join();
join.setPersistentClass( persistentClass ); persistentClass.addJoin( join );
final InFlightMetadataCollector.EntityTableXref tableXref final InFlightMetadataCollector.EntityTableXref tableXref
= context.getMetadataCollector().getEntityTableXref( persistentClass.getEntityName() ); = context.getMetadataCollector().getEntityTableXref( persistentClass.getEntityName() );

View File

@ -803,7 +803,9 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
} }
public void addJoin(Join join) { public void addJoin(Join join) {
if ( !joins.contains(join) ) {
joins.add( join ); joins.add( join );
}
join.setPersistentClass( this ); join.setPersistentClass( this );
} }

View File

@ -0,0 +1,111 @@
/*
* 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.orm.test.annotations.comment;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import org.hibernate.annotations.Comment;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.BaseUnitTest;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Currency;
import java.util.stream.StreamSupport;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
/**
* @author Yanming Zhou
*/
@BaseUnitTest
public class CommentsTest {
private static final String TABLE_NAME = "TestEntity";
private static final String SEC_TABLE_NAME = "TestEntity2";
private static final String TABLE_COMMENT = "I am a table";
private static final String SEC_TABLE_COMMENT = "I am a table too";
@Test
@TestForIssue(jiraKey = "HHH-4369")
public void testComments() {
StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build();
Metadata metadata = new MetadataSources(ssr).addAnnotatedClass(TestEntity.class).buildMetadata();
org.hibernate.mapping.Table table = StreamSupport.stream(metadata.getDatabase().getNamespaces().spliterator(), false)
.flatMap(namespace -> namespace.getTables().stream()).filter(t -> t.getName().equals(TABLE_NAME))
.findFirst().orElse(null);
assertThat(table.getComment(), is(TABLE_COMMENT));
assertThat(table.getColumns().size(), is(6));
for (org.hibernate.mapping.Column col : table.getColumns()) {
assertThat(col.getComment(), is("I am " + col.getName()));
}
table = StreamSupport.stream(metadata.getDatabase().getNamespaces().spliterator(), false)
.flatMap(namespace -> namespace.getTables().stream()).filter(t -> t.getName().equals(SEC_TABLE_NAME))
.findFirst().orElse(null);
assertThat(table.getComment(), is(SEC_TABLE_COMMENT));
assertThat(table.getColumns().size(), is(2));
long count = table.getColumns().stream().filter(col -> "This is a date".equalsIgnoreCase(col.getComment())).count();
assertThat(count, is(1L));
}
@Entity(name = "Person")
@Table(name = TABLE_NAME)
@SecondaryTable(name = SEC_TABLE_NAME)
@Comment(TABLE_COMMENT)
@Comment(value = SEC_TABLE_COMMENT, on = SEC_TABLE_NAME)
public static class TestEntity {
@Id
@GeneratedValue
@Comment("I am id")
private Long id;
@Comment(on = "firstName", value = "I am firstName")
@Comment(on = "lastName", value = "I am lastName")
private Name name;
private Money money;
@Column(table = SEC_TABLE_NAME)
@Comment("This is a date")
private LocalDate localDate;
@ManyToOne
@JoinColumn(name = "other")
@Comment("I am other")
private TestEntity other;
}
@Embeddable
public static class Name {
private String firstName;
private String lastName;
}
@Embeddable
public static class Money {
@Comment("I am amount")
private BigDecimal amount;
@Comment("I am currency")
private Currency currency;
}
}