¿Cómo represento una tabla de unión con un campo adicional usando eBean y Kotlin?

Estoy agregando un "rol" de campo a la tabla book_author del ejemplo de relación canonical autor / libro many-to-many:

create table author ( id bigint auto_increment not null, name varchar(255), constraint pk_author primary key (id) ); create table book ( id bigint auto_increment not null, title varchar(255), constraint pk_book primary key (id) ); create table book_author ( book_id bigint not null, author_id bigint not null, role varchar(255), constraint pk_book_author primary key (book_id,author_id) ); 

Solo puedo hacer que eBean juegue bien con esto si agrego una key primaria sustituta ( id bigint auto_increment not null ) a la tabla book_author . Pero no quiero hacer eso porque:

  • La combinación de book_id y author_id debe ser única
  • La tabla debe estar indexada en book_id y author_id
  • Las keys sustitutas tienen sentido como identificadores únicos inmutables. En este caso, la key natural compuesta cumple la misma function: si se elimina el libro o el autor con un ID que coincida con un logging en book_author, entonces el logging book_author también debe eliminarse (en delete cascade).

En resumen, esto es para lo que está diseñada una key primaria compuesta. De hecho, si no hay ningún campo de role en book_author , eBean genera correctamente la tabla sin que yo haga ninguna class correspondiente de Kotlin / Java .

Este es mi mejor bash con el código de Kotlin (creo que Java tendría el mismo problema):

 // Puts an ID field (surrogate key) on tables that extend this. @MappedSuperclass abstract class BaseModel { @Id var id:Long = 0 } @Entity class Author(@Column var name: String): BaseModel() { @OneToMany(mappedBy = "authors") val books:MutableList<Book> = mutableListOf() } @Entity class Book(@Column var title:String) : BaseModel() { @OneToMany(mappedBy = "book") // mapped in both directions! val authors:MutableList<Author> = mutableListOf() } // BREAKS: missing the primary key. // BREAKS: book.getBookAuthors() always returns zero // DOESNT INITIALIZE PROPERLY: ebean.find(BookAuthor.class) @Entity @Table(uniqueConstraints = arrayOf(UniqueConstraint(columnNames = arrayOf("book_id", "author_id")))) class BookAuthor(@ManyToOne @Key val book:Book, @ManyToOne @Key val author:Author) { @Column var role:String = "" } 

Esto es lo que se ve book_author en db-create-all.sql:

 create table book_author ( role varchar(255), book_id bigint, author_id bigint, constraint uq_book_author_author_id_book_id unique (author_id,book_id) ); alter table book_author_assoc add constraint fk_book_author_assoc_book_id foreign key (book_id) references book (id) on delete restrict on update restrict; create index ix_book_author_assoc_book_id on book_author_assoc (book_id); alter table book_author_assoc add constraint fk_book_author_assoc_author_id foreign key (author_id) references author (id) on delete restrict on update restrict; create index ix_book_author_assoc_author_id on book_author_assoc (author_id); 

Como BookAuthor BaseModel (para tablas similares), acabo de hacer que BookAuthor amplíe BaseModel que agrega un campo de identificación a BookAuthor. Entonces todo solo funciona. Pero hacer eso simplemente se siente mal de alguna manera.

Pregunta muy similar: No se puede crear una key primaria compuesta con key externa en PLAY 2.0

Actualizar:

Seguí un Tutorial de Claves Compuestas JPA y creé una class de key compuesta Editable.

 @Embeddable class BookAuthorCompositeKey(@Column var book: Book?, @Column var author: Author?) : Serializable { // Your class must have a no-arq constructor constructor() : this(null, null) override fun equals(other: Any?): Boolean = (other is BookAuthorCompositeKey) && (aook == other.Book) && (author == other.Author) override fun hashCode(): Int { var ret:Int = 0 try { ret += book!!.hashCode() } catch (ignore:NullPointerException) { } try { ret += author!!.hashCode() } catch (ignore:NullPointerException) { } return ret } } 

Luego se actualizó BookAuthor con la nueva class, y se agregó en cascada a las relaciones en Libro y Autor:

 @Entity class Author(@Column var name: String): BaseModel() { @OneToMany(cascade = arrayOf(CascadeType.ALL)) // CHANGED val books:MutableList<Book> = mutableListOf() } @Entity class Book(@Column var title:String) : BaseModel() { @OneToMany(cascade = arrayOf(CascadeType.ALL)) // CHANGED val authors:MutableList<Author> = mutableListOf() } @Entity class BookAuthor(@EmbeddedId val back:BookAuthorCompositeKey) { @Column var role:String = "" } 

Pero db-create-all.sql parece incluso peor: ¡solo tiene una reference a una de las tablas!

create table book_author (book_id bigint no null, role varchar (255));

Hmm … Estoy empezando a pensar que eBean requiere una key sustituta.