Dmitry Malys
Dmitry Malys

Reputation: 1323

Hibernate Creating Or Updating Entity triggers update on related (Child) Entity

Whenever I update UserEntity hibernate triggers an update on the related UserRoles table. I found similar issues here but could not find a working solution.

@Entity
@Table(name = "users")
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserEntity implements Serializable {

  @Id
  @GeneratedValue(strategy = AUTO)
  private UUID uuid;

  @Column(name = "first_name")
  private String firstName;

  @Column(name = "last_name")
  private String lastName;

  private String email;

  @JsonIgnore private String password;

  @JsonIgnore
  @OneToMany(mappedBy = "user", fetch = LAZY, cascade = REMOVE)
  @Builder.Default
  private List<UserRoleEntity> roles = new ArrayList<>();
@Entity
@Table(name = "user_roles")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EntityListeners(AuditingEntityListener.class)
@TypeDef(name = "enum_postgres_sql", typeClass = PostgresEnumType.class)
public class UserRoleEntity {

  @Id
  @GeneratedValue(strategy = AUTO)
  private UUID uuid;

  @Column(name = "role")
  @Enumerated(STRING)
  @Type(type = "enum_postgres_sql")
  private Role role;

  @ManyToOne
  @JoinColumn(name = "user_uuid", updatable = false)
  private UserEntity user;

  @Type(
      type = "com.vladmihalcea.hibernate.type.array.ListArrayType",
      parameters = {@Parameter(name = ListArrayType.SQL_ARRAY_TYPE, value = "permission")})
  @Column(name = "permissions", columnDefinition = "permission[]")
  @Builder.Default
  private List<Permission> permissions = new ArrayList<>();

  @CreatedBy @OneToOne private UserEntity createdBy;

  @LastModifiedBy @OneToOne private UserEntity lastModifiedBy;
}

Create user:

UserEntity user =
        UserEntity.builder()
            .firstName(dto.getFirstName())
            .lastName(dto.getLastName())
            .email(dto.getEmail())
            .createdAt(LocalDateTime.now())
            .build();
repository.save(user);

Update user:

 repository
        .findById(uuid)
        .ifPresentOrElse(
            e -> {
              e.setFirstName(dto.getFirstName());
              e.setLastName(dto.getLastName());
              if (!StringUtils.equals(dto.getEmail(), e.getEmail())) {
                e.setEmail(dto.getEmail());
              }
              e.setUpdatedAt(LocalDateTime.now());
              repository.save(e);
            },
            () -> {
              LOGGER.error("UserEntity with uuid: {} not found", uuid);
              throw new UserEntityNotFoundException(uuid);
            });

Repo

import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<UserEntity, UUID> {}

Does anyone have an idea how to prevent this?

The reason I want to prevent this is that I'm using triggers in Postgresql to write logs of user actions and unnecessary updates mess things up... :(

Here are some logs:

2021-07-07 01:17:04,748 INFO  o.s.d.r.c.DeferredRepositoryInitializationListener:53 - Spring Data repositories initialized!
2021-07-07 01:17:04,768 INFO  c.s.j.l.u.w.UserLogControllerGetCreatedUserIntegrationTest:61 - Started UserLogControllerGetCreatedUserIntegrationTest in 36.711 seconds (JVM running for 39.424)
2021-07-07 01:17:04,936 DEBUG c.s.j.t.m.DatabaseMixin:31 - TestContainers database properties: test@jdbc:postgresql://localhost:56611/test?loggerLevel=OFF
2021-07-07 01:17:05,112 DEBUG o.h.SQL:128 - 
    insert 
    into
        users
        (changed_by_uuid, company_uuid, created_at, email, first_name, last_name, password, status, type, updated_at, uuid) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2021-07-07 01:17:05,115 TRACE o.h.t.d.s.BasicBinder:52 - binding parameter [1] as [OTHER] - [null]
2021-07-07 01:17:05,115 TRACE o.h.t.d.s.BasicBinder:52 - binding parameter [2] as [OTHER] - [null]
2021-07-07 01:17:05,116 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [3] as [TIMESTAMP] - [2021-07-07T01:17:05.104298]
2021-07-07 01:17:05,117 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [4] as [VARCHAR] - [[email protected]]
2021-07-07 01:17:05,117 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [5] as [VARCHAR] - [John]
2021-07-07 01:17:05,118 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [6] as [VARCHAR] - [Doe]
2021-07-07 01:17:05,118 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [7] as [VARCHAR] - [$2a$10$woVXWhuoHEqoIZMlul6/4.cc.33AyTCUc8nDWDiqImuj0vqlY.PK.]
2021-07-07 01:17:05,118 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [10] as [TIMESTAMP] - [2021-07-07T01:17:05.104346]
2021-07-07 01:17:05,119 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [11] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,140 DEBUG o.h.SQL:128 - 
    insert 
    into
        user_roles
        (changed_by_uuid, permissions, role, user_uuid, uuid) 
    values
        (?, ?, ?, ?, ?)
2021-07-07 01:17:05,140 TRACE o.h.t.d.s.BasicBinder:52 - binding parameter [1] as [OTHER] - [null]
2021-07-07 01:17:05,141 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [2] as [ARRAY] - [[Ljava.lang.Object;@24be7cee]
2021-07-07 01:17:05,152 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [4] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,153 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [5] as [OTHER] - [2fa664b9-f9cd-4092-a38b-c46d47c0ab95]
2021-07-07 01:17:05,156 DEBUG o.h.SQL:128 - 
    update
        user_roles 
    set
        changed_by_uuid=?,
        permissions=?,
        role=?,
        user_uuid=? 
    where
        uuid=?
2021-07-07 01:17:05,156 TRACE o.h.t.d.s.BasicBinder:52 - binding parameter [1] as [OTHER] - [null]
2021-07-07 01:17:05,157 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [2] as [ARRAY] - [[CREATE]]
2021-07-07 01:17:05,157 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [4] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,157 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [5] as [OTHER] - [2fa664b9-f9cd-4092-a38b-c46d47c0ab95]
2021-07-07 01:17:05,165 DEBUG o.h.SQL:128 - 
    insert 
    into
        user_roles
        (changed_by_uuid, permissions, role, user_uuid, uuid) 
    values
        (?, ?, ?, ?, ?)
2021-07-07 01:17:05,165 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [1] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,165 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [2] as [ARRAY] - [[Ljava.lang.Object;@4bc4b33b]
2021-07-07 01:17:05,166 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [4] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,166 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [5] as [OTHER] - [afe1ed15-005f-4c2e-8d55-bd3db570dc2e]
2021-07-07 01:17:05,168 DEBUG o.h.SQL:128 - 
    update
        user_roles 
    set
        changed_by_uuid=?,
        permissions=?,
        role=?,
        user_uuid=? 
    where
        uuid=?

Upvotes: 0

Views: 605

Answers (2)

Dmitry Malys
Dmitry Malys

Reputation: 1323

The permissions were mapped badly

This is the way it should look

permissions column is mutable and Hibernate flushes again because it can't determine dirtiness

The solution was:

  @Type(
      type = "com.vladmihalcea.hibernate.type.array.EnumArrayType",
      parameters = {@Parameter(name = EnumArrayType.SQL_ARRAY_TYPE, value = "permission")})
  @Column(name = "permissions", columnDefinition = "permission[]")
  private Permission[] permissions;

Upvotes: 0

Dmitry Malys
Dmitry Malys

Reputation: 1323

Actually, it is a bug in the Hibernate: HHH-5855

I used List instead of Set in many-to-many

Upvotes: 1

Related Questions