如果更改了根对象中的简单 属性,JaVers 会检测子对象的更改

JaVers detects changes in childs if simple property in root object is changed

我使用 Kotlin,我正在尝试将两个复杂对象(具有多个循环)与 JaVers 进行比较。这些对象使用多个 Id-Properties。因此,我创建了 Id-类 以便每个 class 都有一个 Id-属性。在 Id-类 中,我还使用了对根对象的引用,因为我需要使用它们来为我的数据库创建主键。

当我将两个对象与根对象中的单个更改进行比较时,JaVers 应该只列出一个 ValueChange。但是 JaVers 却发现了 5 个更改(NewObject-child、ObjectRemoved-child、ReferenceChanged-child、ListChange-root、ValueChanged-root)。 为了解决这个问题,我更新了子对象的 equals 和 hashCode 方法,以在计算相等性时检查根对象的 id 而不是根对象本身 ==> root1.childList == root2.childList returns true. 有什么想法可以教 JaVers 没有子对象发生变化吗?

League.kt - 根对象

@Entity
data class League(@EmbeddedId val leagueId: LeagueId? = null,
                  var name: String? = null,
                  var region: String? = null,
                  @OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true)
                  var groups: List<TeamGroup>? = null,
                  @OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true)
                  var matchDays: List<MatchDay>? = null) : Serializable {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as League

        if (leagueId != other.leagueId) return false
        if (name != other.name) return false
        if (region != other.region) return false
        if (groups?.map { it.teamGroupId }?.toSet() != other.groups?.map { it.teamGroupId }?.toSet()) return false
        if (matchDays?.map { it.matchDayId }?.toSet() != other.matchDays?.map { it.matchDayId }?.toSet()) return false

        return true
    }

    override fun hashCode(): Int {
        var result = leagueId?.hashCode() ?: 0
        result = 31 * result + (name?.hashCode() ?: 0)
        result = 31 * result + (region?.hashCode() ?: 0)
        result = 31 * result + (groups?.map { it.teamGroupId }?.toSet()?.hashCode() ?: 0)
        result = 31 * result + (matchDays?.map { it.matchDayId }?.toSet()?.hashCode() ?: 0)
        return result
    }
}

LeagueId.kt - 根对象 ID

data class LeagueId(val season : String? = null, val abb : String? = null) : Serializable {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as LeagueId

        if (season != other.season) return false
        if (abb != other.abb) return false

        return true
    }

    override fun hashCode(): Int {
        var result = season?.hashCode() ?: 0
        result = 31 * result + (abb?.hashCode() ?: 0)
        return result
    }
}

TeamGroup.kt - 子对象

@Entity
data class TeamGroup(@EmbeddedId val teamGroupId: TeamGroupId? = null,
                     val name: String? = null,
                     val mode: String? = null,
                     val tableMode: Int? = null,
                     @OneToMany(mappedBy = "group", cascade = [CascadeType.ALL], orphanRemoval = true)
                     var teams: List<Team>? = null,
                     @OneToMany(mappedBy = "group", cascade = [CascadeType.ALL], orphanRemoval = true)
                     var matches: List<Match>? = null,
                     var remarks: String? = null,
                     @OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true)
                     var rows: List<Row>? = null) : Serializable {

    override fun toString(): String {
        return "TeamGroup(id=${teamGroupId?.id}, nr=${teamGroupId?.nr}, name=$name, mode=$mode, " +
                "tableMode=$tableMode, teams=$teams, matches=$matches, remarks=$remarks, rows=$rows, " +
                "league=${teamGroupId?.league?.leagueId?.season}-${teamGroupId?.league?.leagueId?.abb})"
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as TeamGroup

        if (teamGroupId != other.teamGroupId) return false
        if (name != other.name) return false
        if (mode != other.mode) return false
        if (tableMode != other.tableMode) return false
        if (teams?.map{it.id}?.toSet() != other.teams?.map{it.id}?.toSet()) return false
        if (matches?.map{it.matchId}?.toSet() != other.matches?.map{it.matchId}?.toSet()) return false
        if (remarks != other.remarks) return false
        if (rows?.map{it.rowId}?.toSet() != other.rows?.map{it.rowId}?.toSet()) return false

        return true
    }

    override fun hashCode(): Int {
        var result = teamGroupId?.hashCode() ?: 0
        result = 31 * result + (name?.hashCode() ?: 0)
        result = 31 * result + (mode?.hashCode() ?: 0)
        result = 31 * result + (tableMode ?: 0)
        result = 31 * result + (teams?.map{it.id}?.toSet()?.hashCode() ?: 0)
        result = 31 * result + (matches?.map{it.matchId}?.toSet()?.hashCode() ?: 0)
        result = 31 * result + (remarks?.hashCode() ?: 0)
        result = 31 * result + (rows?.map{it.rowId}?.toSet()?.hashCode() ?: 0)
        return result
    }
}

TeamGroupId.kt - 子对象 ID

data class TeamGroupId(@ManyToOne val league: League? = null, val id : Int? = null, val nr : Int? = null) : Serializable {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as TeamGroupId

        if (league?.leagueId != other.league?.leagueId) return false
        if (id != other.id) return false
        if (nr != other.nr) return false

        return true
    }

    override fun hashCode(): Int {
        var result = league?.leagueId?.hashCode() ?: 0
        result = 31 * result + (id ?: 0)
        result = 31 * result + (nr ?: 0)
        return result
    }
}

更新

问题是在子对象的 ID 中引用了根对象。如果我从 id 中删除此引用并移动到对象本身,则 JaVers 只会检测到一个更改。由于我的数据模型,我不确定是否可以在每个 id 对象中删除此引用。 @DiffIgnore 在 Id-属性 中不起作用,因为它被作为 ValueObject 处理。

问题是由您的实体的 InstanceId 值错误引起的。 由于您有复杂的对象作为实体 ID,因此 JaVers 使用 reflectiveToString() 函数来创建 ID 的字符串表示形式。 在您的情况下,它会产生非常糟糕的结果,因为您有循环(ID 具有对拥有实体的引用)。

幸运的是,您可以使用 JaversBuider.registerValueWithCustomToString() 注册自定义 toString() 函数,例如:

@TypeName("Entity")
class Entity {
    @Id Point id
    String data
}

class Point {
    double x
    double y

    String myToString() {
        "("+ (int)x +"," +(int)y + ")"
    }
}

def "should use custom toString function for complex Id"(){
  given:
  Entity entity = new Entity(id: new Point(x: 1/3, y: 4/3))

  when: "default reflectiveToString function"
  def javers = JaversBuilder.javers()
          .build()
  GlobalId id = javers.getTypeMapping(Entity).createIdFromInstance(entity)

  then:
  id.value() == "Entity/0.3333333333,1.3333333333"

  when: "custom toString function"
  javers = JaversBuilder.javers()
          .registerValueWithCustomToString(Point, {it.myToString()})
          .build()
  id = javers.getTypeMapping(Entity).createIdFromInstance(entity)

  then:
  id.value() == "Entity/(0,1)"
}

另请参阅有关实体 ID 的更新文档 https://javers.org/documentation/domain-configuration/#entity-id