Micronaut 数据:使用自动生成的父 ID 插入父子关系失败,导致未设置外键

Chr*_*ian 5 micronaut-data

偶然发现一个问题:在一对多关系中,似乎从父实体自动生成的主键不会馈送到子实体。

我将生产代码归结为以下示例:

  1. 实体,父级和子级的一对多关系,均具有数据库生成的主键字段(Kotlin 代码):
import io.micronaut.data.annotation.GeneratedValue
import io.micronaut.data.annotation.Id
import io.micronaut.data.annotation.MappedEntity
import io.micronaut.data.annotation.Relation
import io.micronaut.data.annotation.Relation.Cascade.PERSIST
import io.micronaut.data.annotation.Relation.Kind.ONE_TO_MANY
import io.micronaut.data.model.naming.NamingStrategies

@MappedEntity("Parent", namingStrategy = NamingStrategies.Raw::class)
data class Parent(
  @GeneratedValue @field:Id val id: Long?,
  val name: String,
  @field:Relation(ONE_TO_MANY, mappedBy = "parent_id", cascade = [PERSIST])
  val children: List<Child>? = emptyList()
) {
  // Copy-constructor, used by ORM:
  constructor(
    name: String,
    children: List<Child>?
  ) : this(
    null,
    name,
    children
  )
}

@MappedEntity("Child", namingStrategy = NamingStrategies.Raw::class)
data class Child(
  @GeneratedValue @field:Id val id: Long?,
  val parent_id: Long?,
  val description: String,
) {
  // Copy-constructor, used by ORM:
  constructor(
    description: String,
  ) : this(null, null, description)
}
Run Code Online (Sandbox Code Playgroud)
  1. 用于持久化父实体的存储库代码 (Kotlin):
import io.micronaut.data.annotation.Repository
import io.micronaut.data.jdbc.annotation.JdbcRepository
import io.micronaut.data.model.query.builder.sql.Dialect.H2
import io.micronaut.data.repository.GenericRepository
import javax.transaction.Transactional

@Repository
@JdbcRepository(dialect = H2)
interface ParentRepository : GenericRepository<Parent, Long> {

  @Transactional
  fun save(parent: Parent): Parent

}
Run Code Online (Sandbox Code Playgroud)
  1. 现在是测试用例。它定义数据库模式并使用内存 H2 数据库创建它。它尝试将单个父实例与一个子实例持久化(用 Spock/Groovy 编写):
import groovy.sql.Sql
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import spock.lang.Specification

import javax.sql.DataSource

@MicronautTest
class ParentRepositorySpec extends Specification {

  @Inject
  DataSource dataSource
  @Inject
  ParentRepository parentRepository

  def 'Inserting parent with single child'() {
    given:
    loadDatabaseSchemaTo(dataSource)
    def child = new Child('Single child')
    def parent = new Parent('Parent', List.of(child))

    when:
    def savedParent = parentRepository.save(parent)

    then:
    savedParent.id != null // Primary Key field gets populated
    savedParent.items[0].id != null // Same with primary key field of child item
  }

  static void loadDatabaseSchemaTo(DataSource dataSource) {
    def createTableParent = '''
CREATE TABLE `Parent` (
  `id` int(5) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
)'''
    def createTableChild = '''
CREATE TABLE `Child` (
  `id` int(5) NOT NULL AUTO_INCREMENT,
  `parent_id` int(5) NOT NULL,  -- <= Mandatory foreign key
  `description` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  FOREIGN KEY (`parent_id`) REFERENCES `Parent` (`id`)
)'''
    new Sql(dataSource).with { sql ->
      sql.execute(createTableParent)
      sql.execute(createTableChild)
    }
  }

}
Run Code Online (Sandbox Code Playgroud)

子表外键字段“parent_id”定义为强制:没有父实体的子实体。

但执行测试用例会抛出:

io.micronaut.data.exceptions.DataAccessException: SQL Error executing INSERT: SQL error executing INSERT: NULL not allowed for column "PARENT_ID"; SQL statement:
INSERT INTO `Child` (`parent_id`,`description`) VALUES (?,?) [23502-200]
Run Code Online (Sandbox Code Playgroud)

日志记录显示,先执行对表“Parent”的插入,然后执行对表“Child”的“批量 SQL 插入”...这会中断。

所以对我来说,在发出针对表“child”的 INSERT 语句之前,从父实体自动生成的主键似乎不会传播到子实体的字段“parent_id”。

我的代码中缺少什么?