TouK / krush

Idiomatic persistence layer for Kotlin
Apache License 2.0
248 stars 13 forks source link

Self-referenced entities generate invalid code #22

Closed cbadger85 closed 2 years ago

cbadger85 commented 4 years ago

When attempting to create self referencing entities, the app fails to build because the generated code creates an invalid getOrNull method.

example:

@Entity
@Table(name = "category")
data class Category(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    @Column
    val name: String,

    @OneToOne
    @JoinColumn(name = "parent_id")
    val parent: Category?,

    @OneToMany(mappedBy = "parent")
    val children: List<Category> = emptyList()
)

generates:

  resultRow.getOrNull(CategoryTable.id)?.let {
    val children_category = children_map.filter { category -> category.key == it }
        .mapValues { (_, category) -> category.copy(category = category) }
        .values.toMutableSet()
    category_children[categoryId]?.addAll(children_category) ?: category_children.put(categoryId, children_category)
  }

The issue is here mapValues { (_, category) -> category.copy(category = category) }, where the copy method is using a category parameter that doesn't exist. I believe the proper code should be mapValues { (_, category) -> category.copy(parent = category) }.

Any help would be appreciated!

EDIT: Actually, it seems like any self-referenced parent-child entity causes problems. I tried this code to use a join table instead and got a error: Exception while building entity graph: java.lang.NullPointerException

@Entity
@Table(name = "category")
data class Category(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    @Column
    val name: String,

    @ManyToMany
    @JoinTable(name="category_category",
        joinColumns=[JoinColumn(name="category_id_1")],
        inverseJoinColumns=[JoinColumn(name="category_id_2")])
    val parentCategories: List<Category> = emptyList(),

    @ManyToMany(mappedBy="parentCategories")
    val childrenCategories: List<Category> = emptyList()
)
Namnodorel commented 4 years ago

Which version of krush are you using? I made some changes for self-reference-support a while ago, but I am not sure whether that is in a release yet. Though I didn't test the combination of self-references + bidir, so that might be a problem.

Pilleo commented 4 years ago

Guys, any updates? Considering to use krush, but that bug could be a blocker for that

pjagielski commented 4 years ago

Recursive structures are not supported for now, krush needs to know what to load in advance. There is a Tree example here : https://github.com/TouK/krush/blob/master/example/src/main/kotlin/pl/touk/krush/one2many/bidi/Tree.kt when you keep all the levels of structure in different entities - you'd have to divide your model eg. ParentCategory, Category, SubCategory.

pjagielski commented 2 years ago

Hi, This is now addressed in 0.6.0 release by using aliases:

            val parentAlias = CategoryTable.alias("parent")

            val categories =
                CategoryTable.join(parentAlias, JoinType.LEFT, CategoryTable.parentId, parentAlias[CategoryTable.id])
                .selectAll()
                .map { it.toCategory(parentAlias) }

Full example: https://github.com/TouK/krush/blob/master/example/src/test/kotlin/pl/touk/krush/realreferences/CategoryTest.kt